Example files available in custforms.zip
NOTE: This document was originally written for Visual dBASE 7.0/7.01, it has been updated for dB2K (release 1), and later versions of dBASE to include information about new properties, events, etc., and any new controls since the document was first written. If a control is new it will be noted. In updating this document, the images were left "as is" unless it was felt to be absolutely necessary to change them ...
In addition, this document refers to dB2K a lot, but unless it is about a dB2K specific aspect, the text can be used for Visual dBASE 7.0 through Visual dBASE 7.5.
Let me begin by stating an opinion of mine that is shared by most, if not all, experienced dBASE developers: Never, ever use a stock dB2K form for anything! Sooner, or later, you will regret having done so. You will run across something that you need to change on all your forms. That will be a monstrous task, unless you heed this advice. Before you create your first form, you should create a base custom form, upon which all others are based. The same can be said for all the controls that you will use on your forms. Never use the stock controls. For the sake of simplicity, this document will use stock controls, but you are advised to read CustCtrl.how to learn more about custom controls.
What if your customer wants you to change the color of your forms to green instead of lightblue, or the company logo changes? All you would have to do is change the primary custom form's properties, and both your subclassed custom forms and all forms that are subclassed from those will inherit the changes!
The original examples for the Visual dBASE 5.x custom form (in the VdBASE 5.x version of CUSTFORM.HOW) were much more complex than necessary. In addition, we'll be looking at the dB2K versions of the code.
If you follow the examples provided, you will have several custom forms that will give you some good functionality (although you may want to re-do them based on your own aesthetics or that of your customer, the idea is to give you a start ...).
In the navigator window of dB2K, there are four icons that are marked "Untitled" when the notebook tab is set to "Forms". The yellow "form" icon is the one you need to click on to design a new custom form. The Form Designer will open with a new blank form to be designed. For the moment, let's make sure you aren't already using a custom form of some kind. Click on the File menu and select Set Custom Form Class. This will open a dialog box. If there is anything shown in the entryfields of the dialog, click on the Clear Custom Form Class button. Now click Ok. Finally, click on the File menu and select Save. In the dialog, type the word BASE and click Save. You may now close the Form. You have just created a custom form class. Of course, this custom form is no different from the stock form built in to dB2K, yet.
Now, just to demonstrate the power of custom forms, let's create another. Double click on the yellow "form" icon again. Again, this will open the form designer. Now click on the File menu and select Set Custom Form Class. This time, when the dialog opens, click on the small button with the wrench to the right of the entryfield for File Name Containing Class. In the next dialog, select "BASE" and click Open. This means that our new custom form will be based upon the template we've already created. Now click on the File menu and select save. In the dialog, type "SDIForm" and click the Save button.
This will be a form for use in an SDI application, so let's set some properties that might be appropriate for that purpose. If the Inspector is not displayed, press the F11 key to display it. In the Window category of the Inspector, set the forms properties as follows:
NOTE: There is an ... anomaly in the forms designer when subclassing custom forms to other custom forms. Sometimes the name of the custom form the new one is being subclassed from does not get written to the source code. The solution -- first thing when you bring a new custom form into the designer that is subclassed from another, go to the File menu, and select the "Set Custom Form Class" option. If the form class is correct, all you have to do is click "Ok", and the proper information will be streamed out (I would venture this is an assertion problem of some sort, but I'm not one of the developers of dB2K itself ...). In dB2K this problem may have been resolved ... I have not seen it in awhile.
Property | Value |
---|---|
autocenter | true |
autosize | false |
escexit | false |
maximize | false |
mdi | false |
minimize | true |
moveable | true |
sizeable | false |
windowstate | 0 - normal |
Now click the File menu and select Save. Close the form and you are done.
Next, let's create a form for our application. This time, double click on the white Form icon to create a new form (not a custom form). When the form designer opens, click on the File menu and select Set Custom Form Class. You will see that the Base form is still shown as the Custom Form Class. It's worth noting here, that once you set a custom form class, that selection is persistant and will remain until you either clear it, or select a different one. Click the wrench button again. This time select the SDIForm that we just created and click Open. Back in the Set Custom Form Class Dialog, click the Ok button. If your inspector is still displayed, you will immediately notice that this form has inherited the Window properties that we set in the SDIForm class.
If the Component palette is not displayed, click on the View menu, then Tool Windows. From the Tool Windows sub-menu, select Component Palette - Form Designer. Now, drag a pushbutton from the Component Palette to the form surface. In the Inspector, under the Miscellaneous category, enter "Close" for the text property. Click on the Events tab at the top of the Inspector and in the onClick event, type the following:
{;form.close()}
Save this form with a name of "Test" and close it. If you execute that form now, you'll see nothing spectacular, other than the fact that the form inherited the Window properties that we set in the SDIForm class. Now, suppose you wanted to change all your forms to be light blue in color. If your application had a lot of forms, this could be quite a task to go back and change all your forms. This is where custom forms and inheritance can show their power.
Double click on the Base custom form icon. This will open the custom form in the designer. In the Inspector, under the Visual category, type "LightBlue" (without the quotes) for the colorNormal property. Save and close the form. Now, execute the Test form. Viola! It's light blue. Not only that, but all forms based upon the Base class or its descendants will be light blue.
A word of caution here: Changing the form color will not affect the color of controls placed on the form. For this reason, you are urged to use the same philosophy regarding controls as has been stated for forms. Never use the stock controls. Always subclass. Refer to CustCtrl.how for more information on subclassing controls.
Size the form appropriately. This may take some thought in advance. Problem: Users can set their screen resolution on most video drivers to quite a few different settings (ranging from 640x480 up to much higher resolutions). There is no good way to deal with scaling your forms to match these settings. If you look in the form designer, and stretch a form out to the lower right pretty far, you can see a line going vertically and horizontally across the form -- the purpose of this is to show you the largest size you might want to make your form to fit in the 640x480 resolution. I recommend never making a form larger than this unless you know for certain that none of your users will use this screen resolution. (There have been attempts to create code that will resize itself based on the current resolution, but the code is unwieldy, and this will slow down the loading of the form.)
The SDIForm should still be the Custom Form class being used, so this form will inherit the properties we have set. The first thing we will do is place a title on the right side of the form. Click on a text object and then click on the form. A text object will be displayed on the form. If the Inspector is not displayed, Right Click on the text object and select "Inspector" from the popup. In the Inspector, set the FontSize to 18 point, FontBold to true. Under the Miscellaneous category, set the "Rotate" to "1 - 90 degrees" and "Transparent" to true. Set the name of the object to "Title1". Click and drag to resize the text object so that the text fits. Next, in the inspector, under the Position category, set the anchor property to "4 - Right". Set the colorNormal property to ready simply "Blue".
You can change the text in one of several ways, the easiest is to simply click on the text in the designer and type in something such as "Golden Stag Productions".
The text control should be on the right edge of the form (that is what the anchor property does), and atthe top (use your own set of aesthetics for exact location -- if you don't like the way this looks, set the anchor property to zero and adjust the location yourself ...).
The idea is that this is a logo -- you could also place an image for your firm on the form in, say, the upper right corner (again basing this on your own, or your customer's aesthetics).
Next, let's place another title at the top left of the form. Click on a text object on the Component palette and click on the form. Click and drag to move this text object to the upper left corner of the form. Set the fontSize, fontBold properties, transparent as before. Set the anchor to "2 - Top" and resize the object so that the text fits. Set the border property to true and the borderStyle property to "1 - Raised" as well, set the colorNormal to blue, and set the name of the object to "Title2". This is actually something where we will assume the text will change from each "subclassed form" to the next, as it will be used as a title for that specific form. Change the Text to "Primary Custom Form". (Again, the anchor property sets this control at the very top of the form -- if you would rather it didn't, set the anchor to zero and move the text control to where you need it to be.)
Press CTRL+S to save the form. Enter a name for this, such as "PRIMARY". dB2K saves this file as a .CFM file, rather than a standard .WFM.
You can exit the designer (CTRL+W or whatever keystrokes you wish), and now note in the Navigator that you have a form "PRIMARY.CFM" listed. This is your application's custom form.
We will look at using this form in a moment, but first, let's examine the code that was streamed out. (It is always a good idea to understand the code streamed out by the designer, because you may need to change something at some point ... indeed, we will be adding some code later that the designer doesn't know how to deal with ...)
class primaryCForm of SDIFORMCFORM custom from "sdiform.cfm" with (this) scaleFontBold = false height = 13.8182 left = 14.7143 top = 0.3182 width = 80.7143 text = "" endwith this.TEXT1 = new TEXT(this) with (this.TEXT1) height = 13.8182 left = 75.2857 top = 0 width = 5.4286 anchor = 4 // Right colorNormal = "Blue" fontSize = 18 fontBold = true rotate = 1 // 90 degrees text = "Golden Stag Productions" endwith this.TITLE2 = new TEXT(this) with (this.TITLE2) height = 1.5455 left = 0 top = 0 width = 75.2857 anchor = 2 // Top colorNormal = "blue" fontSize = 18 fontBold = true text = "Primary Custom Form" endwith endclass
The custom form is "subclassed" from the SDIForm class that we previously defined. The first statement streamed out, and everything between this and the "endclass" statement are now considered to be a part of this custom form.
Note the first statement includes the word "CUSTOM". This is very important, because without it, dB2K will assume this is just another form, not a Custom form. Note that the properties we set in the SDIFrom class are not streamed out. These are defined in the SDIForm class and should not be streamed out to any form that is derived from this form, unless the values are changed -- this defeats the purpose of setting them in the custom form.
This holds true for the two text controls that we placed onto the form as well -- these should not be changed unless absolutely necessary in any subclassed forms (sometimes I have found a need to move them around, espcially if you need to resize the form. although with the anchor property being set, this may not be necessary either ...).
We could have placed a ton of other controls directly onto the form, but again that defeats the purpose of a custom form. If you need specific controls for a form, you may not need them on all forms that are subclassed from this one.
The idea is that you might have one main custom form that defines the "look" of all the forms in your application, but then you might have special purpose custom forms that have more functionality specific to that purpose, derived from the primary one.
To do this, we need to open a new form in the designer, and tell the designer to use our "Primary" form:
Double click on the first "Untitled" icon in the Navigator under the "Forms" tab. This is the standard form icon.
With this form in the designer, we're now going to tell the designer to use the custom form we just created as the template for this form -- this is done through the "File" menu: In the dialog, select "PRIMARY.CFM".
Note that the custom form field is filled in. Click "OK".
Note that the new form now has the same properties and text controls of the Primary custom form we just created.
Click "OK".
Rather than simply saving this as a standard form, let's make sure we save it as a CUSTOM form (before we do anything else):
Select the "File" menu
Select "Save as Custom ..."
In the dialog, select the "Form" radio button, and enter "DataForm" for the class name, and "DataForm" for the file name. Click the "OK" button, and it's been saved.
Exit the designer, but DO NOT SAVE THE FORM again, and you should see "DataForm" in the navigator, but with the Custom "yellow" icon.
Rather than creating a bunch of custom controls for handling navigation in tables and such, in the command window, type:
// if using Visual dBASE 7.x: set procedure to "&_dbwinhome.custom\databuttons.cc" additive // if using dB2K (using a source alias): set procedure to :classes:databuttons.cc additive
This will load the DataButtons custom controls that shipped with dB2K. These controls will then appear on the DataButtons tab of the Component Palette of the Form designer. (You may not need to do this, but it can't hurt ... in theory this custom control file is automatically loaded by dB2K when you start the software.)
Double click the icon for "DataForm", which will automatically place this in the designer.
Change the text of the title that says "Primary Custom Form" to "Data Custom Form" (click on the text, and make the appropriate change).
In the component palette, drag a Container onto the form surface. In the component pallette, on the "DataButtons" tab, you will now see a BUNCH of controls. Drag a BarDataVcr control and drop it on the container. Drag a BarDataEdit control and drop it on the container. Place both of these near the top of the container. Now resize the container so it is just tall enough to contain the buttons.
NOTE: There is a bug in the form designer's streaming capabilities dealing with custom controls -- various controls will always stream out properties that they should not. The following code should not be streamed out, but in Visual dBASE 7.0 through 7.5 it is (in dB2K this is not an issue):
with (this.CONTAINER1.BARDATAVCR1.BITMAPFIRST) value = false endwith with (this.CONTAINER1.BARDATAVCR1.BITMAPPREVIOUS) value = false endwith (etc.)It is safe to delete this code in your custom form here, as well as in any derived forms. This means simply going into the source code and deleting it. You should wait to do this until you are done working on this form in the designer, since each time you bring the form back into the designer, the code will be streamed back out again.
Next, from the standard tab of the control palette, drag a TabBox control to the surface of the form. Make certain the TabBox control is selected and click the Events tab of the Inspector. Set the dataSource to read:
ARRAY {"Individual Record", "Find" }
In the "onSelChange" event, type:
{;form.pageno = this.curSel}
and hit the Enter key.
We're going to add a second page to the form. To do this, in the designer at the top of the screen in the toolbar are some "Page" buttons, click on the "Next Form Page" button. Don't worry that your controls all disappeared -- they were set to only display on Page 1, which is fine.
Now we're going to add an instance of the Seeker control with a text control next to it, and a grid. These are useful to help a user find a record they are looking for in a table.
On the "standard" tab of the component palette select the text control and drag it to the top of the form. Click on the text and change the text to "Find:", then set the transparent property to true (otherwise it will look really ugly). Next, on the custom tab of the component palette, find the entryfield that says "Seeker", and drag it over next to the text. You may want to make it wider than it defaults to by dragging the right side over ... NOTE: SEEKER.CC ships with dB2K and is in the "Classes" directory (in Visual dBASE 7.x, Seeker.cc is in the "Custom" directory instead) ...
Finally, we'll create a "non-editing" (view-only, whatever you wish to call it) grid. From the standard tab of the component palette drag the grid control to the form. Set the following properties:
Property | Value |
---|---|
allowAddRows | false |
allowColumnMoving | false |
allowColumnSizing | false |
allowEditing | false |
allowRowSizing | false |
integralHeight | true |
rowSelect | true |
Size the grid so it fills a good portion of the form.
Page two of your form should look like the image above. Select the "Previous Form Page" button from the toolbar, and save and exit the designer (Ctrl+W works great for this).
Now that you have this form set, what do you do with it?
To do this, we start out by creating a new form. In dB2K, use the normal method you would use to create a form -- double click on the first "Untitled" icon in the navigator.
NOTE that the template being used is for the "Primary Custom Form" -- we need to change this to our "Data Custom Form".
File
Set Custom Form Class ...
in the dialog that appears, enter the name of the CFM (or use the tool button to find the name):
File Name Containing Class:
DATAFORM.CFM
Class Name: DATAFORM
Click on the OK button. Your form will suddenly look just like the DataForm class we defined previously.
While this form looks like the custom Data form it is not that form -- this is a derived form -- it currently has all the properties and buttons and everything of the original form class. However, we can move objects around, add new objects, classes, methods, etc., and not affect the original at all.
Normally, when you place objects on a form, and move them, change them, etc., you get a set of handles that appear black on the form/object. Since the objects that are currently on the form are part of a custom form class, if you click on them, the handles are white to show that they are different.
To test how it works, click on the title (at the top of the form), and change the text to say "Fish Sample".
For our example, we will assume that you have the sample tables handy -- you may want to copy the "FISH" table to your test directory.
Next, go to the inspector, and click on the "Tables" tab. Drag the "Fish" table to the surface of the form.
You should see an "SQL" icon, which is the reference to the query object that references the table. (If you do not understand this, you should read the excellent OODML.HOW document created by Alan Katz.) Click on the inspector (or open it if you need to), and click on the "rowset" and "I" button, then set the indexName property to "NAME".
In the field palette you should see the fields you want to use. For now we will just drag these controls directly to the surface, although if you read HOW TO document "Custom Controls", you will see that you may want to do this differently for the future ...
Once you have dragged each field over, tinker with their locations a bit so that you are fairly happy with the results. The image control you may want to set the alignment property to "3 - Keep Aspect Stretch" (otherwise it may be a bit distorted).
Next, switch to page two of the form in the designer, and click on the grid. In the inspector, set the datalink to the table, and then go to the COLUMNS object and click the "wrench" or "tool" button -- in that dialog select some of the fields (make sure that the "Name" field is the first one), and click "OK". This sets the columns array up so that it works the way it should.
Go back to page one of the form. Press "Ctrl+S" or "Ctrl+W" to save the form -- enter "FISH" as the form's name.
Try running the form. Navigate. Go to the second page, and use the "seeker" control and type a letter or two to see that SEEKER works.
Lucky for you, you're using custom forms. Bring the BASE custom form into the designer (double-click the icon). Go to the colorNormal property in the inspector, and change the colorNormal to "NAVY". Save it. Now try running your FISH form. Note that this form shows the background to be dark blue (navy).
How much work was that? You changed one form, and any other form that is derived from it will always inherit those changes.
What happens if the company name changes, or if you are using a logo image and the logo changes? Well, the same sort of thing. You can make a single change in the primary custom form, and all derived forms will inherit that change. (OK, you may need to tinker with the colors of the text controls and such to make them more aesthetically pleasing against the "navy" background, but this could be done using custom controls, which I keep harping on ...)
The simplest thing to do is to set the VISIBLE property to false on the derived form -- this will make it disappear on the form that is derived from the custom form class, but will not change the layout of the custom form itself. In the form designer - click on the offending object, and then bring up the inspector. Find the VISIBLE property, and set it to false.
function Repaint parameter oForm if type( "oForm" ) == "U" or empty( oForm ) oForm = form endif if type( "UpdateWindow" ) # "FP" extern CLOGICAL UpdateWindow( CHANDLE ) USER32 endif RETURN UpdateWindow( oForm.hWnd )
To use this code in your own forms, since this is now a method of the form, treated it like any other method:
CLASS::REPAINT()
And if you need to call it to repaint a different form, you could call it as:
CLASS::REPAINT( oForm )
Where "oForm" is an object reference for the form in question.
DISCLAIMER: the author is an employee of dBASE, Inc., but has written this on his own time. If you have questions regarding this .HOW document, or about dB2K you can communicate directly with the author and dBVIPS in the appropriate newsgroups on the internet..HOW files are created as a free service by members of dBVIPS and dBASE, Inc. employees to help users learn to use dB2K more effectively. They are edited by both dBVIPS members and dBASE, Inc. Technical Support (to ensure quality). This .HOW file MAY NOT BE POSTED ELSEWHERE without the explicit permission of the author, who retains all rights to the document.
Copyright 2001, Kenneth J. Mayer. All rights reserved.
Information about dBASE, Inc. can be found at:
http://www.dbase.com
Note: The sample forms discussed in this document should be included in the .ZIP file that this HTML document came in.
Included code:
BASE.CFM
SDIFORM.CFM
PRIMARY.CFM
DATAFORM.CFM
FISH.WFM
Special thanks to Gary White for helping me out with this -- a lot of thought went into the re-design of this HOW TO document, and much of that is Gary's fault ... <g>
EoHT: CUSTFORM.HOW -- January 30, 2001 -- KJM