A new Mover class
by Jean-Pierre Martel, Editor of The dBASE Developers Bulletin

Background

A mover is collection of objects that allows you to select some items from a list. We call it mover because the user can move items from one place to another. In other words, from a list of available choices to a list of selected items. dBASE developers have been widely exposed to movers in recent years. In the March/April 1995 issue of the dBASE Advisor, Ken Mayer presented the first Mover form that I can recall. His dBASE User’s Function Library Project (dUFLP) contains a `Mover.cc` dated from October 1998. In July 1998, Dan Howard made an ItemMover class, and in May 1999, Gary White published a Mover Form in the dBASE Programming newsgroup. The Custom class to be discussed is based on the latter Mover form in which Gary White made the code and I designed the interface.

Introduction

The main purpose of this article is to introduce dBASE developers with a new Mover class through its main components. These are: making custom classes, creating and transforming arrays, showing examples of real-world programming, and challenging our programming habits on the behavior of controls in forms. The overall scope of the article is very broad since it covers a variety of subjects of interest to anybody wanting to understand the intricacies of dBASE.

How to make a custom class

A custom class can be created in three steps. First, open an empty form in the Form Designer, drop a container and add the various objects needed. Secondly, connect the code to the controls so that your form will behave the way you want. The last step would be to transform your form into a custom container class. To do this, open the Form Designer again, select your container (yes, only your container) and from the File menu, select Save as Custom…

Sometimes the result is not as expected. Therefore, I will use a more complicated way to create this custom class to prevent unexpected results. The first two steps are similar, except that we won’t create a container. The objects will be dropped directly on the form. In our case, this base form is called `Ancestor.wfm`. The third step would be to transform this form into a custom container class. To do this, load your form’s code into the Editor using the Command window (`modify command Ancestor.wfm`) and delete the lines similar to these ones…

| `** END HEADER -- do not remove this line` `//` `// Generated on 15/06/1999` `//` `parameter bModal` `local f` `f = new AncestorForm()` `if (bModal)` `f.mdi = false //ensure not MDI` `f.readModal()` `else` `f.open()` `endif` `class AncestorForm of FORM` `with (this)` ... and replace them with these two lines: `CLASS MG_Mover(ParentObj) of CONTAINER(ParentObj) CUSTOM` `with(this)`

Also take out from the (ex-) Ancestor form constructor code any property that can’t apply to a container (`autocenter`,`left`,`maximize`,`metric`,`top` and so on) but keep at least the two ones that must apply (`height` and `width`). Save the code under a name that ends with the suffix`.cc` (e.g.,`my_class.cc`) and you’ve just created a new custom class.

If you make a custom class including a Grid, don’t place the grid inside a container in a custom class: dBASE will become unstable, probably because of a bug in the Grid class. In this case, it would be wiser to make a custom form class. The form should have a container holding all the other objects except the grid. This procedure is valid for VdB 7.01, but could be superfluous in future versions of dBASE.

What’s your name?

Coming back to the specific cc I am doing, we now face a problem: how to call the objects in this cc once it is dropped on a form. In the original form, the first listbox was called `Form.Listbox1`. When the first MG_Mover is dropped on a form, this listbox becomes `Form.MG_Mover1.Listbox1`. If we drop another copy of this cc, the equivalent listbox will be named `Form.MG_Mover2.Listbox1`. This presents a problem when we want dBASE to move an item from one listbox to the other. How can the code be written when we don’t even know how these objects are called? The answer? With the help of two magic words :`This` and `Parent`.

For example, if we click a pushbutton, in its `onClick()` function,`this` designates the button,`this.parent` means the container, listbox1 is called `this.parent.listbox1`, and the form is `this.parent.parent` (or simply `form`). On the other hand, in the `onOpen()` function of the cc itself,`this` becomes the container, any object inside the container will be called `this.object_name` and the form is still called `form`.

Genealogy of the MG_Mover class
Parent | The Form
Parent | MG_Mover (a container)
Objects | Listbox1 | Listbox2 | P_Plus | P_All | P_Minus | P_None | P_OK | P_Cancel | Rectangle1 | T_Possible | T_Final | T_Message

As one can be someone’s parent and someone else’s son or daughter, the same object could be called `this`,`this.parent` or something else, according to the context.

