1001 Things You Wanted To Know About Visual FoxPro phần 9 potx

93 498 1
1001 Things You Wanted To Know About Visual FoxPro phần 9 potx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Chapter 11: Forms and Other Visual Classes 369 toControl.AddProperty( 'nOriginalTop', toControl.Top ) ENDIF IF PEMSTATUS( toControl, 'Left', 5 ) toControl.AddProperty( 'nOriginalLeft', toControl.Left ) ENDIF IF PEMSTATUS( toControl, 'Fontsize', 5 ) toControl.AddProperty( 'nOriginalFontSize', toControl.FontSize ) ENDIF Next we check to see if the current object is a container. If it is and it contains other objects, we will have to pass them to this method recursively: DO CASE CASE UPPER( toControl.BaseClass ) = 'PAGEFRAME' FOR EACH loPage IN toControl.Pages This.SaveOriginalDimensions( loPage ) ENDFOR CASE INLIST( UPPER( toControl.BaseClass ), 'PAGE', 'CONTAINER' ) FOR EACH loControl IN toControl.Controls This.SaveOriginalDimensions( loControl ) ENDFOR CASE INLIST( UPPER( ALLTRIM( toControl.BaseClass ) ), 'COMMANDGROUP', 'OPTIONGROUP' ) LOCAL lnButton FOR lnButton = 1 TO toControl.ButtonCount This.SaveOriginalDimensions( toControl.Buttons[lnButton] ) ENDFOR We can also handle the special cases here. For example, grids have RowHeight and HeaderHeight properties that need to be saved and we need to save the original widths of all of its contained columns. Combo and List boxes also require special handling to save the original ColumnWidths: CASE UPPER( toControl.BaseClass ) = 'GRID' WITH toControl .AddProperty( 'nOriginalRowHeight', .RowHeight ) .AddProperty( 'nOriginalHeaderHeight', .HeaderHeight ) .AddProperty( 'nOriginalColumnWidths[1]' ) DIMENSION .nOriginalColumnWidths[ .ColumnCount ] FOR lnCol = 1 TO .ColumnCount .nOriginalColumnWidths[lnCol] = .Columns[lnCol].Width ENDFOR ENDWITH CASE INLIST( UPPER( toControl.BaseClass ), 'COMBOBOX', 'LISTBOX' ) WITH toControl .AddProperty( 'nOriginalColumnWidths', .ColumnWidths ) ENDWITH OTHERWISE *** There is no otherwise I think we got all cases ENDCASE 370 1001 Things You Always Wanted to Know About Visual FoxPro Having saved all the original values, we need to ensure that the resizer is invoked whenever the form is resized. A single line of code in the form's Resize method is all that is needed: Thisform.cusResizer.AdjustControls() The custom AdjustControls method loops through the form's controls collection in much the same way as the SaveOriginalDimensions method. In this case, however, the method invokes the ResizeControls method to resize and reposition the controls using the form's current width divided by its original width as the factor by which its contained controls are made wider or narrower. The sample form Resize.SCX shows how the class can be used to handle resizing a reasonably complex form. However, a word of caution is in order here. This class will not cope with objects that are added at run time using delayed instantiation. This is simply because the set up assumes that all objects exist before the resizer itself is instantiated. If you need to use delayed instantiation in a resizable form, call the resizer's SaveOriginalDimensions method explicitly with a reference to the newly added object and then immediately invoke the ResizeControls method. Certain other classes that we introduced earlier in this book would also require modification to function properly in a resizable form. For example, when used in a resizable form the expanding edit box introduced in Chapter 4 requires that its SaveOriginalDimensions method be called each time it is expanded. Since its size and position changes whenever the form is resized, it is not enough to store this information once when the edit box is instantiated. How do I search for particular records? (Example: SearchDemo.scx and Srch.scx) The ability to find a specific record based on some sort of search criterion is a very common requirement. We have created a generic 'pop-up' form (Srch.scx) which can be used to search for a match on any field in a given table. You just need to be sure that any form that calls it, does so with the following parameters in the order in which they are listed. Table 11.1 Parameters passed to the search form Parameter Name Parameter Description ToParent Object reference to the calling form TcAlias Alias in which to perform the search TcField Field in which to search for a match TcAction Action to take when a match is found Chapter 11: Forms and Other Visual Classes 371 Figure 11.3 Generic search form in action Each object on the calling form must be capable of registering itself with this form as the current object. This is required because when the user clicks on the 'search' button, the button becomes the form's ActiveControl. However, we want to search on the field bound to the control that was the ActiveControl prior to clicking on the search button, so we need some way of identifying it. All we need to achieve this functionality is a custom form property called oActiveControl and the following code in the LostFocus method of our data aware controls: IF PEMSTATUS( Thisform, 'oActiveControl', 5 ) Thisform.oActiveControl = This ENDIF The search form is instantiated with this code in the calling form's custom Search method. Notice that the search form is only instantiated if the calling form does not have a reference to one that already exists: *** First see if we already have a search form available IF VARTYPE( Thisform.oChild ) = 'O' *** If we have one, just set the field to search Thisform.oChild.cField = JUSTEXT( This.oActiveControl.ControlSource ) Thisform.oChild.Activate() ELSE DO FORM Srch WITH This, This.cPrimaryTable, ; JUSTEXT( This.oActiveControl.ControlSource ), ; 'This.oParent.oActiveControl.SetFocus()' 372 1001 Things You Always Wanted to Know About Visual FoxPro ENDIF *** This works because all controls that are referenced in code *** have a three character prefix defining what they are ( e.g., txt ) *** followed by a descriptive name Thisform.oChild.Caption = 'Search for ' + SUBSTR( This.oActiveControl.Name, 4 ) The search form saves the parameters it receives to custom form properties so they are available to the entire form. It also sends a reference to itself back to the calling form so that the calling form is able to release the search form (if it still exists) when it is released: LPARAMETERS toParent, tcAlias, tcField, tcAction IF DODEFAULT( toParent, tcAlias, tcField, tcAction ) WITH Thisform *** Save reference to the form that kicked off the search .oParent = toParent *** Also save the table to search and the field name to search .cAlias = tcAlias .cField = tcField *** Finally, save the action to take when a match is found .cAction = tcAction *** Save Current record number .nRecNo = RECNO( .cAlias ) *** Give the parent form a reference to the search form *** So when we close the parent form we also close the search form .oParent.oChild = This ENDWITH ENDIF The search form has three custom methods, one for each of the command buttons. The Find method searches for the first match on the specified field to the value typed into the textbox like so: LOCAL lnSelect, lcField, luValue, lcAction *** Save current work area lnSelect = SELECT() WITH ThisForm If the field contains character data, we want to force the value in the textbox to uppercase. This is easily accomplished by placing a ! in its format property in the property sheet. Therefore, there is no need to perform this task in code: luValue = IIF( VARTYPE( .txtSearchString.Value ) = 'C', ; ALLTRIM( .txtSearchString.Value ), .txtSearchString.Value ) SELECT ( .cAlias ) Chapter 11: Forms and Other Visual Classes 373 We then check for an index tag on the specified field using our handy dandy IsTag() function. If we have a tag, we will use SEEK to find a match. Otherwise, we have to use LOCATE: IF IsTag( .cField ) SEEK luValue ORDER TAG ( .cField ) IN ( .cAlias ) ELSE lcField = .cField IF VARTYPE( EVAL( .cAlias + '.' + lcField ) ) = 'C' LOCATE FOR UPPER( &lcField ) = luValue ELSE LOCATE FOR &lcField = luValue ENDIF ENDIF If a match is found, we perform the next action that was passed to the form's Init method and save the record number of the current record. Otherwise, we display a message to let the user know that no match was found and restore the record pointer to where it was before we started the search: *** If a match was found, perform the next action IF FOUND() *** Save record number of matching record .nRecNo = RECNO( .cAlias ) SELECT ( lnSelect ) lcAction = .cAction &lcAction ELSE WAIT WINDOW 'No Match Found!' NOWAIT *** Restore Record Pointer GOTO .nRecNo IN (.cAlias ) SELECT ( lnSelect ) ENDIF ENDWITH The code in the FindNext method is very similar to the code in the Find method. Unfortunately, we must use LOCATE in the FindNext method because SEEK always finds the first match and always starts from the top of the file. The is the code that finds the next record, if there is one: *** If we are on the last record found, skip to the next record SKIP *** Must use LOCATE to Find next because seek always starts *** at the top and finds the first match lcField = .cField luValue = IIF( VARTYPE( .txtSearchString.Value ) = 'C', ; ALLTRIM( .txtSearchString.Value ), .txtSearchString.Value ) IF VARTYPE( EVAL( .cAlias + '.' + lcField ) ) = 'C' LOCATE FOR UPPER( &lcField ) = luValue REST ELSE LOCATE FOR &lcField = luValue REST 374 1001 Things You Always Wanted to Know About Visual FoxPro ENDIF The code in the search form's custom Cancel method cancels the search, repositions the record pointer, and closes the search form like so: WITH Thisform GOTO .nRecNo IN ( .cAlias ) .Release() ENDWITH Some of the most important code resides in the Destroy method of each form. If the search form did not have this code in its Destroy method: IF TYPE( 'Thisform.oParent.Name' ) = 'C' Thisform.oParent.oChild = .NULL. ENDIF it would refuse to go away when the user clicked on the CANCEL button because the form that called it would still be pointing to it. In this case, the reference held by the calling form is known as a dangling reference. The reference that the search form has to the calling form (This.oParent) cleans up after itself automatically, so it is less troublesome. When the search form is released, the reference it holds to the calling form is released too. This code in the calling form's Destroy method makes certain that when it dies, it takes the search form with it: *** Release the Search form if it is still open IF VARTYPE( Thisform.oChild ) # 'X' Thisform.oChild.Release() ENDIF How do I build SQL on the fly? (Example: FilterDemo.scx and BuildFilter.scx) Many excellent third-party tools are available and provide this functionality as well as the ability to create, modify, and print reports over the output of user-generated SQL. It is not our intention to re-invent the wheel here. If the requirements of your application for end-user generated SQL are complex, clearly the solution is one of these very fine products. Having said that, there are occasions when it is necessary to present the end-user with a means of building a very simple filter condition that can be applied to a single table. In this case, the third-party tool may provide a lot more functionality than you require. BuildFilter.scx is a simple filter builder and is provided with the sample code for this chapter. It builds the filter condition for a single alias. With slight modification and the addition of a pageframe, this form can easily be modified to join two tables (or more) prior to building the filter. Chapter 11: Forms and Other Visual Classes 375 Figure 11.4 Generic form to build filter condition Call BuildFilter.scx using this syntax: DO FORM BUILDFILTER WITH '<ALIAS>' TO lcSQLString You can then use the returned filter condition to run a SQL query like this: SELECT * FROM <alias> WHERE &lcSQLstring INTO CURSOR Temp NOFILTER or to set a filter on the specified alias like this: SELECT <alias> SET FILTER TO ( lcSQLstring ) The BuildFilter form expects to receive a table alias as a parameter and stores it to the custom cAlias property in order to make it available to the entire form. The custom SetForm method is then invoked to populate the custom aFieldNames array property that is used as the RowSource for the drop down list of field names pictured above. After ensuring that the specified alias is available, the SetForm method uses the AFIELDS() function to create laFields, a local array containing the names of the fields in the passed alias. Since we do not want to allow the user to include memo fields in any filter condition, we scan the laFields array to remove all memo fields like so: LOCAL lnFieldCnt, laFields[1], lnCnt, lcCaption, lnArrayLen WITH Thisform *** Make sure alias is available IF !USED( .cAlias ) USE ( .cAlias ) IN 0 376 1001 Things You Always Wanted to Know About Visual FoxPro ENDIF *** Get all the field names in the passed alias lnFieldCnt = AFIELDS( laFields, .cAlias ) *** Don't include memo fields in the field list lnArrayLen = lnFieldCnt lnCnt = 1 DO WHILE lnCnt <= lnArrayLen IF lnCnt > lnFieldCnt EXIT ENDIF IF TYPE( .cAlias + "." + laFields[ lnCnt, 1 ] ) = "M" =ADEL( laFields,lnCnt ) lnFieldCnt = lnFieldCnt - 1 ELSE lnCnt = lnCnt + 1 ENDIF ENDDO After the memo fields have been removed, the array, ThisForm.aFieldNames, is built using the remaining elements in the laFields array. Thisform.aFieldNames contains two columns. The first column contains the field's caption if the passed alias is a table or view in the current dbc and the caption property for the field is not empty. If the passed alias is a free table or its caption is empty, the first column of the array contains the name of the field. The second column of the array always contains the field name: DIMENSION .aFieldNames[ lnFieldCnt,2 ] FOR lnCnt = 1 TO lnFieldCnt lcCaption = "" IF !EMPTY( DBC() ) AND ( INDBC( .cAlias, 'TABLE' ) ; OR INDBC( .cAlias, 'VIEW' ) ) lcCaption = PADR( DBGetProp( .cAlias + "." + laFields[ lnCnt, 1 ], ; 'FIELD', 'CAPTION' ), 40 ) ENDIF IF EMPTY( lcCaption ) lcCaption = PADR( laFields[ lnCnt, 1 ], 40 ) ENDIF .aFieldNames[ lnCnt, 1 ] = lcCaption .aFieldNames[ lnCnt, 2 ] = PADR( laFields[ lnCnt, 1 ], 40 ) ENDFOR .cboFieldNames.Requery() .cboFieldNames.ListIndex = 1 ENDWITH It is the form's custom BuildFilter method that adds the current condition to the filter. It is invoked each time the ADD CONDITION button is clicked: LOCAL lcCondition WITH Thisform IF TYPE( .cAlias + '.' + ALLTRIM( .cboFieldNames.Value ) ) = 'C' lcCondition = 'UPPER(' + ALLTRIM( .cboFieldNames.Value ) + ') ' + ; ALLTRIM( Thisform.cboConditions.Value ) + ' ' ELSE lcCondition = ALLTRIM( .cboFieldNames.Value ) + ' ' + ; Chapter 11: Forms and Other Visual Classes 377 ALLTRIM( Thisform.cboConditions.Value ) + ' ' ENDIF *** Add the quotation marks if the field type is character IF TYPE( .cAlias + '.' + ALLTRIM( .cboFieldNames.Value ) ) = 'C' lcCondition = lcCondition + CHR( 34 ) + ; UPPER( ALLTRIM( .txtValues.Value ) ) + CHR( 34 ) ELSE lcCondition = lcCondition + ALLTRIM( .txtValues.Value ) ENDIF *** If there are multiple conditions and them together .cFilter = IIF( EMPTY( .cFilter ), lcCondition, .cFilter + ; ' AND ' + lcCondition ) ENDWITH Thisform.edtFilter.Refresh() There is no validation performed on the values entered for the filter condition. This is something that you will definitely want to add if you use this little form as the basis of a SQL generator in your production application. This could be accomplished by adding a ValidateCondition method to the form. This method would be called by the BuildCondition method and return a logical true if the text box contains a value appropriate for the data type of the field selected in the drop down list. Our Str2Exp function introduced in Chapter 2 would help to accomplish this goal. How can I simulate the Command Window in my executable? (Example: Command.scx) Occasionally you may find it necessary to walk an end-user of your production application through a simple operation such as browsing a specific table. You may think that your users need a copy of Visual FoxPro in order to do this. Not true! With a simple form, a couple of lines of code and a little macro expansion, you can easily create a command window simulator to help you accomplish this task. 378 1001 Things You Always Wanted to Know About Visual FoxPro Figure 11.5 Simulated Command Window for your executable The Command Window form consists of an edit box and a list box. The form's custom ExecuteCmd method is invoked whenever the user types a command into the edit box and presses the ENTER key. The command is also added to the list box using this code in the edit box's KeyPress method: *** IF <ENTER> Key is pressed, execute the command IF nKeyCode = 13 *** Make sure there is a command to execute IF !EMPTY( This.Value ) WITH Thisform *** Add the command to the command history list .lstHistory.AddItem( ALLTRIM( This.Value ) ) *** Execute the command .ExecuteCmd( ALLTRIM( This.Value ) ) ENDWITH *** Clear the edit box This.Value = '' This.SelStart = 0 ENDIF *** Don't put a carriage return in the edit box! NODEFAULT ENDIF A command may also be selected for execution by highlighting it in the list box and pressing ENTER or double-clicking on it. The list box's dblClick method has code to display [...]... much easier 404 1001 Things You Always Wanted to Know About Visual FoxPro Figure 12.7 Grid builder with enhanced functionality In order to use our improved builder, you first need to register it by running GridBuildReg.prg included with the sample code for this chapter This program adds a new record to Builder.dbf in the Visual FoxPro wizards subdirectory Builder.app uses this table to determine which... been added directly to the form is viewable in this way, inherited code will not be seen here): 396 1001 Things You Always Wanted to Know About Visual FoxPro Figure 12.4 Form Inspector - code display Double-clicking a property brings forward a dialog that allows you to change the value of that property (Warning: There is no error checking associated with this function and if you try to set a property... easy to do because, with the exception of the first three fields (Platform, UniqueID and Timstamp), all the information is held in memo fields So we have created a form to display the information contained in either a SCX or a VCX file more easily (Figure 12.1 below) Figure 12.1 SCX/VCX editor 392 1001 Things You Always Wanted to Know About Visual FoxPro Why do we need this? How often have you wanted to. .. caption of the calling form Finally it calls the custom SetUpForm method: LPARAMETERS toSceForm IF VARTYPE( toSceForm ) # "O" *** Not passed a form object RETURN F ENDIF 398 1001 Things You Always Wanted to Know About Visual FoxPro WITH ThisForm *** Save reference to Calling Form oCallingForm = toSceForm *** Set DataSession to same as the Calling Form DataSessionID = oCallingForm.DataSessionID *** Set Up... object on the form, interactively The form is designed to accept an object reference to an active form and to display information about that form in its pageframe Typically we use it by setting an On Key Label command like this: ON KEY LABEL CTRL+F9 DO FORM inspForm WITH _Screen.ActiveForm 394 1001 Things You Always Wanted to Know About Visual FoxPro The table information page When activated, the "Table... string Note that it doesn't matter how you actually store your user information, providing that you can create a view called lv_UserList with two fields (named UserName and Password) containing a list of valid Log-In Names and their associated Passwords for use by this form Figure 11 .9 Generic log-in form 386 1001 Things You Always Wanted to Know About Visual FoxPro Log-in form construction The log-in... in a relation, you need another way to do this This is when it is useful to have a text box that can perform a lookup and then display the result The lookup text box class, used to display the contact type in the Contacts.scx form pictured below, allows you to handle this task by merely setting a few properties 388 1001 Things You Always Wanted to Know About Visual FoxPro Figure 11.10 Lookup text box... because you can use them over and over again Not only will they help you produce applications more quickly, they will also help to reduce the number of bugs you have to fix because they get tested each time you use them We hope that this chapter has given you a few classes you can use immediately as well as some ideas for creating new classes that are even more useful 390 1001 Things You Always Wanted to. .. (lcTable) *** Get the record Details FOR lnCnt = 1 TO FCOUNT() lcField = FIELD( lnCnt ) lcFVal = TRANSFORM( &lcField ) INSERT INTO currec VALUES (lcField, lcFVal) NEXT SELECT (lnSelect) *** Populate and update the Grid WITH ThisForm.pgfMembers.Page1.grdCurRec RecordSource = "currec" 399 400 1001 Things You Always Wanted to Know About Visual FoxPro GO TOP IN currec SetFocus() ENDWITH *** Release the screen... Clearly, if the user made no changes to the contents of the textbox, we do not want to search for the postal code and populate the text boxes in the container: LOCAL vp_PostalCode, loLocation, lnTop, lnLeft *** Check to see if the user changed the postal code 384 1001 Things You Always Wanted to Know About Visual FoxPro *** If nothing was changed, we do not want to do anything IF ! This.lPostalCodeChanged . create a command window simulator to help you accomplish this task. 378 1001 Things You Always Wanted to Know About Visual FoxPro Figure 11.5 Simulated Command Window for your executable The Command. form. Figure 11 .9 Generic log-in form 386 1001 Things You Always Wanted to Know About Visual FoxPro Log-in form construction The log-in form is very simple indeed and has three custom properties. Contacts.scx form pictured below, allows you to handle this task by merely setting a few properties. 388 1001 Things You Always Wanted to Know About Visual FoxPro Figure 11.10 Lookup text box

Ngày đăng: 05/08/2014, 10:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan