Developing a Database Application:
The dBASE™ PLUS Tutorial

Ken Mayer, based on work by Michael Nuwer and Ken Mayer


Phase IV: Creating Custom Components


  Back to the tutorial menu     Back to previous part of tutorial: Creating the Data Modules   Proceed to the next part of the tutorial: Creating the Forms 

Goals and Objectives

The goals of phase IV of this tutorial are:

Additional Reading

What is a Class, What is a Custom Class, and What is a Control?

Let's begin with a control. Controls are the objects that appear on the Graphical User Interface. They sit on top the form or report and control functionality. There are several different kinds of controls. Command buttons, entryfields, listboxes and comboboxes are familiar to all Windows users. Each type of control has a different visual appearance and a different purpose. Moreover each control has a set of properties that define its characteristics, a set of events that it can respond to, and a set of methods that define its actions.

In the previous Phase of this tutorial, we introduced the idea of creating and using a custom class. A Class is a definition of an object -- it stores within its definition all of the properties, events and methods associated with the object (this is, by the way, 'encapsulation'). A Custom Class is a developer defined class, based on one of the classes built-in to dBASE™ PLUS. A Custom Class definition is usually stored in an ASCII file, and uses the extension ".CC" (although this is not necessary, dBASE™ PLUS knows what to look for if you use that extension.).

The primary reason you should use Custom Classes is that you will be able to make a single change in one location in your code which is passed along to all the objects based on this class. The next time you load a form the change will take effect, and every object that is subclassed from the custom classes in this file will inherit those changes. This is where the true power of custom classes comes from!

For 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 custom text control that you have used 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 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 throughout the application, you have consistency that you won't necessarily get (by design or by accident) if you do not use custom controls.

Let's Create Our Own Custom Classes

The tutorial application is going to use many controls on its forms and reports. This means we need to create our own set of Custom Classes. When it comes time to develop other applications you will not need to create your custom classes from scratch because you will already have a file to start with. You will only need to add the file we are about to create to the new project. But this is a tutorial and one important thing to learn is how to create your custom controls from scratch.

The tutorial application will require 15 custom controls. Most of these controls will be derived from built-in controls and include a custom title, label, entryfield, combobox, spinbox, image, grid, container, radiobutton, pushbutton and tabbox. We will also need to create a custom tool bar which contains a set of buttons for data entry purposes.

It should be noted that as we did with Data Modules, we are going to use the "custom" folder to store some of our work, as a matter of fact, for this part of the tutorial, we will be using it for everything.

The simplest method of creating a visual custom control is to bring up a new form, and design the control on the form. After you have designed the custom controls, you can save the form -- this will allow you to change the custom control visually.

Now we will create a new form for designing the custom controls. Go to the navigator, and select the "Forms" tab. Double-click the icon that says "[New Form]". You will be placed in the "form designer". If you are asked if you want to use the Wizard or the Designer select Designer. As noted elsewhere, for the Tutorial, always use the Designer unless told otherwise.

Along with the new form surface, you should see the Component Palette and the Inspector, probably the Field Palette, but we can ignore that for now.


The palettes or windows associated with the designers are "docked" in dBASE™ PLUS 11 by default. I tend to tear them away from the docked positions and resize/position them as needed. This can be done by clicking and dragging the titlebar of any individual component and dragging it to the left or right to pull it away from the framewindow of dBASE. Then you can treat them like any other window ... Of course, if you prefer them docked to the side of the screen, that is completely your prerogative (see below "Preference Issue").

All of our custom classes will be created on this form. The first control will be an entryfield. The Entryfield control is the most basic of the data entry controls. Nearly all forms that deal with data entry use entryfields for a lot of the data.

Before we start building the components, we are going to set the form's metric property to 6 - Pixels. This is because Windows, and most Windows applications, work in Pixels. The default metric in dBASE forms is "Character" which is not as useful. It will be important for at least one control that we are creating here, as the size of the control needs to be set correctly when it is created. If we set it to "Character" and then use a form with the metric set to pixels, everything will get very weird. In the Inspector, find the metric property and set it to "6 - Pixels".

