OODML and Forms

Creating a Simple Data Form in dBASE
Last Modified: January 22, 2004
Ken Mayer, Senior SQA Engineer
dBASE, Inc.


NOTE: This document was originally written for Visual dBASE 7.0/7.01, it has been updated for dB2K (release 1) to include information about new properties, events, etc., and any new controls since the document was first written. There have been very few changes for later versions of dBASE. 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.


The Purpose of this Document

This document is aimed at assisting a beginning developer, or a developer new to dBASE (and later versions) understand the basics of getting a form to communicate with the Object-Oriented DML of dBASE.

The idea is that the student will follow along, and create a form that uses one of the sample tables that ships with dBASE. The form will be tested along the way, so that the student can see their progress ...

This document will NOT get into working with custom forms, custom controls, and so on. There are other HOW TO documents that cover these in detail, and when developing a real application, the student should examine those documents (available from the same location that this was downloaded from). There is also a Tutorial available from the same site that gets into creating a complete application using custom forms, custom controls, etc.

Suggested Reading
It is suggested that a student coming to dBASE from earlier versions of dBASE (dBASE/DOS, Visual dBASE 5.x, etc.) examine "XBase to OODML" HOW TO in the Intermediate section of the Knowledgebase to get a good feel for the differences between XBase commands and the new OODML. It is also suggested that a student coming to dBASE from ANY platform, or as a new developer, take a good look at "Beginning Data Objects" -- a HOW TO document that takes a very detailed look at how the data objects in dBASE work and how they are related to each other.

Getting Started

First, as the sample files will be changing over time, this project is not based on any particular table. Please note that if the table shown in this document is no longer available, you can use some other table to learn to do what we will be doing ... there is nothing here that is table-specific ...

Before doing anything, load dBASE. You should see the Navigator window -- this has a variety of options. Before we do anything else, we need to set ourselves to a working folder. The simple version of doing this, for this project, is to set your working directory to the SAMPLES directory that ships with Visual dBASE.


The Navigator in dBASE

To do this, click on the button with a folder on it, next to the "Look In" drop-down (combobox). A special window will appear that allows you to work in the directory/folder structure on your hard drive(s). Find the "Plus" folder, and under it (double-click on it), you should see the "Samples" folder. Double-click on that folder, and click the "OK" button.


The 'Choose Directory' window

You will see a variety of files in the navigator -- ignore them, as we will not be using them.

Create The Form

To create a new form, using the navigator, click on the "Forms" tab of the navigator. You will see two "Untitled" icons -- the second one is for a custom form -- we're not going to get into that here -- ignore it. Use the first one -- simply double-click on it (you can also right-click on the icon, and select "New Form" or click on it once, and use the <Shift>+<F2> keystroke).

You should see a new form appear as shown in the image below:


A Blank Form

With the blank form on the screen, you will also see a few "palettes" ... we will use these in a moment. However, next we need to put a table reference on the form, so that the form knows what table we are working with.

Put A Query On The Form

The table reference is a query object -- this object will appear as an icon with the letters "SQL" on it. To put the Query object onto the form, click on the navigator, and select the "Tables" tab. You will see tables from the SAMPLES directory there. As noted elsewhere in this document, the actual table does not matter. Since in dBASE, the "FISH" table exists, and this is being written with dBASE in mind, I will be using the "FISH" table. If this table does not exist in your SAMPLES directory, use any other table ...

Click on the table you wish to use, and hold the mouse button down -- drag the icon to the form surface. This is called "Drag and Drop".

Now, let's examine what just happened ... you have placed an icon onto the form, and your form should now look like:


A Blank Form with a Query Object

In addition, when this was done, the form's property "rowset" was set automatically. This is very important to note, as this property gets used a lot -- it refers to the rowset object.

To see that this was done, click on the Inspector -- this is one of the palletes on the screen. The inspector is very useful as it shows you what is going on with the objects, and allows you to change the objects. We will be using it a lot. If you do not see the inspector, right click on the form's surface, and in the popup menu that appears, select "Inspector". It should appear on the screen. You can also turn the inspector on/off by using the <F11> key.

Find the rowset property of the form in the Inspector. (This will be under the "Miscellaneous" heading, if you are using the categories -- if you see a "+" next to the word "Miscellaneous", click on the word, and it will expand to show a bunch of properties ...)