A few precautions

Once every object is named properly, we will add few details to the custom class. First, this cc was conceived in pixels. If you drop this on a form whose `metric` is `Char`, the MG_Mover becomes huge. So we added at line 003 (this numbering system refers to the one in the MG_Mover code on-line)`parent.metric := 6 // Pixels` to change on the fly the `form.metric`. The developer will still be able to change it back to anything else after, without affecting the MG_Mover already on the form.

Secondly, the `listbox.dataSource` needs to be populated by an array. Without it, the MG_Mover is dead. In case the developer forgot to create that array, two precautions were taken. At design time, when the cc is dropped on the form in the Form designer, the listbox1 will be populated with three items — refer to line 027 in the source code:`` `dataSource := 'ARRAY {"Item No. 1","Item No. 2","Item No. 3"}'` (in the listbox1 constructor code). Also, when the form is opened, the `onOpen()` method will check if the array exists: if not, nine items will be created on the fly by default. This job is handled by the following code:

| `247 if type("this.a_Items") # "A" // if the array doesn't exist` `248 this.a_Items=new array() // create it` `249 For i = 1 to 9` `250 this.a_Items.add("Item No. " + i) // populate it` `251 Next` `252 else` `253 this.a_items.sort() // if the array exists, sort it` `254 endif` `255 this.available = new array( this.a_Items.size ) // create another array the same size as a_Items` `256 acopy( this.a_Items, this.available ) // copy a_Items' elements in it`

Thirdly, the container has a border in the Form designer (line 004), to show its limits clearly. When the form is run, this border will disappear (line 246) and the mover will harmoniously be a part of your form. Also, the cc will move to the bottom left corner of your form (line 007). I am not sure if that it is such a good idea. You can always change this anchor property to whatever you like (change it in the cc to change the default, or in the form to affect that form only).

The Assignment-only operator

Some of you may want to know why I used`:=` instead of`=` in most of the places in the mover code. This operator is particularly useful when assigning values to properties. If you inadvertently misspell the name of the property with the`=` operator, a new property is created; your code will run without error, but it will not behave as you intended. In order to prevent the creation of a variable or property if it doesn’t exist, it is recommended to use the assignment-only`:=` operator. By using the`:=` operator, if the property (or variable) does not exist, an error occurs (this paragraph is taken from the dBASE on-line help).

Welcome to the world of arrays

The MG_Mover uses arrays. An array is an ordered group of memory variables called elements. Each element can store one value of any valid data type. In the `onOpen()` function of our custom container object, three arrays are created. The first one,`` `a_Items` contains the original data. It will be copied to`` `available`: selected items will be subtracted from this second array. If the user wants to reset the choices available,`` `a_Items` will still hold the original information and be able to pass it to`` `available`. The third array, `chosen,` is the list of the selected items.

| `248 this.a_Items=new array()` `255 this.available = new array( this.a_Items.size )` `259 this.chosen = new array()`

If you look carefully, we are not creating`` `Whatever=new array()`, but`` `THIS.whatever=new array()`. This means that these arrays are created as a property of an object, where`` `this` is the container. Inside the form, with the help of the words`` `this` and`` `parent`, these arrays can be accessed from anywhere as if they were public variables. They can’t be modified by anyone else but you if you are on a network; so they don’t have the drawbacks of public variables. Moreover, you don’t have to release these arrays when you don’t need them anymore. dBASE will release all its objects and their properties when the form will be released, automatically!

To create arrays

There are three ways to create arrays. Line 027 is showing the first way. In the constructor code,`` `dataSource := 'ARRAY {"Item No. 1","Item No. 2","Item No. 3"}'` creates a single dimension array. In this case, the three elements are separated by commas inside the braces.

| `160 this.chosen=new array()`

Another way to create an array is with the “new” operator. Line 160 creates a new instance of the array class called `chosen` (we would write`` `declare this.chosen[0]` in VdB 5). When an array already exists, we can create another array by making a copy of the first one, for example`` `this.available = this.a_Items`. Here, the array`` `available` is created from`` `a_Items`. The third way to create an array is by combining the following two instructions:

| `158 this.available = new array( this.a_Items.size )` `159 acopy( this.a_Items, this.available )`