Go to the Component Palette and select the "Standard" tab. The controls on this page are the dBASE™ PLUS built-in controls, and this is the only time we will use these controls. Click the EntryField control and drag it onto the form.

We have found that we always make specific settings for our entryfields. These settings include, for example, the color when the field is highlighted. You should set these "base" or personalized properties now. While the EntryField is selected, switch to the Inspector.

On the properties tab, find name. The name property is very handy when working with any of these controls. When you place any control directly on the form from the component palette, it is given the useful name of "classnameN" where "classname" is the name of the class (i.e., Entryfield), and "N" is a number (i.e., 1). So the first entryfield you place onto a form from the component palette would be named: Entryfield1. This is not the most useful name you could have.

Therefore, it is a good idea to use descriptive names for your controls, because otherwise, particularly on a complex form, it will get very difficult to know which object is which.

Click on the "name" row in the inspector, and type MYENTRYFIELD in the space to the right. Be sure to press the "Enter" key (or use the up or down arrow key), otherwise the change will not take effect.

Next find the selectAll property. Set this to false. With this setting the full text will not be selected when a user tabs into the entryfield.

The next property to modify is value. Set this to "MyEntryField". 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.

We also want to set the colorHighLight. In this case, click on the colorHighLight property in the inspector, then click the wrench at the far right. A color selector dialog form will appear. However, the options are fairly limited here. Click "Cancel". We're going to go with a less vivid color combination than in earlier versions of the tutorial, and use "WindowText/0x80ffff". If you just put this into the Inspector and press the <Enter> key, the value will be stored. When the control gets focus it will have a light yellow background with the standard black text (unless you are using Windows settings with very different colors).

Finally, select the "Events" tab on the Inspector and find onGotFocus. Click the right most icon, the one with the "T", and select "Codeblock" from the dropdown list. Type this.keyboard( "{Home}" ) in the field (overtyping the default text). This codeblock will execute when the entryfield gains focus. It simply ensures that the cursor goes to the beginning of the text.

At this point let's save the form. Press <Ctrl>+S, select the "custom" folder, and enter "CustomControls.wfm" as the file name.

Before we create additional controls on our CustomControls form, let's convert this form object into a custom class. The idea is that we will create various objects and change their properties and events in the CustomControls form. After the controls are customized for our application, we will export the control to a CC file. We will export the entryfield control now, then return to create the other controls that will be used in the application.

Make sure that the entryfield is highlighted on the form designer, then go to the file menu and select "Save as Custom ...". A dialog form will come up. The class name should already be filled in as "MYENTRYFIELD". This is the name we assigned to the control. In the "Custom Component file name" field, enter (you may select the folder using the "pencil" button) "C:\dBASETutorial\custom\MyControls.cc". You should make sure that the "Selected components" radiobutton is selected and that the "Place in Component Palette" checkbox is selected. Click "OK."


You will see that a new file called MyControls.cc has been created. If you open that file in the Source Editor, you will see the code for the new entryfield class.

Create the Remaining Controls

As we continue adding and customizing controls with our custom control form, it will look similar to the following image. You can use this image as a guide for creating the additional controls. I added some callouts to show what some of the controls are, as they aren't real obvious.


Text
The text control is a highly flexible, very versatile control. It will evaluate HTML tags, rotate text, maybe even sing and dance. It can be used on forms to display text and, because it can evaluate codeblocks, it can be used on reports to display field data. The price for all this power is that there is a significant amount of baggage that gets dragged along with a text object, whether you choose to use it or not. This, in most cases, will not be significant. However, large, complex forms or reports that contain a lot of text objects will, in all likelihood, see performance degradation.

For our application a text control will be used to display a title on our forms; to display a message on the About form; and to display field information on the reports. Here we will create a basic text control that will be used on the About form.

