Example files available in mpforms.zip
The purpose of this document is to discuss multiple page forms and how to use them. This is a fairly simple topic, but is one that for a new developer using dB2K can be a bit confusing.
NOTE: This document was originally written for Visual dBASE 7.0/7.01, it has been updated for dB2K and dBASE Plus 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 addition, this document refers to dBASE a lot, but unless it is about a dBASE Plus specific aspect, the text can be used for Visual dBASE 7.0 through Visual dBASE 7.5 and dB2K.
There are at least three methods that can be used to create and work with Multiple Page Forms (using Tabs, using Pushbuttons, other programmatic means -- we will not be discussing the latter here), this HOW TO document will discuss two of them. We will not be discussing the use of the Notebook control, which is a visual control that can be used in addition to the discussions here to show multiple pages of a container (not of a form). Do not let this document limit your imagination. dBASE is a powerful developer's tool, and the chances are good that there are more ways than those discussed here to accomplish your task.
The Page Number (PageNo) property defines for a form what the current page number is. This can be changed programmatically with the statement:
form.PageNo := 2
for example.
Objects have a PageNo property, which defines what page they should appear on. As an example, an entryfield that was assigned to Page 2 would have its page number property defined along the following lines:
class ENTRYFIELD1(parentObj, name) of ENTRYFIELD(parentObj, name) with (this) height = 1 left = 1.2857 top = 0.7273 width = 10.7143 metric = 0 // Chars pageno = 2 endwith endclass
Importantly: since the default page of any form is page 1, any object placed on Page 1 normally will not stream out a "pageno" property in dBASE for page 1.
The PageNo property can be manipulated as any other property. However, in most cases, you will want to leave a specific object on a specific page.
In some cases, you may wish an object to appear on all pages of a form. This, as it turns out, is just as easy as the rest -- if you set the PageNo property to 0 (the number zero), that object will appear on all pages of the form.
The TabBox Control is a dBASE control, and is designed to allow you to create and manipulate multi-page forms easily.
NOTE: The Tabbox control is not limited to working with multiple page forms. It is possible to have multiple tabbox controls on the same form, with various code associated to do things ... Once again, dBASE is a powerful language/software package, and if you can think of a use for the Tabbox control the chances are you can use it for that ...
In the "Standard" page of the Component Palette when designing a form, you will find an icon for the TabBox. If you click on it and drag it to a form, a single-tab tabbox will be placed on the bottom of your form.
The TabBox should default to having a PageNo property of 0. If it does not have this number, please set it now. You can assign a name to the TabBox if you want. The default is usually TabBox1, which is what this document will assume.
You need to set the tabs for this yourself. If you look at the DataSource property, you should see: ARRAY {"TabBox1"}.
This brings up a topic of interest that we will briefly examine (arrays are covered in more detail in other HOW TO documents):
Literal Arrays The default array type for a TabBox is what is called a Literal Array. This is an array that may be defined with the visual designer. The one thing about it that is a little disconcerting is that it has no name. There is no way to refer to the contents of this array. In most cases, when using a TabBox, this is absolutely fine! However, you don't have to use a literal array if you don't want to. You can use a more standard dBASE Array instead (one with a name) -- the only reason I can think of for doing this would be if you needed to know the text of the tab the cursor is currently on, but there could easily be other reasons.The visual designers allow you to build your array through a really spiffy dialog box. To get to it, click on the tool icon next to the current array definition. You can add and remove and rearrange items quite easily with this dialog, and when you're done the "OK" button will place the contents of the array on the form. The number of tabs on your tabbox is the same as the number of elements in the array.
PROCEDURE Tabbox1_onSelChange
In this procedure, you will want to set the form's PageNo to the current tab number. The current tab number property is: CurSel. The procedure might look like:
PROCEDURE Tabbox1_onSelChange form.PageNo := this.CurSel
The reason to put this into its own event handling code, is that you may wish to get more fancy (such things as perhaps checking to see which page is now the current one, and setting specific values for objects on the form, maybe changing the form's rowset to a new one ... lots of possibilities spring to mind ...).
If you drop a couple of pushbuttons on a form, you will need to set the OnClick events to handle changing page numbers. This is simple enough (example below).
You should set the OnClick event for both of these buttons to handle changing the page number, but since there are some other things that may need to be done as well, it is recommended that you call an event, rather than using a codeblock.
One suggestion is to simply create an event for each that handles incrementing and decrementing the page number, and then calls another routine to handle anything else that may need to be accomplished when you change pages (such as setting the focus to the appropriate object on the appropriate page).
The following is suggested code (it assumes your pushbuttons are named "NextPage" and "PreviousPage"):
PROCEDURE NextPage_OnClick form.PageNo++ // increment the page number class::MyPageChange() // procedure to handle // other items necessary PROCEDURE PreviousPage_OnClick form.PageNo-- // decrement the page number class::MyPageChange() // same same
The OnClick description for the pushbuttons would then be (in the Property Inspector, Methods page): class::NextPage_OnClick or: class::PreviousPage_OnClick.
When navigating through a form with several pages, you might have a problem if you simply left the pushbutton code alone. What if you are on Page 1, and the user clicks on the "Previous Page" button? There is no Page 0 (well, there is, but it's usually pretty ugly, because it contains all objects on the form). What if you are on the last page, and the user clicks on the "Next Page" button? If there is no page defined, the form will display a blank page.
This can be handled in the MyPageChange procedure by enabling or disabling the pushbuttons as needed:
PROCEDURE MyPageChange if form.PageNo == 1 form.PreviousPage.Enabled := false else form.PreviousPage.Enabled := true endif if form.PageNo == 3 // assume page three is the last number form.NextPage.Enabled = false else form.NextPage.Enabled = true endif
These two if/endif statements could be refined a bit using the dBASE IIF() function, but I wanted to show the longer version of the code for clarity's sake.
Here's the short-hand version:
form.PreviousPage.Enabled := iif(form.PageNo==1,false,true) form.NextPage.Enabled := iif(form.PageNo==3,false,true)
SUGGESTION: In order to avoid hard-coding a constant value like the last page number in your code directly, is to use a method of the form itself, used to display the highest page number that has any components on it:
form.pageCount()To use this in the code described above, try:
form.NextPage.Enabled := iif(form.PageNo==form.pageCount(),false,true)The advantage to doing this is that if you add pages to the form later (or remove them) you do not have to update the code to handle it.
PROCEDURE MyPageChange // if using pushbuttons to move between pages: form.PreviousPage.Enabled := iif(form.PageNo==1,false,true) form.NextPage.Enabled := iif(form.PageNo==form.pageCount(),false,true) // handle setting focus and anything else that might be // needed when the page number changes. do case case form.PageNo == 1 form.entryfield1.setfocus() case form.PageNo == 2 form.entryfield43.setfocus() case form.PageNo == 3 form.entryfield71.setfocus() endcase
The code may look something like:
if form.Tabbox.Cursel # 1 and empty(form.entryfield1.value) MsgBox('Must Enter Client # First!') This.CurSel := 1 form.entryfield1.SetFocus() else form.pageno := this.cursel endif
When saving a form that uses multiple pages in the forms designer, you may find that if you were working on a page other than page 1 when you saved the form, that the page number you were working on got streamed out to your .WFM. When you run the form the next time, you will not start on Page 1. The fix for this is to ensure that no matter what page you were working on, when the form opens, it starts on page 1. This is best done in an OnOpen method for the form:
PROCEDURE form_onOpen form.Tabbox1.curSel := 1 // this will fire the onSelChange // event of the tabbox // any other code you need
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: MULTPAGE.HTM -- January 22, 2004 -- KJM