In this case, the `new` operator creates a new instance of the array class, the same size as`` `this.a_Items` (the same number of elements). If you could see the value of each element in the listbox just after this line is executed, dBASE would display `false`: in fact, no value was assigned to them. These values are attributed next by `aCopy()`. Right now,`aCopy()` is not a method of the array class; it is a function. Its first parameter is the source array (`this.a_Items`) and its second parameter is the destination array (`this.available`). Since the first is exactly the same size as the second, all its elements will bear the same value as the elements in the second.

To add elements to an array

| `248 this.a_Items=new array()` `249 For i = 1 to 9` `250 this.a_Items.add("Item No. " + i)` `251 Next`

When an array already exists, we can add elements to it through its `add()` method. This is precisely what line 250 is doing. Here we made a loop (`For/Next`) to create nine elements. Each time dBASE meets `Next`, it bounces back to line 249 and increment the value of `i` until it reaches `10` and gets out of the loop. Actually, the line 250 is doing two things at the same time: it adds a new element, and gives it a value. This value is obtained by concatenation of a string (“Item No. ”) and a number (the value of `i` in the loop), for example “Item No. 7”. Here dBASE does the automatic type conversion which makes the expression simpler. So we don’t have to write:

| `this.a_Items.add("Item No. " + ltrim(str(i)))`

There is no need to sort the elements since they are created in alphabetic order. If we just added an element to an existing array, then we would have to sort the array with the `array.sort()` method, as in line 198 and 203.

To delete elements from an array

| `199 this.available.delete( this.available.scan( aItem[i] ))` `200 this.available.size --`

The `add()` method adds an element to an array; the `delete()` method erases an element. However, the size of the array will stay the same. In other words, dBASE deletes the element, moves all the other ones up to fill the gap but leaves an empty value in the last element. To take it off, you have to resize the array (line 200). We could have written`` `this.available.resize( this.available.size -1 )`, but we’ve chosen`` `this.available.size --` instead, which is shorter and faster. The decrement operator`` `––` takes a variable or property and decreases its value by one (on the contrary, the increment operator`` `++` increases the value by one — as in line 212— where`` `oTo.cursel ++` means that the cursor is moved to the next item in the listbox).

The dataSource property

Now we have to remember that listboxes are only dataSourced to arrays, not dataLinked. That means that listboxes don’t display live data but only a picture of the array the last time the listbox was dataSourced. Each time the user wants to move an item, dBASE needs to know which items were selected, find them in the array, delete the elements, resize the array, add them to the other array, sort it and datasource the two listboxes anew to display the changes. Thanks God, dBASE does that at blazing speed!

The code executed when the user click the button to move an item from left to right is this one:

| `077 onClick := {;this.parent.MoveItem(this.parent.listbox1.selected(),true)}`

For the pushbutton,`This.parent` is the container. So when we push this button, we invoke the container’s `MoveItem()` function. This function needs two parameters (denoted by two strings separated by a comma inside the parenthesis). The first of these parameters is `this.parent.listbox1.selected()`, and the second is`` `true`. We will use the latter to inform dBASE from which listbox to which listbox it has to move the item. When the `multiple` property of the listbox is `true` (it is so),`listbox1.selected()` returns an array containing the currently selected items, one element per selection. We will need that array in the `MoveItem()` function:

| `191 Function MoveItem( aItem, bAdd ) // if you allow multiple selection` `192 if not empty( aItem)` `193 oFrom = iif( bAdd, this.listbox1, this.listbox2 )` `194 oTo = iif( bAdd, this.listbox2, this.listbox1 )` `195 for i = 1 to aItem.size` `196 if bAdd` `197 this.chosen.add( aItem[i] )` `198 this.chosen.sort()` `199 this.available.delete( this.available.scan( aItem[i] ))` `200 this.available.size --` `201 else` `202 this.available.add( aItem[i] )` `203 this.available.sort()` `204 this.chosen.delete( this.chosen.scan( aItem[i] ))` `205 this.chosen.size --` `206 endif` `207 endfor` `208 oFrom.datasource := oFrom.datasource` `209 oTo.datasource := oTo.datasource` `210 oTo.cursel := 1` `211 do while oTo.value # aItem[aItem.size]` `212 oTo.cursel ++` `213 enddo` `214 this.countItems()` `215 endif` `216 return`