Save the updated form. Then export this component to the MyControls.cc file.

TextLabel
The textLabel control is one of the most often used controls in dBASE™ PLUS. It is usually placed next to some other control to give the user a visual cue what the field is or to give some other definition to what's happening on the form.

The textLabel is a limited version of the text control which makes it a leaner text control that uses less resources. Unless you need the extra abilities of the text control, you should use a textLabel instead.

To create a custom TextLabel:

Save the updated form (File|Save or Ctrl+S). Then export this component to the MyControls.cc file (be sure the control is selected).

PushButton
The pushbutton is a control used to give the user a method of interacting with the form. Pushbuttons are often used to navigate in a rowset, place the user in edit or append modes, to save or cancel changes to a rowset, delete rows, exit forms, and much much more.

In dBASE™ PLUS a pushbutton can display text or an image or both. The disabledBitmap, downBitmap, focusBitmap, and upBitmap properties are used to set images for each of the button's states. You will find images in RESOURCE.DLL or you can use other images for your buttons -- in dBASE™ PLUS 11 the graphics abilities have been enhanced and you can use PNG, GIF, BMP, and a variety of other image types for your buttons.

The text property is what it sounds like -- you can display text on a pushbutton. You can display text and an image on a pushbutton. It all depends on how you want your form to look. Note that if you use an ampersand (&) in the text, you will be creating an accelerator key for the button.

The onClick event is the one used the most with pushbuttons, which fires when the user clicks it with the mouse.

Pushbuttons are used everywhere in forms, and we will be using them in our application. They are, for the most part pretty straight-forward.

Our custom pushbutton will not need many custom properties. Use "MyPushButton: for the name and "Button" for the text (both without the quotes). And just for fun set the mousePointer to 13-Hand (or not, it's kind of silly, really).

SpinBox
The spinbox control is really just an entryfield with a couple of pushbuttons added to the right. It is used for numeric or date values, to increment or decrement the values by using the buttons (which show up/down arrowheads).

The spinbox has all the characteristics of the entryfield, and the following as well:
The rangeMax, rangeMin, rangeRequired properties are used to set a top/lower value and to enforce that value -- i.e., if the rangeRequired property is set to "true" the user cannot move past the rangeMin or rangeMax values or enter a value outside that range.

In addition, there is the spinOnly property -- this can be used to enforce the use of the buttons, and ONLY the buttons by the user -- they cannot enter a value by typing it if this is set to true.

The step property is used to set the value the pushbuttons increment/decrement the value of the spinbox. The default for this is 1, but it can be changed to something else.

Place a standard spinBox onto the custom controls form. The name should be changed to MYSPINBOX. And the following additional properties should be set:

Save the form, and then save the custom spinbox with "Save as Custom" ...

ComboBox
The Combobox may seem like it's related to the listbox, but it's really related more to the entryfield. It is an entryfield object with a pushbutton used to "drop down" a list of selections. The source of information for the items listed in the drop down list is the dataSource property.

The most common dataSource options are arrays and fields.

The combobox control can be quite useful for a lot of different situations, and can be automatically used with a lookupSQL property. Recall that we defined a few of these when we created the datamodules. When we build our application's forms, we will use a combobox to display the items list of the lookupSQL fields.

The style property is used to define which style combobox to use. The default is "1 - DropDown", which means that the user must click the button to cause the list to drop. The style property can be set to "0 - Simple" which means that the drop down list appears automatically. You can set it to "2 - DropDownList" which means that a) the user must click the button to see the list and b) the user cannot enter a value into the entryfield portion of the combobox. In other words, the user is restricted to using items in the list. This option is useful for controlling a predefined list of valid entries that can be put into the field.

The dataSource is not dynamic -- once the dataSource is loaded into memory, it does not get updated -- if changes are made to the table you are using the field from, it is not automatically refreshed in the combobox. You can, however reassert the datasource:

            form.combobox1.dataSource += ""

