Example files available in custclas.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.
NOTE: This discussion is aimed at use with the visual components of dB2K, but the same concepts can be used for the non-visual components as well - including but not limited to custom queries, custom file classes, and more.
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 control 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 set of base custom controls, upon which all others are based. The same can be said for your forms (see CUSTFORM.HOW for details). Never use the stock controls. For the sake of simplicity, this document will use stock forms, but you are advised to read Custform.how to learn more about custom forms.
Once you have a feel for what can be done, hopefully you will be inspired to create classes of your own (and perhaps enhance the knowledge of everyone on the dB2K newsgroups in the process).
A Custom Class is a developer defined class, based on one of the stock classes (classes built-in to dBASE). A really good example of a Custom Class file ships with dB2K -- it is in the CLASSES (in Visual dBASE 7.x this is the CUSTOM folder) directory, and is called DATABUTTONS.CC. We will briefly look at one of the buttons defined in this class file, but most of the code we will look at will be a bit different than what's defined here.
A Custom Class definition is usually stored in an ASCII file, and uses the extension ".CC" (although this is not necessary -- dB2K knows what to look for if you use that extension -- you could use ".PRG" or some other extension just as well -- but you would not have easy access to the dB2K two-way tools abilities).
One method of using a Custom Class file is to add it to your procedure file listings:
SET PROCEDURE TO MyClass.CC ADDITIVE
The use of the "ADDITIVE" keyword is important, because without it, you will close any currently open procedure files.
You can force dB2K to use your custom class definition all the time by modifying the DB2K.INI file (usually found in \DB2K 01\BIN) -- look for the following section:
[CustomClasses] CC0=:Classes:\DATABUTTONS.CC CC1=:Classes:\REPORT.CC CC2=:Classes:\SEEKER.CC
Note:
in Visual dBASE 7.x the definitions include full paths, in dB2K, we are using
Source Aliases here, and the "Classes" alias is actually a pointer to (by default):
C:\Program Files\dBASE\dB2K 01\Classes
You can add your own custom class file(s) to this list, by simply adding lines to the file, for example:
CC3=C:\MYFILES\MYCLASS.CC
The next time you start dB2K, assuming that the directory and file given exist (if not, you'll get an error on startup), these will be automatically opened for you. (This has the side-effect of using memory for each custom class in each custom class file ...)
In addition to the methods mentioned above, you can save a custom class you are designing with the forms designer (more on this later), using the FILE menu. This will create a new .CC file if needed, or if you save to a .CC file that already exists, it will place your new custom class in that file (over-writing any previous version of the class if it exists -- note that you do have the option to save a previous version -- but you will need to give the new version a different name).
class ButtonNext( ParentObj ) of ButtonData( ParentObj ) custom with ( this ) text := "Next" onClick := { ; if ( NOT this.form.rowset.next() ) ; ; this.form.rowset.next(-1) ; ; endif ; ; super::RefreshRowState() } endwith endclass
This is copied directly out of the sample custom class file. Let's look at the parts.
The statement:
class ButtonNext( ParentObj ) of ButtonData( ParentObj ) custom
This statement begins the OOP constructor code sequence, and is very important here - it is what defines this as a custom class (the word 'custom'), the name of the custom class, and what class it is based on. Everything between the word "class" in this statement and the final statement "endclass" are the properties and methods defined in the constructor code definition of the custom class.
The first word in this statement is vital. "Class" -- this tells dBASE that we are defining a class. For every "Class" statement there must be an "Endclass" statement.
The part of this statement that says "ButtonNext( ParentObj )" is telling dBASE that this new class is named "ButtonNext". Because an interface object (like an entryfield, pushbutton, spinbox, or whatever) does not exist outside of a form, you must supply a parent object reference ( ParentObj ) for your custom class (a parent may be a form, or a container object -- a container is either the "container" object itself, or the notebook control). The dB2K Forms Designer defaults to using the standard "PUSHBUTTON1" style of naming classes when they are instantiated. In the case of the ButtonNext class, if you place an instance of this class on a form, dBASE will default the name to ButtonNext1.
The words "of ButtonData( parentObj) " are important, in that they tell dBASE (and us) that the new class is derived (or subclassed) from another class, called "ButtonData". The "ButtonData" class is defined earlier in the DATABUTTONS.CC file, and is derived from the Pushbutton class definition, which is built-in to dBASE itself. The technique of creating a main class and then subclassing from that for your own applications is very common, and will be used throughout this document.
(NOTE: You are not required to use the parameter name supplied here of 'parentObj' -- you could be more explict with your name ... I am just using the convention supplied by Inprise!)
The word "custom" is necessary to tell dB2K that you are creating a custom class. Even more useful, if the word "Custom" is there, your visual class will appear in the component palette on the "Custom" tab automatically. (We'll get to that later ...) For non-visual classes, the word "Custom" is not as important.
The rest of the class definition should look very much like a standard list of properties and/or methods -- just like what you might see when a form is generated by the forms designer. By assigning these values in the custom class definition, you can re-use this any time you want to use a button to to navigate to the next row of a table assocated with the form (specifically the form's rowset: form.rowset). Each of these properties will be inherited in any new "next button" you place on a form.
If you haven't already taken a look at the code generated by the forms designer in dB2K, and are familiar with the 5.x version, you will note that there's a difference. dB2K uses the "with" statement to shorten your object references when defining the standard properties of an object:
with( this ) // "this" refers to the current component text = "Next" endwith
The above looks like more typing, unless you have a lot of properties you wish to set. If you do, this becomes a lot shorter, than constantly typing:
this.text = "Next" this.someotherproperty = "something else"
Remember the with/endwith construct -- it can save you a lot of work. (For more information you can look it up in the online help ...)
The best part of all this is that if you wanted to change the bitmap or the behavior of the button for all of your forms that use it, you could change it in the custom class definition. Any forms that use this button will automatically get the new bitmap or behavior.
To ensure that the buttons custom class is available, type in the command window:
set procedure to :classes:databuttons.cc additive
(Note that you really shouldn't need to do the above statement unless you have modified your dB2K.INI file, or issued a "close all" or "close procedure" statement at the command window ...)
Next, to create the form type:
create form testclas
If you are asked by dB2K about using the Wizard, select the option to NOT use it.
When the forms designer opens, you should get, among other things, a Component Palette. On the component palette are several pages denoted by tabs ("Standard", "Data Access", "Data Buttons", "Custom" and "ActiveX"). Click on the "Data Buttons" tab, and you should see a set of controls, including a bunch of buttons. Hold the mouse over each and you will see the speedtip that shows the name of the control. Find the one that says "ButtonNext", and click on it and drag it to the surface.
Move the button around a bit. Notice that you can now inspect the button by giving it focus (click once on it). Using the Inspector you can see properties, events and methods. You can change button images with the inspector here, as well as the code (events) associated with the button ...
To see this button actually do anything, you will need to place a table and some fields on the surface. For our purposes, we'll assume the "Fish" table in the SAMPLES directory. Click on the navigator, drag the table icon for "FISH" to the surface of the form -- this will give you an icon that shows "SQL" on it (it's a reference to the query object -- this gets covered in more detail in Alan Katz' OODML.HOW). Go to the field palette and drag the "name" and "ole graphic" fields to the surface of the form.
To test the actual button, and see it in action, we need to run the form. Since you have the form up in design mode, look at the Visual dBASE speedbar. There is a button with a lightning bolt on it. This is the "Run" button. Click once on this button and wait a moment while the Forms Designer saves your form to a .WFM file and then starts the form.
When the form runs, click on the button -- this says "Next". It should navigate you through the table one row at a time, each time you click on it, until you reach the end of the rowset. (Try it)
Now that we have done some basic testing of how to use a predefined button, let's start creating our own classes. If you need a bunch of pre-defined custom buttons, you can use the databuttons.cc that ships with dB2K, you can get the dBASE Users' Function Library Project (information below) which ships with a lot of them ("CUSTBUTT.CC" is included in this library), and other developers have created their own libraries.
For example, you may want to always have all your entryfields have the colorHighlight set to white text on a blue background, you may want the selectAll property set to false, you may want various other properties set. This would be set in a base entryfield class, and then you might subclass that entryfield for specific field types, like phone numbers or names.
The simplest method of creating a visual custom control is to bring up a form, and design the control on the form. (The problem is that it's not as easy to continue editing these visually after that -- there are requests in to dBASE, Inc. by the developer community to allow a "custom control editor" sort of like the form designer, that can be used specifically to edit our visual custom controls, but this is not currently available in the product. One suggestion is that after you have designed the custom control(s), you can save the form you use to design them -- this will allow you to change the custom control visually.)
First, let's create a new form just for creating custom controls (do not confuse this form with the one we are using to test the custom controls):
create form newclass
Bring the Object Palette to the front, and click on the entryfield object (on the "Standard" page). Drag this to the surface of the designer. Move it to the top of the form. (You might want to consider placing a text object next to this class that tells you which object it is, in case you need to modify it later.)
Using the inspector, set the following properties:
Name: BaseEntryfield colorHighlight: White/Blue selectAll: false value: CustomEntryfield onGotFocus: {; this.keyboard( "{Home}" )}
This last (onGotFocus) is an event, and in the inspector will appear there. We are entering a codeblock that is executed when the entryfield gains focus. It simply ensures that the cursor goes to the beginning of the text. It is a personal preference, and you may not wish to use it ...
The idea is that we will save this (in a moment) and all of our custom entryFields will then be based on this specific control, so we only want to change properties and events that we want to have affect ALL our subclassed entryfields.
To save this out, in the form designer make sure that this entryfield is highlighted, and go to the file menu. Select "Save as Custom ...", and enter the a filename for this to be saved to ("mycustom" works). You should make sure that the radiobutton "Selected Controls" is selected. You may want to uncheck the "Place in Component Palette" checkbox, otherwise everytime you load dB2K, the components in this file will always be loaded into the designer. This is really a matter of preference, some developers prefer this, I don't.
Let's look at the code that was streamed out by the designer. To do that, save the form, and then we'll open the new custom control file that was created. Bring your custom class (or control) file into the editor by typing:
modify command mycustom.cc
In the editor, you should see:
class BASEENTRYFIELD(parentObj, name) of ENTRYFIELD(parentObj, name) custom with (this) onGotFocus = {; this.keyboard( "{Home}" )} height = 1 left = 1.2857 top = 0.7273 width = 10.7143 metric = 0 // Chars colorHighLight = "White/Blue" value = "BaseEntryfield" selectAll = false endwith endclass
For our purposes, the "name" parameter is not vital, and you can delete it (as a matter of fact, it can sometimes cause problems and should be deleted). Note that the height, left, top and width properties were streamed out, even though we didn't do anything with them.
You can delete the left and top properties as these will change for each instance of an entryfield that we use on a form. It's ok to delete the height and width, but not a big deal. The "metric" property gets streamed out, and it's probably a good idea to leave it. This is useful because it tells the form designer what the height and other properties were designed in (there are various metrics, including pixels ...), so that it can convert them if the form this control is being placed on is using a different metric.
Notice that the other properties that were modified in the inspector have been streamed out. This is the important thing.
One thing that is quite useful in dB2K that did not exist in earlier versions is the assignment operator ":=" -- the colin placed in front of the equal sign tells dBASE to only assign a value if the property exists. This is very handy, because it's easy to typo a property name, if we just use the standard "=" for this, we might assume we had it correct -- dB2K will let us go along with that and instead it will create a new property by that name.
The reason I discuss this, is that I really recommend that you change the "=" to ":=", so that if a typo does occur, dB2K will catch it for you.
Note, however, that using ":=" inside the with/endwith and attempting to create a custom property (either intentionally or otherwise) will not generate an error, but it may create the custom property.
The value property is set to some arbitrary value so that when you place this entryfield onto a form you know that it's not the standard entryfield (which says "ENTRYFIELD1" in the value ...). When you actually datalink this to a field in the table, the value property will change the value of the actual field. Setting the value property is only for design purposes ...
So, your code should look something like the following (I line up the properties for readability -- it's a style thing ...):
class BASEENTRYFIELD(parentObj) of ENTRYFIELD(parentObj) custom with (this) onGotFocus := {; this.keyboard( "{Home}" )} height := 1 width := 10.7143 metric := 0 // Chars colorHighLight := "White/Blue" value := "BaseEntryfield" selectAll := false endwith endclass
From here, we can now create "subclasses" of entryfields. You can do these in the form designer, or you can simply do them in the editor (which is my personal preference at this point).
One example would be to create a custom entryfield that was "disabled" -- meaning that it was used on your form to display information only. One use for this might be on a multiple page form -- you may only want the user to change the data on one page, but display more data for that record on multiple pages (or child records from a different table associated with a parent record, which is what I use this for).
To do it, you would subclass your baseEntryfield, and change the properties that need changing:
class DISABLEDENTRYFIELD(parentObj) of BASEENTRYFIELD(parentObj) custom with (this) colorNormal := "WHITE/MAROON" when := {; return false } mousePointer := 12 // No value := "DisabledEntryfield" endwith endclass
The definition shown above gives a subclassed field that is derived from the baseEntryfield class -- it inherits any properties that are set in the baseEntryfield class, but also includes any changes given in this "subclass". If we were to use the obvious method of disabling an entryfield by setting the enabled property to false, the colors would not be what we wanted, and the mousePointer would not change (since the field is disabled). To get around this, we use the when event set to "return false" -- this does the same as setting the enabled property to false but the colors do not get changed by Windows (this is default behavior and not really changeable) and the mousePointer property will change. The mousePointer is set to the universal "no" symbol, which is a nice visual to the user when the mouse is over the entryfield that tells them this is a non-editable field.
These are pretty basic entryfields. What if you wanted to do something fancy with them? For example, when the user tabs off the entryfield, you might want to do some sort of calculations, or something of that nature? You can write the code yourself. The big thing to remember is that the whole purpose of custom controls is that they are "reusable". If you are writing a one-time-only routine, it shouldn't be a custom control, although it could be a subclassed custom control.
To use these two controls we have created on a form, save your custom control file. In the command window, type:
set procedure to mycustom.cc additive
Bring up your test form ("TESTCLAS.WFM") from before into the designer, and let's get busy ...
The custom tab of the component pallette should now have two new entryfields, which are the ones that are in your file "MyCustom.CC". Place the cursor over the entryfields until you find "BASEENTRYFIELD" -- click on it and drag it to the surface of the form.
Notice that it says "BaseEntryField" (you may see only part of the text, but it's all there). We want to datalink this to a field in the fish table. So, to do that, make sure you have this entryfield highlighted, and go to the inspector. Click on the "datalink" property, and click the "Tool" button (looks like a wrench). Select the "Species" field and click "Ok". Note that this should now display the information contained in the species field. You will probably want to widen the field a bit.
Next, let's use a disabledentryfield object by doing the same as above, but for that entryfield in the component palette. For the datalink, let's use the "Name" field.
Save and run the form.
Note that the first entryfield based on our "baseEntryField" should, when focus is placed on it, change colors. Note that we cannot set focus on the field that is datalinked to the disabledEntryfield control, and if the mouse is over it, we see the universal "no" icon.
At this point you have some basics, and can go out and create your own controls.
Before you do, you may want to see some of what's out there. For example, with this "how to" document, you should have gotten a copy of the file FORMCNTL.CC -- this file is a part of the dUFLP library (found at the author's website). This library is freeware code, and you may want to examine it. There are custom entryfields that handle dates (using some other routines that are not included here) -- when you leave the entryfield, they can check to see if the date entered is a holiday or non-working day and change it to a standard work day. The possibilities are virtually endless for this sort of thing. The complete dUFLP library has a wide variety of custom code that you can use or adapt for your own applications. The idea is "Why re-invent the wheel?" ... If someone else has already written code and is willing to share it, feel free to use it!
In earlier versions of Visual dBASE (5.x) there was one container -- the form. However, in Visual dBASE 7.0 and later releases, there are two more (at least visual ones -- we're not getting into the database classes here, nor the form designer) -- the Container object and the Notebook object.
The container object is what I want to discuss, but most of what is mentioned here could be said about the Notebook control.
What if you are a business developer and you always build applications that use First Name, Last Name, Address1, Address2, City, State, Zip, Phone, and Email fields in every single application you build, and further you always use the exact same layout?
Isn't it frustrating to have to put each field and corresponding text control onto the form, and get that layout just right each time? Isn't there a better way?
Sure. In dB2K, do it once on a container, and then save the container object as a custom control. Once you have done that, you can put that control back on to any form you want, and all the layout is done, the only thing is to datalink the fields (and if you're really ambitious you could write some code that did that for you too!). Of course, if you use the exact same field names each time, that problem could be solved by setting the datalinks to "form.rowset.fields["fieldname"].value" and you would be set.
Here's an example, and you should follow along. Create a table with the following structure (rather than using an already existing one, because I can never guarantee that it has the same layout):
Fieldname Type Width First Name Character 20 Last Name Character 20 Address 1 Character 30 Address 2 Character 30 City Character 30 State Character 2 Zip Character 10 Phone Character 14 Email Character 30
Using techniques already shown, create a custom text object and save it to the same file we're already using (MYCUSTOM.CC). This text control might look like:
class CUSTOMTEXT(parentObj) of TEXT(parentObj) custom with (this) height := 1 width := 12 metric := 0 // Chars alignVertical := 1 // Middle alignHorizontal := 2 // Right text := "CustomText" transparent := true endwith endclass
This text will be used as "right aligned" so that it always lines up on the right. You could add a fontName and fontSize property if you wanted, but for now we'll use the defaults (Arial, 10 point).
Add a few rows to the table for testing purposes (you could use the "generate" command, but that gives random values which can be a bit odd, to say the least).
In the command window, type:
set procedure to mycustom.cc additive
Create a new form, and drag your test table from the navigator to the surface.
Now, before you do anything else, drag a container from the "Standard" tab and place it on the surface of the form. You may want to size it a bit, and you will end up re-sizing it probably as you go -- that's ok. Set the properties:
Name: MyBusinessLayout Transparent: true
The name is so that when it gets saved to your custom control file you will know what you called it. The 'transparent' property set to 'true' is so that if you have any form colors, the container will let them show through (you can also set a background image on a form and if the container is not transparent it will not be visible).
Now, go to the Custom tab of the component palette and drag your new text control to the surface. Change the text to read "Name:". Drag your "BASEENTRYFIELD" over to the surface. Click the "datalink" property and set it to the "First Name" field. You should do this again for the last name field. Repeat for a layout similar to:
Name: [first name] [last name] Address: [address 1 field] [address 2 field] [City field] [st] [zipcode] Phone: [Phone field] EMail: [Email field]
Align the text and entryfields exactly as you need them lined up. (This is probably something you've done a lot of times -- but you may never have to do it again!)
Resize the container as much as you need to so that everything appears, but the container is not a lot larger. You may want to remove the border (set the border property to false), or change it ...
Go to each object on the container (click on it) and change the name to something meaningful (NameText, FirstNameEntryfield, etc.). This is so that you have a meaningful name to refer to each object on the container.
Next, you should consider if there are other properties you may wish to set for each of these controls. For example, the state field is already only two characters, but you may wish to force it to upper case, and the zipcode has a specific format as does a phone number (hint, use the picture clause).
Finally, click on the container surface, and go to the file menu. Select "Save as Custom ...", and make sure that the items in the dialog box are correct (save it to your MYCUSTOM.CC file).
If all worked properly, you should be able to see, in your MYCUSTOM.CC file, the following (or something very close to the following):
class MYBUSINESSLAYOUT(parentObj, name) of CONTAINER(parentObj, name) custom with (this) transparent = true left = 0.2857 top = 2.9091 width = 45.2857 height = 7.4091 metric = 0 // Chars endwith this.NAMETEXT = new CUSTOMTEXT(this) with (this.NAMETEXT) height = 1 left = 0.4286 top = 0.1364 width = 8.1429 text = "Name:" pageno = 0 endwith this.FIRSTNAMEENTRYFIELD = new BASEENTRYFIELD(this) with (this.FIRSTNAMEENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["First Name"] height = 1 left = 9.7143 top = 0.2273 width = 15.5714 pageno = 0 endwith this.LASTNAMEENTRYFIELD = new BASEENTRYFIELD(this) with (this.LASTNAMEENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["Last Name"] height = 1 left = 26.5714 top = 0.2273 width = 15.8571 pageno = 0 endwith this.ADDRESSTEXT = new CUSTOMTEXT(this) with (this.ADDRESSTEXT) height = 1 left = 0.4286 top = 1.2727 width = 8.1429 text = "Address:" pageno = 0 endwith this.ADDRESS1ENTRYFIELD = new BASEENTRYFIELD(this) with (this.ADDRESS1ENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["Address 1"] height = 1 left = 9.7143 top = 1.4091 width = 20.5714 pageno = 0 endwith this.ADDRESS2ENTRYFIELD = new BASEENTRYFIELD(this) with (this.ADDRESS2ENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["Address 2"] height = 1 left = 9.7143 top = 2.4545 width = 20.5714 pageno = 0 endwith this.CITYENTRYFIELD = new BASEENTRYFIELD(this) with (this.CITYENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["City"] height = 1 left = 9.7143 top = 3.5909 width = 17.7143 pageno = 0 endwith this.STATEENTRYFIELD = new BASEENTRYFIELD(this) with (this.STATEENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["State"] height = 1 left = 28.1429 top = 3.5455 width = 4.2857 picture = "!!" pageno = 0 endwith this.ZIPCODEENTRYFIELD = new BASEENTRYFIELD(this) with (this.ZIPCODEENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["Zip"] height = 1 left = 33.2857 top = 3.5455 width = 10.7143 picture = "99999-9999" pageno = 0 endwith this.PHONETEXT = new CUSTOMTEXT(this) with (this.PHONETEXT) height = 1 left = 0.4286 top = 4.6364 width = 8.1429 text = "Phone:" pageno = 0 endwith this.PHONEENTRYFIELD = new BASEENTRYFIELD(this) with (this.PHONEENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["Phone"] height = 1 left = 9.7143 top = 4.7727 width = 13.7143 picture = "(999) 999-9999" pageno = 0 endwith this.EMAILTEXT = new CUSTOMTEXT(this) with (this.EMAILTEXT) height = 1 left = 0.4286 top = 5.8636 width = 8.1429 text = "EMail:" pageno = 0 endwith this.EMAILENTRYFIELD = new BASEENTRYFIELD(this) with (this.EMAILENTRYFIELD) dataLink = parent.parent.customers1.rowset.fields["EMail"] height = 1 left = 9.7143 top = 5.9091 width = 28.8571 pageno = 0 endwith endclass
My recommendation is to remove the datalink for each entryfield, or if you wish to leave it because you ALWAYS use the same field names, I suggest you change:
parent.parent.customers1.rowset.fields["FIELDNAME"]
to read:
form.rowset.fields["FIELDNAME"]
However, that is really up to you.
Note that most of the properties defined in the original objects did not get streamed out (except for the height property, which due to a bug in the form designer's streaming engine always gets streamed out ... in this case it's not a big deal, but it might be if you changed the font in the original class and wanted to change the height for all subclassed objects).
The best thing is that the next time you need to do this layout, all you have to do is set the custom control file to be active (set procedure to mycustom.cc), create the form, make sure there is a table on it that has the right layout, and drag the whole container object to the form. Even better, you can move the container object around, and all the controls ON it will move in relative position to each other.
The primary reason you should consider is customization -- it's very handy to be able to make a single change in one location in your code rather than in every single form.
Example: Your customer decides that he wants all text controls on every form in your application to use a different font than the default "Arial", and he wants the fontsize to be 12, because his eyes are going. If you have a single text control that you have used instances of on all your forms, the changes can be made in your custom control file. If you do not do this, then you will have to go through each form either by code, or in the designer, and make the changes.
The same can be said for other properties -- colors, mouse pointers, and more. In addition, if you decide to attach code to events for some of your controls, you can change the behavior in one place, rather than trying to do it in every single instance of a specific control.
A secondary reason to use custom controls for every control you use on a form, which is really tied into the primary reason is that you make your application's appearance more uniform. If you always use instances of the same text control, the same entryfields, the same notebook controls, and so on through out the application, you have consistency that you won't necessarily get (by design or by accident) if you do not use custom controls.
In order to get the consistency, you may want to go another step in making your controls all use the same fonts and colors. An example of this might be to put a set of constants at the beginning of your custom control file, and set values there. The following is extracted from the beginning of the formcntl.cc file in the dBASE Users' Function Library Project:
// Font info: #DEFINE CONTROLFONTNAME "Arial" // default #DEFINE CONTROLFONTSIZE 10 // default // Colors: #DEFINE HIGHLIGHT "White/Blue" #DEFINE NORMAL "Black/White" #DEFINE NORMALNOBACK "Black" #DEFINE DISABLED "White/Maroon" #DEFINE TITLECOLOR "BLUE" // Grid specific colors: #DEFINE GRIDBACK "WHITE" #DEFINE GRIDHEADER "BLACK/BTNFACE" // TabBox colors: #DEFINE TABBOXBACK "BLUE" // background #DEFINE TABBOXNORMAL "BLUE/SILVER" // current tab // TreeView colors: #DEFINE TREEVIEWCOLORS "WINDOWTEXT/WINDOW" // text/background // Slider colors: #DEFINE SLIDERCOLORS "BLACK/SILVER" // Standard border for entry objects: #DEFINE ENTRYBORDERSTYLE 7 // used in kmCustEntryField, kmCustEditor, // kmCustImage, kmCustGrid, kmCustCombobox // kmCustTreeView #DEFINE CONTAINERBORDER 10 // kmCustRectangle, kmCustContainer #DEFINE CHECKBORDER 3 // kmCustCheckBox, kmCustRectangle #DEFINE GADGETBORDER 0 // kmCustSlider, kmCustProgress
By changing any of these constants, you change the values used in each custom control that uses those constants. In the example above, if your users wanted you to use "Times New Roman" at size 12, you would change the "CONTROLFONTNAME" constant to "Times New Roman" and the CONTROLFONTSIZE constant to 12.
The next time you load a form the change will take effect, and every control is subclassed from the custom classes in this file will inherit those changes. This is where the true power of custom controls comes from!
If you are interested in some pre-designed custom controls (of a wide variety) that are free for your own use, as long as you credit the authors of the code, the dBASE Users' Function Library Project can be found in the Knowledgebase.
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
EoHT: CUSTCLAS.HOW -- January 30, 2001 -- KJM