If you look in the MG_Mover code, two versions of this function are provided: one if you just have to click once on an item to move it, and the second version, reproduced here, where a multiple selection is allowed. These two parameters are called `aItem` and `bAdd`: the first one corresponds to the mover's `listbox1.selected()`, while the second to `true` . If the array `listbox1.selected()` is not empty (line 192), and when`` `bAdd` is `true` (lines 193 and 194,`` `oFrom` is the container’s listbox 1 and `oTo` is the container's listbox 2. Since this is the case, only lines 197 to 200 will be executed in the `For/Next` loop.

In this loop, for the number of items selected (or for`` `aItem.size`), we will add each element of the array returned by`` `listbox1.selected()` to another array called`` `chosen` (from which listbox 2 was dataSourced). The `chosen` array is then sorted at line 197 and the listbox 2 is dataSourced anew at line 209.

Meanwhile, we have to do some maintenance. If the selected item(s) are already added to`` `chosen` array, they are not taken off the`` `available` array (from which listbox 1 was dataSourced). To find where the selected items are in the latter array, we will use the `array.scan()` method (line 199): its only parameter,`aItem[i]` means the “`i`”th item (for example the 7th item) in the array returned by `listbox1.selected()`. So scan() will find that exact element and once found, it will then be deleted in the`` `available` array (line 199 also). This array will then be resized (line 200) and this change will be reflected in`` `listbox1` (line 208).

The Custom class behavior

One of the buzzwords of the eighties was “artificial intelligence”. The concept went nowhere because at the time desktop computers didn’t have the processing power to analyze very complex algorithms quickly. Now that personal computers can do millions of instructions per seconds, our programs should be expected to behave more intelligently. For example if the user should not make a choice, why this choice should still seem to be available to him/her?

In the MG_Mover, buttons are enabled/disabled according to the context. When the final list is empty, the OK button is disabled, so are any button whose purpose is to take out an item from it. Moreover, the MG_Mover counts the number of choices selected, which is handy when a lot of them are available.

All the objects’ complex behaviour is imposed by just seven lines of code in the `CountItems()` function:

| `226 this.P_Minus.enabled := this.Listbox2.count() > 0` `227 this.Listbox2.mousePointer := iif(this.P_Minus.enabled = true, 13, 12)` `228 this.P_None.enabled := this.Listbox2.count() > 0` `229 this.P_OK.enabled := this.Listbox2.count() > 0` `230 this.P_Plus.enabled := this.Listbox1.count() > 0` `231 this.Listbox1.mousePointer := iif(this.P_Plus.enabled = true, 13, 12)` `232 this.P_All.enabled := this.Listbox1.count() > 0`

Line 226 says: when the container’s listbox is not empty (when `this.Listbox2.count()>0` is `true`), the button P_Minus is enabled. The next line uses the `IIF` shortcut to the `if/else/endif` programming construct. It means: if `this.P_Minus.enabled=true` then `this.Listbox2.mousePointer := 13 // an hand` or else this pointer`:=12 // a "No" circle`.

When the cursor is placed over a listbox, it changes from an arrow to a “No” circle (on an empty listbox) or to an hand (over a filled listbox, as if the cursor would be over an URL). As the Internet is getting more and more popular, it influences our paradigms and challenges our habits as developers. One of them is the double-clicking.

While Gary White was creating the code for this custom class, he and I had the following discussion about this. I asked him:

— Why should the user click on one or more items to build a list, then click a button to move them when it would be so easy to move each item as soon as it is single-clicked?
— Experienced Windows users expect certain behaviors to be consistent. If changes improve the application and make it easier to use, users will gladly accept those changes. If they are confusing and make using the app more difficult, they will not. The single click move and the inability to select multiple items are not things that would make me happy if I were the user of the application.
— Gary, double-clicking was a necessity when the user had to select an item first, then apply a command to it. Today, this double procedure is often useless. It was imposed on us when we learned how to use computers and now we, as developers, are imposing that on our own users. We are like battered children who, when they become adults, can’t escape but beat our own children.
— Jean-Pierre, lets be pragmatics. Assume you have a list of 50 items and the user wants the first 40 of them. With `multiple=false`, then can either click 40 times, or they can select all and then click 10 times on the other direction. With `multiple=true`, they click the first one, scroll down to the 40th one, shift-click and click the button.
— Well, yes but if there is few choices in the listbox, it is easier to use a single-click moving.
— How do you know in advance that the listbox will offer few choices?
— Err, well, I don’t know but…
— ...and if you use single-click moving, the use of double-clicking to move everything the other side becomes risky as a double-click can be interpreted as two consecutive single clicks.
— We just have to right-click to move all. Anyway, I must say that I am not as convinced as I was about the opportunity of single-click moving.
— Then why not giving the choice to developers? You may want to include a note in your code about that.
— Good idea. That is what I will do. Thanks.

So the MG_Mover allows multiple selection by default. If you want single-click moving, the header provide the instructions to allow it.

A new tool in your toolbox

If you forgot to check the checkbox shown in the first illustration of this article (“Place in Component Palette”), or if you used the more complicated way to create a custom class, you may want to make the custom class available each time you design a new form. If so, follow these steps. First open the Form designer. From the File menu, select Set Up Custom Components…

Then click the Add button, go in the directory where you put the MG_Mover, select it and click the Open button: you then come back to the same dialog box as above. This time push the OK button. That’s all. From now on you just have to right-click on any form to open the Component Palette (below), and drag’n drop the MG_Mover on a form. All its objects will appear instantly on your form.

The only thing left for you to do is to write an `onClick()` function for the OK pushbutton. In the code you will make, it will be important to transfer the data displayed in listbox2 (from the pushbutton point of view, it will be the elements in `this.parent.chosen` array) to your application before closing the form since once the form is released from memory, this array is destroyed automatically.

Shortcomings

When this Custom class is dropped on a form, the Form designer will stream a bunch of unnecessary code for each of the pushbuttons. For example:

| `with (this.MG_MOVER1.P_PLUS)` `value = false` `endwith`

That can be safely removed.

Secondly, lines 22 and 38 were inactivated at the last moment before publication because of a bug discovered by our proof-reader, Mr. Fabian Cevallos (thanks).

Third, a listbox is limited to display about 4096 items. So if your planning to use the MG_Mover to invite all your ex-girlfriends to your wedding, you could have problems, at least with dBASE.

Moreover, for items whose names contain an ampersand, these names will be displayed in any listbox with an underline instead of the ampersand. For example, “TopDrug Sinus & Cold” will become “TopDrug Sinus _Cold”. The solution could be to use “and” instead of an ampersand. If you modify the code of the MG_Mover to dataSource one listbox to a field (instead of an array), another solution (discovered by David Sanderson) would be to enclose the field in single quotes instead of double quotes. For example:`dataSource = form.rowset.fields['FIELD_NAME']`. A third solution (not very practical) is to replace any “&” with “&&”: they will be displayed as single ampersand in your listboxes but if you use this information elsewhere, it will display “&&” in entryfields and grids.

Lastly, when you drop a MG_Mover on a form, the `form.metric` is changed to pixels. Personally, I prefer this metric for anything but reports (where centimeters is my favorite). But there is a known bug with pixels: if you want to place a grid that have a column dedicated to a logical field, the checkmarks (ü) will not be visible unless the `form.metric` is `char` (this is a bug reported by Todd Kreuter). Frankly, to place a mover and a grid on the same form seems to me a wee bit heavy but if you want to mix them anyway, don’t forget to set back the `form.metric` to `char` if the grid displays a logical field.

Conclusion

This article discussed four major subjects:

1. to show how to make a custom class;
2. to review the main methods for creating and transforming arrays;
3. to show examples of real-world programming to new dBASE developers;
4. to challenge our programming habits regarding the behavior of controls in forms.

We hope that this article enlightened your knowledge and that this product will be useful to you. The MG_Mover is offered as a finish product, ready to be use in professional applications.

Acknowledgements

Finally, I would like to thank Gary White for his generosity since he wrote more than 80% of the code. Moreover I would like to thank Dan Howard for his guidance and his suggestions about this text.

To download the code of the MG_Mover, click here
(it’s a 5Kb zipped file)