The code above (using your own combobox name appropriately) will cause the datasource to be re-evaluated (the actual dataSource reference is contained in the property, adding a null character string will force the reassertion).

Place a standard combobox onto the custom controls forms.

Save the form, and then save the custom class.

Radiobutton
The radiobutton control often confuses people -- this is because the value property returns a logical (true/false) value, but what is stored in the field of the table is the TEXT property of this control.

The purpose of radiobutton controls is to present the user with a limited set of choices for a character field. A really simple example would be setting ice cream flavors, and you only had three: Chocolate, Vanilla or Strawberry. By setting three radiobuttons onto a form with the text reading "Chocolate" for the first, "Vanilla" for the second, and "Strawberry" for the third, and datalinking all three of these radiobuttons to the same field, the user will only be allowed one of those three choices.

For radiobuttons to work together you must use the group property for the first radiobutton -- set it to true (if not done for you by the designer). And you must make sure no other controls appear in the "Z-Order" between the radiobuttons. (The "Z-Order" is the sequence the events are defined in the form's constructor code. You can modify this in the designer through the layout menu, and in the source code directly by cutting and pasting code appropriately.) If you follow this tip you should not have problems with your radiobutton controls behaving properly.

Place a standard radiobutton onto the custom controls form.

Save the form, and then save the custom component.

Container
The container object is a very useful class and you should be using it in your applications. This particular control is great for several purposes -- one is to group a set of controls together -- by placing them onto a container, those controls can be moved around the form all in the same exact relationship to each other, and you do not have to worry about it. Another purpose is to create your own set of custom controls -- perhaps a set of controls that you will re-use on several forms. In fact we will do this when we create a custom toolbar for our application.

Drag a container from the Standard page of the component pallet.

Save the form, and then save the custom component.

Grid
The grid object is one of the most amazing controls included with dBASE™ PLUS. A grid can be used to display data in a tabular format and is a very powerful tool for working with parent-child data relationships. The grids that we will use in this tutorial are intended for displaying or reading data. We will not let the user edit data in a grid. Therefore our custom grid control will use properties that make it a read-only control.

Place a standard grid onto our custom controls form. Change the name property to "MyGrid" and modify the following properties:

Our custom grid does not have a datalink, and therefore we cannot set any of the column properties at this time.

Save the form, and then save the custom component.

TabBox
The Tabbox control is usually thought of as something used specifically to change pages of a multi-page form, but it is actually quite flexible. The default placement of a tabbox on a form is to anchor it to the bottom -- you can, however, anchor a tabbox elsewhere, and indeed, set it with no anchor at all, and place it where you will on the form.

The curSel property is what is used to determine which tab of the tabbox has been clicked.

The onSelChange event is fired every time the user clicks on a tab and changes the active tab to a new one. This can be used in a multipage form to change the page of the form:

            form.pageNo := this.curSel

In the tutorial application, each data form will use a tab box that lets the user switch between a page containing details about the individual record and a page containing a grid that can be used to find a record. We will start by setting the following properties of the custom tab box:

To add the dataSource, you must use the DateSource Property Builder. To use this tool, click the wrench associated with the dataSource property. When the DataSource Property Builder pops up, you will notice that the "Type" of the data source is an array and the array elements are the "Data Source".

We will use this dialog to change the array elements of the Data source. Click the wrench button on this dialog form. The Build Array dialog form appears. First click the delete button to remove "Tabbox1" from the array. Then in the "String" entryfield type "Individual Record" and click Add. Next type "Find Record" and click Add again. We need only these two tabs for our tabbox, so click OK to close the Build Array dialog and in the DataSource Property Builder click OK to close this form.

In addition we need to add code for two event handlers. First, add an onOpen event handler for the tabbox. Enter the following code:

            this.curSel := 1
            form.pageNo := 1

Next add an onSelChange event handler. This fires each time the tabbox is changed. We will use this event to activate the necessary page of the form.

             form.pageNo := this.curSel

Save the form, and then save the custom component.

Image
Image controls are used to display images on forms. These are not images that can be modified by your users.

The dataSource property is used to determine where the image "comes from". You can get the image from a resource file (a .DLL), from a file, or from a field in a table -- the confusing factor with the field is that the inspector shows "BINARY" as the option -- it refers to a binary FIELD.

The alignment property is used to determine how to display the image. If you use the default "0 - Stretch" it means that the image will be stretched to fit within the height and width of the control. This can create some very interesting effects. You can force the image to the top left of the control, to use the "aspect ratio" meaning no matter what the size, it will not be distorted, to center within the image control, or to use "True Size" (which can make for some interesting effects as well, if the image's true size is huge).

Place an image control onto the form, and set the following properties:

Save the form, and then save the custom component.

Editor
The editor control is used for large quantities of text and is most often datalinked to a memo field. One of the frustrating things with editor controls is that there's no colorHighlight property. So, we will make it act as if there were by changing the colorNormal. In addition the editor has a hand popup that allows simple formatting of the text in the control. This feature will be turned off for our application.

The editor control may ignore the rowset's autoEdit property, which can be a bit disconcerting and frustrating. Therefore we will add the following code to the editor control's key event handler, so to avoid this particular problem:

            function MYEDITOR_key(nChar, nPosition,bShift,bControl)
               /*
                  This code by Gary White is provided to
                  get around a problem with rowsets that
                  have the autoEdit property set to false,
                  and editors. The editor seems to be immune
                  to this property once you make a change
                  in it -- if you then save or abandon, you
                  can actually edit the contents of the editor
                  object ...
               */
               // this = editor
               // dataLink = field
               // parent = fieldArray
               // parent = rowset
               if type( "this.datalink.parent.parent" ) # "U"
                  r = this.datalink.parent.parent
                  if r.autoEdit == false and ;
                     ( r.state # 2 and r.state # 3 )
                     return 0
                  endif
               endif
            return

Save the form, and then save the custom component.

Two Subclassed Controls

The next two controls are going to be subclassed from controls we created above. First we will create a special version of the entryfield which will be a disabled entryfield; and second we will create a special version of the text control which will work as a title for our forms.

A Disabled EntryField
A disabled entryfield control is used to display information only. One use might be on a multiple page form, where 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).

You can create a subclassed control by using the custom controls form, however, do not begin by dragging a standard entryfield onto the form. Instead, click the custom tab on the Component Pallet and locate the "MyEntryField" control. (If this control is not on the Component Pallet, switch to the Command Window and type "set procedure to mycontrols.cc".)

Drag this control onto the form and change the name to "MyDisabledEntryField" (without quotes).

MyDisabledEntryField is derived from the MyEntryfield class which means that it inherits any properties that are set in the MyEntryfield class, but also includes any changes given in this "subclass".

One method of disabling an entryfield is by setting the enabled property to false, however, the colors would not be what we wanted, and the mousePointer would not change (since the field is disabled). To get around this, we will 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).

In the Inspector, find the Event when, and in the entry area, enter: return false and press <Enter>

In addition we should change the colorNormal to "White/Maroon" so that this entryfield looks different than the normal entryfield.

Set the mousePointer property to "12 - No" (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.

Save the form, and then save the custom component.

The Title Control
All of the forms in our application will use a text control to display the form's title. This control will be subclassed from the MyText control that we created earlier.

Drag a MyText control from the Custom tab of the Component Palette onto the custom controls form. Use the inspector to find the name property and change it to "MYTITLETEXT".

The text property will be "Title" which is merely a default. This will be overridden on each form where the control is used.

In addition set the following properties for this control:

Save the form, and then save the custom component.

Create the Toolbar Control

Everyone who has worked with dBASE for a long time has discovered that the container class is very useful and powerful.

We will use the container to create a toolbar that contains buttons for interacting with the data and for navigating the rowset. These pushbuttons will be placed within a container object and will function as a single control when placed on a form. To begin, we need to place a standard container object on our CustomControls form. The location of this container does not matter because we will soon be exporting it to our custom component file. Change the following properties of the container:

Now that we have the toolbar's container, we need to have some pushbuttons that allow us to edit rows, add rows, delete rows, save changes, and abandon changes. Sound like a tall order? Well, not that tall, but there are a variety of new things to learn.

The first thing we need to note is that by default, a form's rowset is in "edit" mode -- meaning that when a user clicks on a field and types something, they are editing the data. This could mean that a user might accidentally change something that they didn't want to.

What's worse is that if they close the form at this point, the changes made to the row will be saved. If they click on a navigation button, the changes will be saved. So, what can we do?

The solution is to set the rowset's autoEdit property to false.

Of course, we will want to allow the user to edit a row, otherwise what's the point? So we will use a button to put the rowset into edit mode. However, we'll start with a button to allow the user to add a new row to the table.

On the "Custom" page of the Component palette, you will see the custom controls that you created earlier. If you are not sure what one is, place the mouse over it, and a speedtip will appear, telling you what the object is. We want a MyPushbutton -- click on the object and drag it to the MyToolBar container.

When we created the buttons used in this toolbar, we used 24 for the "height", 4 for the "top" and 62 for the "width" properties (for most of them).

The text property of your button defaults to the name of the object, which is "MyPushbutton1" -- we are going to change the name of the button, as well as the text, because if you have a lot of pushbuttons, it gets confusing working with them if they are simply "MyPushbutton1", "MyPushbutton23", etc.

So, with the pushbutton having focus (if it doesn't, click on it), go to the inspector, and find the property "Name" -- it will be under the category "Identification". Click on the property, and on the right side, type "PBNEW" and press the <Enter> key.

Next, find the text property, which will be under the "Miscellaneous" category. Click on "text" and enter "&New" (without the quotes) and press <Enter>.

You should see the text on the pushbutton change to "New" on the form.

Change the fontSize to 8.

We need to put an image on the button, using the Inspector go to the upBitmap property, and click on the tool button. This will bring up a new dialog.

In the "Location" combobox, select "Filename", and then click on the tool button to the right. The file you are going to use is one of the images that ships with dBASE™ PLUS from a library created by a company named GlyFx. In the filename dialog, you will have to change the folder you are looking in to something like (this is on Windows 10):
C:\Users\Public\Documents\dBASE\Plus11\Media\Images\glyfx\Aero\CE\Dbase Aero\PNG\16x16
Yes, these images are "buried" pretty deeply in the folder structure. Find the image called "favourites_16.png" and select it (click on it, and then click "Open", when that dialog closes, click "OK").

In the inspector once again, select the speedTip property, and type: "Add a new record" (and press <Enter>).

Now, at this point in time, the pushbutton will not do anything when the user clicks on it, so we must add some code to the button. The code for this button will be pretty simple, but the question is, where do we assign the code?

Look at the inspector -- you should see an "Events" tab -- click on this. When you do, you should see a set of "Events" -- you can assign code to any of these events. It just so happens, with pushbuttons, that the first event listed is the one we want to use to assign code when the user clicks the button with their mouse.

Click on the onClick event. Where it says "Null" you should also see two small buttons, one shows the end of a wrench, the other shows a "T" for "Type". We want to use the wrench (or tool). Click on this, and you will see a small editor window, with the following:

            function PBNEW_onClick()
            
               return

Type the following line into this function:

             form.rowset.beginAppend()

When a user clicks this button, the form will clear -- all objects that are "datalinked" will suddenly be empty, so that the user can add new data to a new row.

If, in a running form, what you (or the user) have done at this point is to create a blank row -- it is not real, however, until the user saves the row. Until they save it, the new row is in what is called a "buffer". The buffer is important, both here and in editing mode. The user can save the row by using a "save" button (that calls the save() method of the rowset), or by navigating in the rowset (using the buttons we created earlier) ... either way, the row will be saved.

Another effect of the user clicking on the "New" button is that there is a property of the rowset called state that gets changed. We will take a look at the state property later.

Since we are going to export the entire "MyToolbar" container as a single control, we do not need to export the New button at this time.

Edit Row Button
We need to add the "Edit" button, so do the following steps:

Enter code like the following:

            function PBEDIT_onClick
               form.rowset.beginEdit()
               return

The beginEdit() method of the rowset allows the user to modify the copy of the row -- the user is not editing the actual data -- as soon as the row is saved, the copy in the buffer is written back to the actual row making the changes in the table. This is important, because there is an abandon() method (which we will see soon) that allows the user to abandon their changes to the row.

Like with the "New" button, clicking this button (or running the code) sets the rowset into a special "state" (again we'll look at the state property later). In addition, if the user changes the row in any way, we set a property of the rowset called modified to true (it defaults to false). This property can be quite useful, and we'll come back to it later.

Save Row Button
We need to add the "Save" button, so do the following steps:

Enter code like the following:

            function PBSAVE_onClick
               form.rowset.save()
               return

Abandon Changes Button
We need to add the "Cancel" button, so do the following steps:

When abandoning changes, you may want to ask the user first ... if adding a new row, abandoning would release the new row in the buffer. If editing a row, abandoning changes the copy of the row in the buffer back to what the row looked like before editing the row. A person may have done some real work to get all the data just right, and accidentally hitting the abandon button could wipe all that out.

Enter code like the following:

            function PBCANCEL_onClick
               if form.rowset.modified
                  if msgbox( "Abandon changes to this row?", "Abandon changes?", 36 ) == 6
                     form.rowset.abandon()
                  endif
               else
                  form.rowset.abandon()
               endif
            return

Delete Row Button
We need to add the "Delete" button, so do the following steps:

The problem with deleting a row in a table in dBASE™ PLUS, using the OODML (as we are doing here) is that the row is not easily recoverable. To all intents and purposes, it is completely gone. You may want to ask the user if they really want to delete the row by adding a quick dialog box that asks. If the user clicks the "Yes" pushbutton, then go ahead and delete the row.

            function PBDELETE_onClick
               if msgbox( "Delete this row?", "Delete Row?", 36 ) == 6
                           form.rowset.delete()
               endif
               return

The Navigation Buttons
The last two buttons will be used to navigate through the rowset. Pushbuttons are the standard Windows method of navigating through a table. What are the standard navigation buttons? Well, you need to be able to step through a table row by row, so you need "Next" and "Previous" buttons. Many applications also use buttons to go to the top of the table and to the end of the table (first row/last row), but the tutorial application will not include this feature.

First, we will add two MyPushButtons. Place the first one near the "Delete" button and set its "width" to 4. The name for this button will be "PBPrev". Place a second MyButton to right of this PBPrev. Its "width" is also 4 and its name will be PBNext.

The "Next" Button
Let's work with PBNext first. This may seem backward, as it is the last button on the toolbar, but ...

Now we get to the more interesting part of this. The code can be very simple, and we will do the simple version first.

Enter the code:

                form.rowset.next()

In the editor, so that your onClick event code looks like:

            function PBNEXT_onClick()
               form.rowset.next()
               return

There is, however, a problem with this function. When the row pointer is at the "end of rowset" a blank row will be displayed in the form. We don't really want our users to see this because they might think this is a valid row, and try to enter data ... this won't do at ALL!

So, how do we deal with it? How do we, rather than display the "end of rowset" display an error message for the user? We need to modify the code, obviously.

The next() method of the rowset object returns a logical value (true or false) -- if next() is successful, the method returns the value "true", if it wasn't we return the value "false". We can check for that in our code ... if we get a "false" value, we don't want to be at the end of rowset (endOfSet -- we'll look at this later), so we want to tell the user that they cannot continue, and display a message.

What we want to do is to try to navigate using the next() method of the rowset, and if the method returns "false", it means we hit the "end of rowset", so we need to back up one row (we can do this using the next() method, and use the optional parameter for the number of rows to navigate, in this case we will use "-1" -- the negative sign means to go backward). We will then display a message to the user that says we can't do this. The code is actually fairly simple:

            if ( not form.rowset.next() )
               form.rowset.next( -1 )
               msgbox( "At end of rowset", "Can't Navigate", 64 )
            endif

For details on the use of the msgbox() function, see online help -- it gives a lot of detail.

The "Previous" Button
Now we will work with the PBPrev button.

Enter code like the following:

            function PBPrev_onClick()
               if ( not form.rowset.next(-1) )
                  form.rowset.next()
                  msgbox( "At beginning of rowset", "Can't Navigate", 64 )
               endif
               return

Note that this code is almost, but not quite, identical to that used for the "Next" button ... it works very much the same, except we are going in the opposite direction. The "if" statement checks to see if we can navigate "backward" (if you will) through the rowset, and so on.

That should do it for our custom toolbar. We now have an object (a container object) that has five data access buttons and two data navigation buttons. These buttons also have code which will execute on a form's default rowset.

You may want to work on lining up the buttons, moving them, adjusting them a bit to get them just where you want them. If you set the top at 4, that will be a good start. Use the mouse and the keyboard (when you click a control, you can "nudge" it a bit -- this works).

Save the updated custom controls form. To export this component to the MyControls.cc you need to select the container. This container and all the object it contains will be exported as a single control.

Close the form in the designer.

To Avoid Problems Later ...

There is one last thing that we need to do before we can use our newly created custom controls. Open MyControls.cc in the Source Editor (click the "Other" tab and you will see any .cc files that are there) and look at one of the Class declaration lines. The MyEntryField class looks like this:

            class MYENTRYFIELD(parentObj, name) of ENTRYFIELD(parentObj, name) custom

You will notice that each class has two parameters: "parentObj" and "name". For reasons that are beyond the scope to this tutorial, we do not want to use the "name" parameter. The general rule about custom classes is to not include the "name" parameter in your custom class unless you are actually passing the name to the class. When we build our forms in the next part of this tutorial, we will not pass the "name" parameter, so we need to remove the parameter here.

This is actually easy and quick to do. You should still have MyControls.cc open in the Source Editor. Click the "Replace Text..." button on the toolbar. In the "find what" entryfield dialog enter ", name)" without the quote marks (that is: comma + space + the word "name" + a right parenthesis). In the "Replace with" field enter ")". Then you can click the "Replace All" button (or if you're a bit cautious like me, click "Find Next" and "Replace" until it has found all the occurances of the string).

Save this file and it's ready to use with the forms.

Summary

In this phase of the tutorial we have created a number of custom controls that can be used on our application's forms. The advantage of using custom controls, which you may have noticed requires a good deal of "up front" work, is that if you wanted to change a property or the behavior of a control for all of your forms that use it, you could change it in the custom class definition. Any forms that use this control will automatically get the new property or behavior.

Another advantage of using custom controls is that the next time you need to do this layout, all you have to do is reuse this custom control file (MyControls.cc, that is). The controls are already laid out and tested (we will test the controls in the next phase by using them on our tutorial forms).

After you have completed this tutorial, you may want to look at some other custom control files. The first place to look is the file FORMCNTL.CC -- this file is a part of the dUFLP library which is found in the dBASE™ Knowledgebase and on Ken's dBASE website (The dUFLP). The library is freeware code, and you should, at some point, examine it. There are, for example, 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!


  Back to the tutorial menu     Back to previous part of tutorial: Creating the Data Modules   Proceed to the next part of the tutorial: Creating the Forms