Example files available in forms.zip
NOTE: This document was originally written for Visual dBASE 7.0/7.01, it has been updated for 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 dB2K release 0.4. Note however that some bugs that were fixed in Visual dBASE 7.5 are no longer referenced here ...
This document is aimed at helping a developer use the form controls in dBASE. Now, this seems like it might be a silly thing to write, as most people can figure out how to use an entryfield when they design their forms, but there are a lot of questions on the dBASE Newsgroups concerning various controls. Some of the questions may seem pretty basic to someone who has been using dBASE or any other Windows product for some time, and some of the questions are fairly advanced. This is an attempt to explain the basics of most of these controls, as well as an attempt to get into some of the more interesting uses that some controls may be put to ...
The following controls are not covered in this document:
There are sample forms included with this document that show the use of some of the controls, as well as various properties, events and methods of those controls. If you are uncertain how a control works, run the forms and experiment, that's how this document and sample forms were created, experimentation.
Before we get into a discussion of the different controls covered in this document, it's opinion time ... This is an opinion of mine that is shared by most, if not all, experienced dBASE developers: Never, ever use a stock dBASE 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 (see the Custom Classes HOW TO for details). The same can be said for your forms (see the Custom Forms HOW TO for details). Never use the stock controls. For the sake of simplicity, this document will use stock controls, but you should check out the HOW TO on Custom Classes to learn something about creating your own custom controls.
NOTE: With all the emphasis on using custom controls above, one question might be "How do I change my current controls on forms to custom ones?" Assuming you have a custom control file with your own custom controls, you could open the source code of the form in the source editor (right click and select "Open in Source Editor" (or pressafter a single click on the icon)). At the beginning of the source code for your form (see below) add the appropriate SET PROCEDURE statement:
class comboboxForm of FORM set procedure to mycontrols.cc additive with (this) [code snipped]Then use the search and replace options (Ctrl+R) to replace all occurances of specific words with then name of your custom controls, for example -- if using formcntl.cc in the dUFLP library (noted periodically throughout this document), you might replace all occurrances of the words "NEW ENTRYFIELD" with "NEW kmCustEntryField". From this point on the form will use any properties, events and/or methods you have defined for that custom entryfield.
There are two ways to get controls onto a form while using the form designer. The first is to use the controls that appear in the Component Palette. The second is, once you have a query or datamodule on your form that is set to open at least one table, is to drag the controls that are already datalinked to fields in the tables to the surface of the form.
NOTE: The form designer has a built-in assignment of specific field types to specific controls. When you first install dBASE, you will find that, for example, spinboxes are assigned by default to numeric and date fields. This may not be desireable -- and you can change this in the form designer. Go to the menu (when you have a form in the designer) and select File and then select "Associate Component Types". (You can also do this by right clicking on the field palette and selecting "Associate Component Types" from the popup there.) Note that Numeric and Date types have spinboxes associated with them -- if you select Entryfields for these, this will affect new controls you place in your application until you change the settings back. (This is mentioned because I have very few situations where I prefer spinboxes for numeric and/or date fields, and find I would rather use entryfields for these types. From discussions in the newsgroups, I'm not the only one who feels this way ...)
Properties, events and methods that are specific to a control are covered in the discussion of that control.
Properties
Properties are the attributes of controls and include among others font,
identification, size and visual properties. A property setting can be changed
programmatically as well as in the form designer. The setting is normally a
single value, for example, a character string, number or reference to another object.
The dataLink is "live" -- this means that as soon as you run the form, you are working with the table's field(s).
There are a lot of tricks working with tables, and you should read the HOW TO document on OODML, to learn the details. The basics are that you really are working with a record "buffer" -- this means you see on the screen an "image" of the record (or row), and if you make changes, it is to the image. If you navigate or save the changes, then they are written back to the table. However, you can also abandon those changes before they are written to the table. All of this is done automatically for you by the rowset object of dBASE.One trick I will suggest here, but you can take it or leave it -- set the rowset object's autoEdit property to false. This will disable changes to a rowset unless the user clicks an edit or append button (which you would need to provide on your form). It also will not affect the color of the entryfields -- see discussion on this in the section on using entryfields.
If you drag a field from the Field palette, you will get a more useful name: "ClassnameFieldnameN", where "Fieldname" is the actual name of the field. If you have spaces in it, these will be removed or replaced with underscores, "Classname" and "N" are the same as above, so if you dragged a "First Name" field to the form from the field palette, you might see a name of EntryfieldFirst_Name1.
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. You could use something like what is used by the field palette, but unless you have multiple first name entryfields on a form, you can probably remove the number.
"Morphing" a field's value is basically storing one value in the field and displaying a different value in the control that is being used on the form. Morphing can be used in lookup tables, where you store a key or code value in the main table, and perform a lookup in another table -- what the user sees is a completely different value from what is stored in the table. dBL has a method of handling this automatically -- this is discussed with the Combobox control, and can be examined in more detail in the online help under "lookupSQL".
Events
Events are something that controls respond to, a mouse click, a control getting focus,
a change in the control's value, etc. Events are always "firing" provided the
event is occurring, however, in order to have something happen, you have to
"hook" code to the event. (Windows itself is really just a big "event" handler --
it does not do anything until some event is fired ...)
Events whose name begin with ON, such as onOpen, occur after the action to which they refer. Thus, the onOpen event occurs after the form is opened. Events that begin with CAN, such as canClose, must return a logical value that will determine whether the indicated action may proceed. So, a form's canClose event must return true before the form may close. Returning a value of false will prevent the form from closing. Failure to return a Boolean value may yield unpredictable results.
Note that there are a set of Mouse events not discussed here (onLeftDblClick, onLeftMouseDown, onLeftMouseUp, etc.). These are pretty obvious when they fire if you look at the names of the events. The onLeftMouseUp (same for onMiddleMouseUp and onRightMouseUp) has the interesting effect that it often gets called twice on a double-click ... if you attempt to use both, you may be shooting yourself in the foot ...
Methods
Methods are code that performs an action. Controls have a number of built in
methods which are called through the control, i.e., form.entryfield1.copy().
Code which responds to a control's event is also a method, but not necessarily
a method of the control (usually a method of the form).
One thing you should remember is that if you use the built-in methods as a programming hook to insert your own code (this is called over-riding the method), you will nearly always want to call the original built-in method before, during, or after your code. (This would be achieved by entering: SUPER::methodname() into the code ...)
There are more methods than setFocus(), but most of them are either specific to the control, or are pretty straightforward. Some of these are clipboard specific (copy, cut, paste, undo). Some deal with moving the control (move()). You can also explicitly release a control from memory using the release() method. It didn't seem like it was necessary to really spend a lot of time on these.
There is not a lot that needs to be said here that isn't covered in the "shared properties ..." section of this document.
Non-Datalinked Entryfields
SEEKER: One interesting entryfield that ships with dBASE, and appears in the component palette on the Custom page (unless you have closed all procedures, and such) is a Seeker control.This control does not get datalinked to any fields in a table. Its purpose is to perform what is called an "incremental search" -- this is a search that is performed based on what is entered into the entryfield. To use a seeker control see the document in the Knowledgebase concerning "Working With the Custom Classes that Ship WITH dBASE".
A really great use of the seeker control is to combine it with a "read-only" grid (one with the allowEditing and allowAddRows properties turned off, and the rowSelect property turned on) -- this will show the current row as you use the seeker ...
See sample form: SEEKER.WFM for an example of this. Note that seeker.wfm creates a dummy table and randomly populates it.
The problem is that entryfields default to the type "Character", with a max length of 25 characters. This means entering a number or a date will not automatically use the standard error checking built in to dBASE and the value you get when you check the entryfield's value property may not be what you were expecting. For example, consider the following code:
local f f = new valuesForm() f.open() return class valuesForm of FORM with (this) scaleFontBold = false height = 7 left = 10 top = 1 width = 18 endwith this.entryfield1 = new entryfield(this) with (this.entryfield1) height = 1 left = 5 top = 1 width = 8 endwith this.entryfield2 = new entryfield(this) with (this.entryfield2) onChange = {;form.entryfield3.value=form.entryfield1.value+this.value} height = 1 left = 5 top = 2.5 width = 8 endwith this.entryfield3 = new entryfield(this) with (this.entryfield3) height = 1 left = 5 top = 4.5 width = 8 endwith endclass
If you copy and paste this code into a dBL program and run it, you will see a form with three entryfields. Enter a numeric value in the first entryfield, tab to the second entryfield, enter another numeric value and press the tab key. The bottom entryfield will contain the results of adding the other two values. What you expected? Probably not. The results would be the same if you tried entering dates. The strings you enter are concatenated instead of adding the numeric values.
The solution is to use the onOpen event of the entryfield (or a spinbox for that matter), and you can set a value that will force the entryfield to work as a date or numeric type. For dates, you could set a blank date with {} (two curly braces), or you could set a value to the current date using the date() function. For a numeric value, if you do not have a default starting value, simply using zero will work:
function entryfield1_onOpen form.entryfield1.value = {} // or: // form.entryfield1.value = date() // or {} for empty date // or: // form.entryfield1.value = 0 // numeric
If the selectAll property is false, tabbing to the entryfield will not highlight what is in the entryfield. The cursor will start at the left of the entryfield, and the user may need to delete numbers in the entryfield.
The spinbox has all the characteristics of the entryfield, and the following as well:
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.
Tip: use the group property for the FIRST radioButton -- set it to true (if not done for you by the designer). 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 (for example, a user using the arrow keys will loop through the radiobuttons ...).
It has some really interesting capabilities, but you must be careful ... some things don't work quite the way you might expect.
Editors and the Rowset's autoEdit Property
The editor control may ignore the rowset's autoEdit property,
which can be a bit disconcerting and frustrating. If you add the
following code to the editor control's key event, you can avoid this
particular problem:
function key /* 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
HTML and Editor Controls
Editor controls, as mentioned elsewhere, can be used to display
text formatted with HTML.
One item of interest here is that the editor control can actually use the HTML <IMG> tag. However, please note that HTML itself does not know anything about tables, rowsets, fields, and so on, so you cannot directly set the image source to look at a field.
However, if what you need to do is embed an image into an editor control's HTML that is stored in a table, what you can do is use the binary field object's copyToFile() method, and then in the editor control set the <IMG> tag's SRC to point to that file. You might have to do this sort of thing by using the rowset's onNavigate or canNavigate event(s) to copy the file out to disk and then delete it. This should give you a pointer or two, though.
Note that you can drag & drop an image into a memo editor and the appropriate HTML tag will be stored in the memo.
Please note that many other HTML tags can be used as well in your HTML in the editor, but not all. These tags include lists (<UL> <OL>), header, font (useful for colors), italic, bold, underline, etc ...
See the Text control and information about HTML there for more details on what is available ...
Tip: If you (or the user) make any changes to the dataSource, those changes are not automatically reflected. The dataSource is read when the form is instantiated and is not re-read unless you specifically tell it to do so. You can accomplish this by simply issuing the following command (using the name of your listbox):
form.listbox1.dataSource += ""This causes the listbox to re-evaluate the dataSource property ... Note that this would be called in the onChange event of the control that changed the dataSource of the listbox.
If you use the multiple property, the selected method takes on a different meaning than it does if you have multiple set to false. This method will actually give you a reference to an array of selected elements in the listbox.The array is accessed like:
form.listbox1.selected()[ index ](index is the array's index, i.e., 1 would be the first element in the array)If your listbox.dataSource is pointing to a field in a table, the value returned by the selected() method will be a bookmark for the rowset. This can be a bit disconcerting, as you might have expected to see the value of a specific item. In order to do that, you would need to move to the row in the rowset. You could use code similar to:
clear // need to do this, or the listbox gets updated // as we navigate form.rowset.notifyControls := false a = form.listbox1.selected() // loop through the array defined by the selected() // method for i = 1 to a.size // go to bookmark: form.rowset.goto( a[i] ) // display value ? form.rowset.fields["Name"].value next // turn on the notify, or things get really weird form.rowset.notifyControls := true
if form.listbox1.selected( 1 ) // do something endifTips:
Sample Form:
The combobox control can be quite useful for a lot of different situations, and can be automatically used if you set up a field in a table with the lookupSQL property.
The field object's lookupSQL property can be handy -- it gives you the ability to do an automatic lookup on another table (and even the same table, but that can get very strange and is not recommended). Briefly, you can set this as a custom property of the field you wish to lookup in another table, or you can set it in the DataModule or Query object in the query's onOpen (or canOpen) event.For a lookupSQL to work properly, you must have a key value that is stored in the main table (the one you are working in) that matches a key value in the lookup table. The lookup table would ideally have at least one other field.
What you are doing is showing the user the value of the second field (or a calculated field -- we won't get into that here) in a combobox, but STORING IN THE FIELD the key value that matches what appears in the combobox.
An example of this would be something like the following: A customer table stores state ids (for US states), but when you look at a form that displays the customers, the combobox shows the full name of the state. The actual value stored in the customer table would be the state id, not the full name of the state, but what the user would see is the full name of the state.
NOTE: Do NOT set a DataSource for a combobox that is created by the software for use with a lookupSQL field -- the datasource is created automatically for you, and anything you do will be overridden.
More details on this can be found in online help.
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).
NOTE: See above in the lookupSQL insert -- if you are using lookupSQL with a combobox (default behavior) then do NOT set a datasource ...
Sample Form:
HTML: It has been noted that the text control does not completely evaluate all HTML tags the way you might expect. This is true. The only reason this ability exists at all is that dBASE and (the no-longer supported) IntraBuilder products (from Borland) share some of the same codebase. IntraBuilder was designed to put data out on the web, and so the text control is able to handle some HTML in the designer. You can insert any standard HTML tags into a text control. They may not evaluate the way you would expect, UNLESS you use the "Save as HTML" option in the form designer. If you then view the HTML that is output in a standard browser, you should see EXACTLY what you expect.Tags that do not work quite properly in dBASE include:
- <BR> -- this is the line break tag -- the only real problem with this one is that you must include a space after the tag -- and it will act properly. Otherwise you may get some really unusual effects.
- <P> -- the paragraph tag -- the difference between this and the <BR> tag is that the paragraph, in standard HTML, adds a blank line. This does not appear in dBASE. As noted above, if you stream this out to HTML, it works properly in any browser ...
- Evaluation of special characters using the ampersand (&) character -- this includes any of the upper ANSI characters, such as ö (o with an umlaut). To use those, you need to have a handy-dandy ANSI chart available so you can look them up, and enter them using the numeric keypad on your keyboard. The character shown here (o with an umlaut) is ANSI 0246, so try holding the ALT key and using the numeric keypad, type 0246 and then let go of the key ... (if your language driver is set to an ANSI driver, this will work as advertised -- if your language driver is an ASCII (DOS) driver, the characters are mapped differently ... check an ASCII chart).
The reason the ampersand does not work the way you expect is that dBASE still allows the use of the ampersand to work to create an accelerator keystroke for a control (see below -- prefixEnable). If you want to display an ampersand in your text control, you will either want to use two ampersands (&&) or make sure that the prefixEnable property is set to false.
There are undoubtedly more that do not work as expected (such as HTML table tags). dBASE is not meant to be a complete HTML engine ...
As a side note, if you wish to generate HTML pages you may wish to use the custom class in the dUFLP library: HTMLCLAS.CC -- you can use it to generate HTML "on the fly" ... (it has most of the standard HTML 3.2 tags and attributes)
Images and Text Controls
In addition to the above, it is possible to place an image in the HTML used by the text control. One of the developers of dBASE notes the following:
"There is a dialog in design mode ( Format | Define Image ) which helps in constructing the HTML tag for an image when doing an edit-in-place of a Text object. (With a Text object on the design surface, click to select getting black handles, click again to get white handles.)"Note that when designing the content of the Text object, you can drag and drop an image file from the Navigator into the Text object as another way to create the reference. (This drag and drop will also work to add an image to the field text displayed in a Memo Editor.) Once the image is in the text, you can select it to modify the HTML tag using the dialog or to drag & drop it elsewhere in the text changing its anchor point.
"Each image referenced by the HTML tag has to be in a separate file (since nothing was done to extend HTML syntax to provide for alternative image sources such as a table field)." -- BJ
An interesting thing to note is that if you have the patience to work out the details, you can actually create (or use) an image as an imageMap, kind of like some of the HTML pages you see on the web. The hard part is knowing exactly where the mouse is on the image at any one point. But, with a bit of work, the onLeftMouseDown and/or onLeftMouseUp events can be used to work like one of these imagesMaps.
SHAPE SAMPLE FORM:
RECTANGLE SAMPLE FORM:
In the onClick event, you could check to see if the pushbutton's value property is false, and set the text to read "Parent" and the form's rowset to point to the parent rowset. If the value property is true, you could set the text to read "Child" and the form's rowset to point to the child rowset. With some imagination, I am sure you could come up with various other ideas as well.
The TREEVW2.WFM that comes with this HOW TO document uses a lot of pushbuttons that use this and the value properties ...
Pushbuttons are used everywhere in forms, and in the sample forms for this "How To". They are, for the most part pretty straight-forward -- the one thing not obvious is the use of the toggle and value properties, but if you look at some of the sample forms that come with this document, you will see these in action ...
form.pageNo := this.curSelor it could be used on a form that had a large list of names to go to the first name that matched the letter (this would assume a tabbox with a lot of letters for the tabs).
TABBOX SAMPLE FORM:
The progress bar only really has three properties you need to worry about here. rangeMax, rangeMin and value. You set the rangeMax to the highest value you want it to be at (for example, with a table, you might use the rowset's count() method), the rangeMin would be the lowest value (zero or 1 is good), and as you progress through whatever you are doing, you set the value property. You could simply increment it:
form.progress1.value++Which would add one to the value property each time you went through some processing loop.
Currently the progress bar uses the form titlebar color which is defined by your Windows color schemes. There is no way to change the color of the progress bar programmatically (there is no property) at this time.
The progress bar also has no "text" abilities -- you cannot define text to be displayed on, or around the progress bar to show your current state. While this would be useful, you could simply add a text control under (or even on) the progress bar that was updated in whatever code you were using to update the progress bar.
It is possible, when using some methods associated with the database object (packTable, reindex ...), to update a progress bar -- see the onProgress event of the session class in online help. This update is not very good, however ... it only updates every approximately 30% as these methods process.
SAMPLE FORM:
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 (see the Knowledgebase article on working with Custom Classes). You can probably determine other reasons to use these. They are quite handy ...
There are no specific properties that really need mentioning. You may want to set the transparent property to true, so that the form's colors or background image shows through, and working with the borders and such can provide some good effects, but frankly, the point of the container is the "non-visible" parts ...
Containers can make your references to your objects a bit confusing, until you get used to working with them. If you have a container that has two objects on it, and you wish to refer to the second object from some code associated with the first, you cannot simply refer to the second object as:
form.object2The second object is contained in the container, so your reference becomes:
form.container1.object2And of course, if you have a container that has another container, and you wish to reference objects from one container to another, the syntax gets even more fun.Another way of maneuvering through the object hierarchy is to use the "parent" property:
this.parent.object2It takes a bit of getting used to the syntax used with containers, but once you do you will truly understand the power of the Object-Oriented Programming Language in dBL.
One suggestion -- I have found that it is VERY easy to forget where you are in the object hierarchy -- in most cases, except for the form and query objects (and if the query is contained in a datamodule, then the datamodule is the one without), there is a parent.
While in the designer, you can use the inspector to find where you are in the object hierarchy -- the combobox at the top of the inspector shows you a layout that can be quite handy for finding what is the parent of what object ...
You can determine where you are in your code while you are testing it, by using things like:
msgbox( "Parent of this is: "+this.parent.className,; "Where am I?" )If that value is what you are looking for, then you know where you are. If it is not, then you may need to stuff another ".parent" into the mix, and try again. It all does make sense after awhile, really!If you know where you are in the container structure, you can shorten a container reference:
c = form.container1.container2 ? c.text1.textCustom Containers
There are some interesting problems in the way that custom containers work -- if you design a custom container under a specific metric, and all the controls are designed for that metric -- if you attempt to use the container on a form with a different metric, it is probably not going to display properly.There is no really good workaround for this at this time. I tend to just use the default metric for forms (and controls created for my forms) of Character.
One workaround that is not optimal (because it changes a form's metric) is to set the form's metric in the container's onDesignOpen:
this.parent.metric := 6 // PixelsOf course the problem is that this might mess with something else that the developer is doing.
Controls placed on a notebook control will have a pageNo property -- it will refer to the individual page of the notebook that the control will appear on. If you want the control to appear on all pages of the notebook, set the pageNo property to zero.
NOTEBOOK SAMPLE FORM:
SLIDER SAMPLE:
form.reportViewer1.ref.startPage := 5 form.reportViewer1.ref.endPage := 5 form.reportViewer1.ref.render()
NOTE: The dUFLP library (noted at the end of this document) has a report preview form that can be used to see the reportViewer control in action (Preview.wfm -- make sure to understand it fully that you open it in the source editor and read the comment header at the beginning of the document).
You might want to use the treeview to create your own dialog boxes similar to what you get when you call the getDirectory() dialog.
In the designer, to add treeItems, right click on the treeView control. The first selection in the popup that appears is "New Item". If you select that, a new treeItem is placed onto the tree. You can add treeItems "under" a specific item by making sure it is highlighted. This takes a bit of practice, but once you have done it a few times it starts to make sense.
You can programmatically add treeItems to a treeView control as well. See the discussion below after the listing of properties, events and methods.
NOTE: It is not a good idea to mix how you add treeItems to the treeView control -- either add them programmatically or add them in the designer. If you add top level treeItems in the designer, and then add lower- level items programmatically, you may find that the plus sign (used to denote children treeItems) will not appear for the top level items -- which means you double click on them to show the next level of treeItem. (Discovered by Gary White)
The following properties, events and methods are important to the treeView control:
Note -- if you edit a treeItem's text and call sortChildren(), the onEditLabel method is grabbing the value of the text before the sort occurs, and the item may not be sorted properly (it's sorting the old value). See the form TREEVIEW.WFM for examples of how to get them to sort correctly.
The TreeItem control is a child control of the treeView control. This is what is referred to in all of the above, when discussing "children" and so on. The treeItem is one individual entry in the treeView. Note: You cannot create a treeItem without a treeView object as its parent.
Here is a short discussion of using the treeView and treeItem controls by coding them. This code and discussion is by Gary White (credit where credit is due department):
To create a treeView control on a form:
form.treeView1 = new treeView( form ) with ( treeView1 ) height = 10 left = 2 top = 1 width = 20 endwithYou would then add top level items to it with the code:
form.treeView1.treeItem1 = new treeItem( form.treeView1 ) with ( form.treeView1.treeItem1 ) text = "First top level item" endwithWorth noting is the code in the first line:
new treeItem( form.treeView1 )The code inside the parentheses is the parent of this treeItem. So, if you wanted to place a "child" item below this treeItem, you would use something like this:
form.treeView1.treeItem1.treeItem1 = ; new treeItem(form.treeView1.treeItem1) with (this.treeView1.treeItem1.treeItem1) text = "TreeItemChild1" endwithThere is a small bug in the VdB7.01 treeView implementation. Setting the allowEditTree property to false should prevent the user from adding or deleting items from a treeView object. Unfortunately, if the user presses the delete key, this property is not respected and the currently selected item will be deleted. One possible work-around is to use the onGotFocus and onLostFocus events to disable, or enable the delete key:
onGotFocus = {;on key label del ?? chr(7)} onLostFocus = {;on key label del}The properties, events and methods of the treeView and treeItem classes are explained in more detail than in this document in dBASE's online help file. Most are similar to those of other controls. Events are all handled in the treeView class, while properties and methods are divided between the treeView and treeItem. You'll often need to look back and forth between the two objects to accomplish what you may want.
If you wish to use the treeView object for editing table data, it is imperative that you use a unique key value to name the treeItem, so that you can find the proper record to edit. The treeView does not automatically navigate within a table. You can use the treeView's onChange event to manually navigate to the proper record. So, if you have an autoIncrement field as a unique key value in a table (say as an AcctNo field), but you wanted to display the Name field in the treeView, you would add sub-items to the treeItem like this:
function form_onOpen local t, oItem t = this.treeView1.treeItem1 this.rowset.first() do while not this.rowset.endofset oItem = new treeItem( t, str(this.rowset.fields["AcctNo"].value ) ) oItem.text = this.rowset.fields["Name"].value this.rowset.next() enddo t.sortChildren() returnThen, the treeView's onChange event might look like this:
function treeView1_onChange form.rowset.findkey( val( this.selected.name ) ) returnOf course, editing a treeItem's text does nothing to change table data. You could use the onEditLabel to post the changes:
function treeView1_onEditLabel(text) if form.rowset.fields["AcctNo"].value == val( this.selected.name ) form.rowset.fields["Name"].value = text form.rowset.refreshControls() this.treeItem1.sortChildren() form.refresh() endif returnYou can see a more complete implementation of the TreeView in the GetADir.cc in the dUFLP (available in the Knowledgebase).
TREEVIEW SAMPLE FORMS:
The only way to truly understand some of these controls is to try to work with them, look at examples that others have written, and ask for assistance on the dBASE newsgroups.
A lot of custom controls are available in the dBASE Users' Function Library Project (dUFLP) in the Knowledgebase.
Note that this library is updated on an irregular basis, so you may want to check back periodically ...
Special Thanks: to Gary White for the patience he has in helping me with these HOW TO documents. His editing and coding advice has been invaluable, and should make this that much easier for you to read and understand.
Also thanks to Todd Kreuter, Bob Rimmington and Phillip Hansen for going over this with a fine-tooth comb and also giving some suggestions ...
DISCLAIMER: the author is an employee of dBASE, Inc., but has written this on his own time. If you have questions regarding this HOW TO document, or about dBASE you can communicate directly with the author and dBVIPS in the appropriate newsgroups on the internet.HOW TO 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 TO 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: CONTROLS.HTM -- January, 2004 -- KJM