In the rowset property of the form, you should see something like: form.fish1.rowset ("fish1" may be different, depending on the table you dragged to the form's surface).

Now, it may not look like much, but you have actually done quite a bit ... the form now has a primary table to work with ... from here, you can do a lot, and we'll start looking at some of this.

Put Some Fields On The Form

We are going to use the Field Palette to drag fields from the table to the form. If you are developing a real-world application, you may not wish to do this (see the HOW TO document on Custom Controls ...).

The field palette contains a list of the fields in your table, and by default shows icons for each, the icons showing the type of control that will be placed on the form (entryfields, etc.).

For our purposes, drag a few fields -- it doesn't matter which -- to the form surface. Make sure you have at least one or two entryfields, as we need to be able to actually edit some of the data for testing later on.


Form With Controls from Fields Palette

The image shown as an example uses all of the fields in the "Fish" table ... note the colors -- this was done automatically because the table, when it was created by the "Borland Samples Group" has some custom properties for the fields -- including setting a "colorNormal" property to the colors shown here.

An important property that is used for all "datalinked" controls (controls that are linked to data in the rowset) is the dataLink property. To see this, click on one of the entryfield objects, and in the inspector click on the "DataLink" property (this is under the "Data Linkage" category). You will see something like: form.fish1.rowset.fields["Name"]. This was done for you by dragging the object from the Field Palette to the form's surface. You can also do this by hand, or by dragging an Entryfield from the Component Palette, and then setting the datalink property in the inspector yourself.

Interacting With the Data

Now that this is all here, how do we interact with the data? Well, basically, if you simply run the form you can now use it. You could consider it done ...

To see this, run the form -- you can do this by clicking on the button in the toolbar at the top of the screen that shows a lightning bolt. Before you can run the form the first time, you must name it -- in the dialog box, type "TutorialForm" and click the "Save" button. The form will now run.

With the form running, the toolbar at the top of the screen changes, and you will see some navigation buttons ... you can also put the cursor in an entryfield and change the data. There are other buttons in the toolbar that will change if you change the data, there's an "Add" button to add a row (record) to the table, and so on.

However, what we want is to be able to do this kind of stuff ourselves. In a real application, you may not want the user to see the IDE (Interactive Development Environment), so ...

To go back to development mode, click on the button next to the lightning button (the one that shows a ruler, a triangle, and a pencil) ...

Putting Buttons On the Form

Why pushbuttons? These are the standard Windows method of navigating through a table ... we will create a series of buttons here that will have code associated with the onClick event of the buttons that will control the form and the table on the form.

These buttons can be done in quite a few ways, but we're going to do simple text, no images. If you want to do more, you can spend a bit of time tinkering ... but for our purposes, we want to keep it as simple as possible, as we are concentrating on getting these objects communicating with the rowset ... we're not as concerned with esthetics ...

Navigation Buttons

We'll start with navigation buttons. 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. You probably want to be able to go to the top of the table and to the end of the table (first row/last row). So we need four pushbuttons.

We'll start with the "First" button. Set aside some space on your form for buttons (move the objects around, make the form later, whatever ...) so you have room for a few different buttons. In the example, I will place them down the right side of the form ... there are many ways to do this ...

You should see a "Component Palette" on the screen in the design mode. If not, right click on the form's surface, and select this -- it should now appear.

The "First" Button
On the "Standard" page of this palette, you will see a variety of controls. 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 pushbutton -- click on the pushbutton object and drag it to the form's surface. You can move it around to wherever you need it to be ...

The text of this button defaults to the name of the object, which is "Pushbutton1" -- 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 "Pushbutton1", "Pushbutton23", 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 "FirstButton" and press the <Enter> key.

Next, find the "text" property, which will be under the "Miscellaneous" category. Click on "text" and enter "First" and press <Enter>.

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

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 FIRSTBUTTON_onClick
      
      return

Any code you want to be executed when the user clicks on the button goes after the "function" statement, and before the "return" statement.

The code we want is quite simple. We want to go to the first row in the rowset when the user clicks on this button. To do this, we have to execute a method of the rowset, called "first". To do this, we type the code:

      form.rowset.first()

What this does is it goes to the form object, and looks at the rowset property -- if no rowset is assigned to this property, this code will not work. However, we did that when we placed the table reference (the query object) on the form earlier. Assuming that a rowset is assigned, it then executes the "first" method (the parentheses are necessary to execute the code).

That's all there is to it. So you should see now in the editor window:

   function FIRSTBUTTON_onClick
      form.rowset.first()
      return

To test this out, we need to run the form. So click on the lightning button. With the form running, we need to move to a different row (we start at the first one by default). Use the navigation buttons at the top of the screen in the toolbar, to move a few records into the rowset (use the 'next' button). Now, rather than using the button in the toolbar, use the button we just created on our form -- click on it, and you should find yourself back at the first row in the table.

Now that we have created one pushbutton, the steps should be easier for doing more ...

The "Next" Button
We need to add the "Next" button, so do the following steps:

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 NEXTBUTTON_onClick
      form.rowset.next()
      return

Like before, let's test this ... run the form (click on the "lightning" button), and use the "Next" button to go to the next row. Keep doing this until you get to the end of the table (you'll know when it happens ... you'll get a a blank row!!).

Now, what happened here? You are pointing to the "end of rowset" pointer, and all the fields went blank. You don't really want your users to do that ... 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. Go back to design mode (the button in the toolbar next to the lightning button).

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

To do the above, change the code for the "NextButton" by clicking on the button in the inspector, click on the "Events" tab, click on the "onClick" event, and then the Tool button. Change the code in the editor so it looks like the following:

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

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

Now to test this, run the form again, and try navigating past the last row, and you should get the error message we created, and we will not see the end of rowset ...

The "Previous" Button
We need to add the "Previous" button, so do the following steps:

Enter code like the following:

   function PREVIOUSBUTTON_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 table, and so on ...

You can test this by running the form, clicking on it, and seeing the error message, navigating with the "next" button and then the "previous" button ... and so on. Then come back to the designer ...

The "Last" Button We need to add the "Last" button, so do the following steps:

Enter code like the following:

   function LASTBUTTON_onClick
      form.rowset.last()
      return

One more time, you can test this by running the form, then clicking on the button -- it should take you to the last row in the rowset ... when done bring the form back to the designer ...

Your form may look something like this:


Form With Navigation Buttons

Editing Buttons

Now that we can navigate through the table, 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?

There is a property of the rowset object called autoEdit -- this defaults to true. It means that the row is automatically in edit mode. If you want to keep the user from automatically editing the row, the simple solution is to set this property to false.

To change this property, we need to modify it in the inspector, but to get there we must go through the query object ... so, click on the query object (the "SQL" icon on the form), and then go to the inspector. Click on the "rowset" property (it will say "Object"), and then click on the "I" button ("I" is for "Inspect"). You should see the autoEdit property (under the "Miscellaneous" category), and it should show as "true". Double-click on it, and it will change to "false".

To see if it works, run the form again. Click on one of the entryfields, and try to type something ... note that you cannot. This is because of the autoEdit property being set to false.

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

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

Enter code like the following:

   function ADDROWBUTTON_onClick
      form.rowset.beginAppend()
      return
Note that when your 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.

What you (or the user) have done at this point is created 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 "Add Row" 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.

We can test it right now, but let's move on and add the other editing buttons first, and then we can test the series ...

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

Enter code like the following:

   function EDITROWBUTTON_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 "Add Row" 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 ...

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

Enter code like the following:

The problem with deleting a row in a table in dBASE, 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 DELETEROWBUTTON_onClick
      if msgbox( "Delete this row?", "Delete Row?", 36 ) == 6
         form.rowset.delete()
      endif
      return

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

Enter code like the following:

   function SAVEROWBUTTON_onClick
      form.rowset.save()
      return

Abandon Changes Button
We need to add the "Abandon Changes" 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 ABANDONBUTTON_onClick
      if msgbox( "Abandon changes to this row?", "Abandon changes?", 36 ) == 6
         form.rowset.abandon()
      endif
      return

At this point, your form may look like:


Form With Edit Buttons

Modified and State Properties of the Rowset

These two properties of the rowset have been mentioned elsewhere in this document, so let's take another look at them.

The modified property is used when a row is placed into edit mode, to determine if the row's buffer has been actually modified by the user. You can check the value of this property easily in your code by simply using:

    if form.rowset.modified
       // do something
    endif

This can be quite useful in situations where you need to check it. For example, what if the user hits a navigation button after modifying a row? Do you want to just assume that the user wants to save the changes made? Wouldn't it be better to ask? You can do that by adding code to your navigation buttons to check for this. (We won't do it for the example here, but it's a good idea ...)

In addition, if you have code that executes when a user does some task that modifies the code, your code does not set the modified property to true. Therefore, you may need to set this property to have the rest of your form's code work properly. This is done by simply assigning a value:

   form.rowset.modified := true // or false if you need it to be false

The state property is one that is constantly changed, based on what state the rowset is currently in. There are six different possible states: closed, browse, edit, append, filter and locate. By default, your rowset is in browse mode, but if your user modifies the data, they are in edit mode, if they are adding a new row, you are in append mode, and so on. "Closed" means that the query object's active property is false.

What does this mean? Well, it means that like the modified property, you can query it. However, the different modes are determined by numbers, starting at the number 0, and going to 5. So, the modes are:

0 Closed
1 Browse
2 Edit
3 Append
4 Filter
5 Locate

One of the problems with the way the form works when it is running is that it is difficult to tell what "state" the rowset is in. You might want to add a text object to the form that shows this state, or you might want to use a calculated field that updates an entryfield to show the state. The advantage to using a calculated field is that this is automatically updated, where a text field would need to be updated by your own code. The following is a simplified version of this, based on work by Gary White (one of dBASE, Inc.'s own dBVIPS). The steps are provided to make it as easy to create as possible.

Now, after all that, try running the form ... click the "Add Row" button, and the word "View" should change to "Append". Click the "Abandon" button (select "Yes" to abandon). Click the "Edit Row" button, note that the rowstate control shows "Edit" ... Pretty spiffy. Click the "Abandon" button again (and select "Yes" again ...).

Filter by Form

It is possible to allow the user to filter data based on criteria. One method of doing this is to use the "filter by form" technique -- this can be done by putting a button on a form, and using the rowset's beginFilter method -- this method will clear out the datalinked controls on the form, allowing the user to specify what to filter the data on. For example, they could type a specific name that they wanted to look for in the name field (if you have one).

Once the criteria is entered, the user would need to then apply the filter, which could be done either with the same pushbutton, or with another ... this would tell dBASE to find all rows that match the condition(s) specified and only display those in the form -- the user could move through the table looking at those.

Finally, you would to have a method of clearing the filter condition ...

The following is based on code that is much more complicated in the dBASE Users' Function Library Project (dUFLP) -- this code was compiled from several sources and has had various developers put their touch on it ... I am simplifying it for this example ... All of the following is for a single pushbutton ...

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

   function FILTERBUTTON_onClick
      do case
         // we are in "beginFilter" mode:
         case this.text == "Filter"
              // change Text
              this.text := "Run Filter"
              // save current bookMark so we can return 
              // to it:
              form.bookMark = form.rowset.bookMark()
              // set form to "filter" mode
              form.rowset.beginFilter()
            
         // we are in "Run" mode (applyFilter() )
         case this.text == "Run Filter"
              // don't update the controls until told otherwise
              form.rowset.notifyControls := false
              // try the filter -- if we don't find a match
              // we'll clear it out ...
              try // catch error about memo:
                 if not form.rowset.applyFilter()
                    // reset the text back to "Filter"
                    this.text := "Filter"
                    // clear the filter
                    form.rowset.clearFilter()
                    try
                       //attempt to go back to the 'bookMark'
                       // if we can't, we go to the first row
                       // in the table
                       form.rowset.goto( form.bookMark )
                    catch ( exception e )
                       form.rowset.first()
                    endtry
                    // let the user know that no match was found
                    msgbox( "No match found", "Filter error", 48 )
                 else
                    // otherwise the filter worked, 
                    // so now we want to change the image to
                    // a "clear Filter" ...
                    this.text := "Clear Filter"
                 endif
              catch ( dbexception e )
                 msgbox( "Can't filter on a memo field", "Filter error", 48 )
                 form.rowset.goto( form.bookmark )
                 this.text := "Filter" 
              endtry

              // turn this back on
              form.rowset.notifyControls := true
              // and actually refresh to show the first row
              // that matches the filter
              form.rowset.refreshControls()
               
         // clearFilter()
         case this.text == "Clear Filter"
              // reset text 
              this.text := "Filter"
              // clear out the current filter ...
              form.rowset.clearFilter()
      endcase
      return

Before testing this, we need to do one more thing, and that is to set the rowset's filterOptions to allow as much flexibility as possible ... to do this, click on the query icon on the form, and in the inspector, click on the rowset object, then the "I" button. For the rowset object, then click on filterOptions, and select "3 - Match Partial and Ignore Case". If you do not do this, then the default for the filter is to match the length and match case, so the filter may not find a match easily.

Now that this is done, try it out ... try filtering on something you know exists, on something you know doesn't exist, etc. It works pretty well. Note that the Rowstate control automatically updates ...

Other Methods of Filtering Data
You can get more specific and write code that will handle filtering the data, and more. There are problems with "Filter-by-Form" mode, and they include the fact that the filter assumes "equality" -- you cannot specify conditions such as "less than" and so on, and you cannot use "and", "or" type of operations. For that you would need to create a more complex form ...

Locate by Form

The ability to locate a row in a table by form is similar to the way the filter-by-form works, but is also slightly different. Filter limits you to just the rows that match. Locate finds the first row that matches, but no filter is applied to the data ...

Just like the filter button shown above, the code is a bit complex, but this is still simplified quite a bit from the code in the dUFLP custom code ... we'll discuss some of this later.

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

        // here's where all the work starts:
        do case
           // we're now in "locate by form" mode:
           case this.text == "Locate"
                this.text := "Run Locate"
                form.bookmark = form.rowset.bookmark()
                form.rowset.beginLocate()

           // we're in "Run" mode ...
           case this.text == "Run Locate"
                // if user hit 'run' but didn't
                // change anything on the form:
                if not form.rowset.modified
                   this.text := "Locate"
                   form.rowset.abandon()
                   return
                endif

                // problem with doing a locate on
                // memo fields:
                try // the first 'try'
                    // here's the search:
                   form.rowset.notifyControls := false
                   if not form.rowset.applyLocate()
                      this.text := "Locate"
                      try // second/nested try
                         form.rowset.goto( form.bookMark )
                      catch ( exception e )
                         form.rowset.first()
                      endtry
                      msgbox( "No rows match the condition", "Locate error", 48 )
                   else
                      // we found a match ...
                      // "Continue" text:
                      this.text := "Continue Locate"
                      // save *this* bookmark ...
                      form.bookmark = form.rowset.bookmark()
                   endif
                catch ( exception e )
                   msgbox( "Cannot search in memo", "Locate error", 48 )
                   form.rowset.goto( form.bookmark )
                   this.text := "Locate"
                endtry // end of catch for memo

                form.rowset.notifyControls := true
                form.rowset.refreshControls()

           // we're "searching again"
           case this.text == "Continue Locate"
                // look for the next match:
                if not form.rowset.locateNext()
                   form.rowset.goto( form.bookmark )
                   msgbox( "No more rows match the condition", "Locate error", 48 )
                   this.text := "Locate"
                else
                   form.bookmark = form.rowset.bookmark()
                endif
        endcase

Just like with the filter, the locateOptions property of the rowset will affect your ability to find a matching row or rows in the rowset. And, just like the filterOptions property, setting the locateOptions property for the rowset to "3 - Match Partial and Ignore Case" will solve this and make it possible to find most matches.

The locate option is rather handy, but it has similar limitations to what were mentioned above with filters -- if you need a more complex "locate" you would have to write your own code ...

Wow. After all that, hopefully you're starting to get a feel for how all this works ...

One More Button -- To Close the Form

There's only one more button needed, and that is a "close" button -- it's not really necessary, but it can be useful ... the user can close a form from the "X" button on the titlebar of your form. However, we'll throw this one in and call it a day ...

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

   function CLOSEBUTTON_onClick
      form.close()
      return

You could check the state of the rowset, and if the user is in edit or append mode, ask if they want to save it ... there's a lot more that you might want to do. This is just a beginning ...

Summary

Well, after all that, it is hoped you have a better feel for how to get your form to interact with your data. Please note that there are a lot of things we have not spent time on. We didn't get into using a grid, we didn't get into those more complex ways of filtering or finding data, and more. As mentioned at the beginning of this document, there are other HOW TO documents, as well as a tutorial for dBASE to cover those. We didn't get into using custom pushbuttons, custom forms, and more ...

In addition to the HOW TO documents that are available in the Knowledgebase, you may also be interested in the dBASE Users' Function Library Project (dUFLP) -- this is a library of freeware code donated by many developers over the years. The code includes a set of pushbuttons that can be used with forms, and a series of custom controls, and a lot more.


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 dBASE 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 dBASE 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 2004, Kenneth J. Mayer. All rights reserved.

Information about dBASE, Inc. can be found at:

       http://www.dbase.com
    


EoHT: OODMLForm.HTM -- January 22, 2004 -- KJM