DrDialog, or: How I learned to stop worrying and love REXX - Part 6

By Thomas Klein

Sorry for skipping an issue in our series last month, but here we are: The container control.

Now, as I told you before, this control is very special. It contains a wealth of GUI comfort and is incredibly versatile and useful, both visually "appealing" and providing comfortable functionality for the end user.

Unfortunately, this requires a lot of information, data and control to make it do what you want. Finding a good start into the bunch of methods, events and commands that is supported and/or required is not that easy at first sight. Today, I'll try to actually give you a structured guide to what is contained in the help files. Along with this reading, you'll find a sample application that gives you a first glance on most of the tasks when dealing with a container control.

(This sample application is also used to provide most of the examples and screenshots in this article.)

Introducing the container control
Basically, from a end-users point of view, the container control represents a sort of "folder". It can be used to view entries in detailed view, multi-column view or even as a hierarchical 'tree'-type structure (and more). The special thing about it is, that you (as the developer) have complete control over all its features: What it shows, what it does and how it reacts to user interaction. The drawback about this on the other hand is, that you must deal with all that, as the container control does nothing on its own. ;)

It's not automatic like e.g. pointing it to a directory and here we go - nope. In such cases, the programmer (you) would be forced to retrieve everything that's contained in a directory "manually" and feed it into the container control.

To give you an overview on what can be done with the container control, we'll figure out some kind of small address manager (or buddy list if you want). It contains several entries that are made up of name, an associated bitmap and additional data like phone number and email-address.The different types of view that can be achieved with the container control are...

TEXT



Entries are represented by their associated label only, in a single column

COLUMN



Same as TEXT but multiple columns

NAME



Entries are represented by their associated label along with their associated icon on the left, in a single column

FLOWEDNAME



Same as NAME but multiple columns

BITMAP



Like "icon view" in workplace folders: Entries are represented by their bitmap with their name centred below it. A 'grid' algorithm is used to arrange the entries within the container.

DETAIL



Like "details view" in workplace folders: Entries are represented by a row in a table-like view.

This view can be made up of one or more columns in either a single window or a two-part view that shows a movable, vertical split bar to size the window parts.

OUTLINE



Entries are represented like in NAME, but in addition it shows a parent/child relationship of entries by indenting child entries to the right below their respective parent entries. Parent entries will show leading bitmaps that can be used to expand or collapse the view to show the parents child entries (if any).

HIERARCHY



Like "tree view" in workplace folders and actually the same like OUTLINE but with lines leading from a parent entry to its according child entries.

To give you a little "appetizer" on dealing with container controls: Changing from one of the views shown above to another just takes one line of code - a single statement...;) Of course, there's some work to be done prior to it - like actually "filling" the container.

The amount of work you need to face is depending on the type(s) of view you're going to provide to the end user: A "simple" view type like BITMAP requires only a single and easy statement to add an entry to a container... whereas - as you might imagine - the DETAIL view requires a lot more data because of its extended format and the amount of data it is capable to show. The same is true for the hierarchical view types HIERARCHY and OUTLINE. While they still only display bitmap and text of an entry, there is need for additional control of who's the parent of who. But once you provided the necessary data it's fun to work with containers.

But before going into details on how to add entries to a container, let's talk about some general information...

Basics of end-using a container control
Before drag-dropping a container control onto your dialog, you should be aware of the bunch of work that you might be faced to, according to the way the user is intended to use it... to give you an impression of what I'm talking about: Imagine an address book application that is built upon a detail view container control like the one shown above.

What is going to happen if a user adds an entry? Quite simple: You pop up a "new entry" dialog and add the new entry to the container afterwards - so far, so good. Now... what happens if the user selects to delete an entry? This might happen due to a context menu (pop-up menu) selection, a "del" keystroke event for the control or whatever process that you need to figure out. In addition, if a column was not defined as read-only (we'll talk about that later) the user is able to change a single column data of an entry by clicking on it while holding down the ALT key. The container control will handle the steps necessary: Change the display "cell" into an entry field, accepting the new data - but regardless of what is happening in the user interface YOU need to provide the steps that take care of storing the new data into your file...

To sum up: You should know the capabilities of the control in matters of usability and draw some kind of user interaction guidelines from it: How do you want the user to add, remove or edit entries? Okay - here we go with events and methods (functions, statements) for a container control:

Events for a container control
As a container control contains (as its name implies) multiple objects, it might be interesting to know the actual object that was e.g. changed or selected. Such information indeed exists, but it must me retrieved first using a special function named EventData. The syntax for EventData is call EventData (stem) where stem is the NAME of a stem variable. If you want to receive the EventData information into a stem named "myvar", you must specify call EventData ("myvar") Yes - including the quotes!

Now, if you don't know stems you might wonder what that's about. Let me put it this way: A stem is a sort of one-dimensional array (a list) of variables that all have the same "root" name while being identified through an index number. The notation used to deal with stems is quite easy. First, there's the "root" name (the stem name), then a dot, then either the actual index of an entry or a variable that holds the index' value. If you want to create a stem named "myvar" that is made up of three entries, you would simply do: myvar.1 = "first" myvar.2 = "second" myvar.3 = "third" Then, you would put the upper bound of that array (the number of entries) into the entry 0: myvar.0 = 3 This last statement is not mandatory if you set up stems manually, as you know how many entries are contained in your stem, but it's a common method in REXX - even the system functions that are e.g. used to search files will use entry 0 to specify the amount of entries a stem is made up of. This way, the programmer can use entry 0 to determine how many entries are actually returned into the stem. This is absolutely required for looping through the entries for example. Anyway - this should be enough to get you a first picture on stems and we'll concentrate back on the EventData function (we'll get back to stems in a later part of the series).

Okay, let's check the EventData syntax again... this is what is stated about it in DrDialog's help: EventData( [stem] ) Returns the list of data items associated with the current event in the stem variable specified by stem. If stem is omitted, it defaults to EVENTDATA.

The number of data items returned is specified by stem.0. Stem.1 through stem.n contain the individual data items associated with the current event. For a description of the data returned by a specific event for a particular control type, refer to the Controls section. [snip] First, note that actually no stem variable needs to be specified. In this case, the system uses a built-in default to provide a stem variable named "EVENTDATA".

Second, as I mentioned above, stem entry 0 is used to specify the actual amount of entries in the stem.

Now there's something else we should make clear before finally taking a look at the events.

While the items (entries) of a list or combo box are referred to by a simple index scheme like 1 = first entry, 2 = second entry and so on, the items in a container use a different system. Each item has a unique "id" that is created by the system when the item is added to the container. These identifiers need neither to be in sequence, nor ordered. Don't try to use them for calculating something. They are actually only meant to identify items within a container and have no significance "outside" the container. In addition, when the same program is run a second time, the same items will show different ids, which makes them completely unusable for e.g. database record identification or any other "static" references... We'll get back on how to deal with that later - this paragraph was meant to get you to know the item ids, so that you're prepared for the events discussion. Note that the examples below are based upon the assumption that no stem variable was specified and thus the "built-in" variable EventData</tt> is used instead.

Of course there's some more events supported by the container control, but those are more "simple" and don't require you to retrieve additional information: A final example on how to use The event data stuff: If you want to react on a changed event, you need to: CALL EVENTDATA say "ID of item is" EVENTDATA.1 say "change occurred for" EVENTDATA.2</tt> CALL EVENTDATA("myvar") say "ID of item is" myvar.1 say "change occurred for" mydata.2</tt> Don't worry, we'll get back on "what's next" later...
 * select the CHANGED event handler of the container control and
 * code the following:
 * or, if you prefer to use a stem variable on your own, code

Okay, great. Now that we know the events for a container... how the heck do we actually put items into it? ;) Here's for the methods (or "functions" if you prefer) - note that there's some more methods that are applicable to other controls as well like visible</tt> and so on. Because of the fact that we already discussed them in the previous part of the series, we'll focus on those who are somehow "special" when dealing with containers:

The VIEW function
Let's start with the VIEW </tt>function. It is used to set up the "look" of your container (remember the screenshots above? That's what it's used for). To set up a container named "mycontainer" as a single-column list of names (labels), you go with call mycontainer.view "Text" Note that you optionally only need to specify the first letter of the view type - thus, if you code down call mycontainer.view "T" will do the same as the statement above. Setting up a view type ideally is done during the INIT</tt> event of the container. In addition, when using the containers INIT</tt> event handler to set up the view, you don't need to specify the controls name, because the current "context" of the program is stuck to the container (because of the event handler), so you can narrow down the whole stuff to call view "T" Now that you know how easy the VIEW</tt> function can be, let's check its entire syntax: oldview = container.view([type], [title], [bitmapwidth, bitmapheight], [expandbitmap, collapsebitmap]) Phew! First, let's recall that such functions can be used to SET something, GET something or do both actions at once. You might use the "function notation" that returns values to GET the current settings of a view. But honestly, this is not what we are interested in at the moment. We rather would like to SET something, so let's use call container.view [type], [title], [bitmapwidth, bitmapheight], [expandbitmap, collapsebitmap] As we already know, the type parameter holds the view type we want to achieve ("text" in our above sample).

The title of the container control ("buddylist" in the screenshots) is a string that you want to put above the actual contents. Note that some special leading characters can be used to format the title:

If no formatting characters are specified, the title will be centered with no separator line. If you want to have a title "This is my first container" written above the contents, have it left-justified and with a separator line, the [title] parameter for the view call will be: "<_This is my first container"</tt>

Okay, the next parameter is [bitmapwidth, bitmapheight]</tt>. This can be used to specify a size, to which all of the item bitmaps of the container will be sized. If this optional parameter is not used, all bitmaps will be shown in their respective size. If 0 (zero) is used for both dimensions, the system bitmap size is used. I think this is in pixel units, as no explicit information is contained in te help files. Don't blame me for not testing it... ;) To make all bitmaps display as 24 by 24 pixels regardless of their actual size, the parameter would look like "24, 24"... as far as I am correct...

Next, we have [expandbitmap, collapsebitmap]</tt>. These optional parameters are used to specify alternate bitmaps used to display the bitmap button contained in a tree view type (outline and hierarchy) which the user can use to expand or collapse (close) individual "branches" of the view - you might know that from a drive folder in tree view, when you can click on a "+" sign to open a subdirectory view... If the parameter is omitted, the default built-in bitmaps (plus and minus sign) will be used.

Bitmaps in DrDialog are specified in two ways - either by a file name like "c:\mybmps\bitmap01.bmp" or by using a special notation to use bitmaps contained in a dll - we'll talk about this later. DrDialog comes with a BITMAP.DLL that holds several bitmaps. To use bitmap number 52 of this dll, you would write "bitmap:#52".

To keep things simple, we don't care about these features at first and just use defaults where possible. A complete VIEW call would look like this for example: call mycontainer.view "Text", "<_This is my first container" As we should now by now, square brackets in syntax notation mean "optional". Thus, if you want to change a containers title without changing the view type, you simply omit the first parameter but not it's comma so that the programming language "parser" knows that you actually OMIT a parameter. In this case, the statement would be call mycontainer.view, "<_This is my first container" Looks a bit strange at first, hm? But if you don't specify the single leading comma, the parser will assume "<_This is my first container" as the first parameter, thus being the view type... that's why the comma is quite important. And here we are: Now you're able to set up views and play with the title formatting. Next, the real important statement you all are waiting for...

The ADD function
...is used to add a new entry into a container. It's a function that returns a value and - of course - can be used in a CALL notation to simply add the entry without caring about whatever it returns, but we should take a look at the return value, because it's the new entry's ID. This ID is used in conjunction with other statements, even with additional add</tt>-calls: When creating hierarchical views (hierarch, outline), it is used to specify the "parent" that you would like to receive child items for example.

If you set up a "simple" type of view that is made up of labels (and optionally bitmaps) only, you might prefer to use call mycontainer.add "New entry" or with a bitmap named "new.bmp" contained in C:\mybmps...: call mycontainer.add "New entry", "C:\mybmps\new.bmp" To achieve more control over the adding process (like obtaining a sort order) we need to take a look at the syntax of the add function (this is my personal version, slightly different from the one mentioned in Drdialog's help): newid = mycontainer.add(label [,bitmap] [,where] [,reference-id] [,data])

I know that where and reference-id are quite "clumsy" both to explain and to understand... let me put it this way: Unless you're dealing with parent/child-relationships in hierarchical view types, you actually don't need to deal with reference-id. Neither do you need the "first", "last" or "next"-operands for the where parameter. If you need to provide a sorted container, simply do the sorting somewhere else, then add the items in the sorted order without having to rely on where and reference-id.

The way I do it is to use an "invisible" list box on my dialog that already comes with built-in sort algorithms in its add</tt> function. Then I simply read the sorted list and make the appropriate entry being added into the container.

However - when it comes to adding new entries after the container is populated you're better off with using the parameters described herein, but we'll talk about that later, as retrieving data about the items in a container requires additional functions... the same is true for "z-order" issues, that you might have read about in the help file. This is not mandatory to talk about now. Let's go on.

Detail view containers
In detail view, we have to deal with two more issues than simply use the view and add function... we need to set up the columns and we need to supply the data that is contained it the "cells" (in a column field of an entry). Both tasks are accomplished by using the SetStem</tt>function. The concept of SetStem</tt> is to set up a collection of data for each column (format or data content), then passing all the information at once. Luckily we already know stems in basic, hm?

As usual, we'll first take a look at the syntax, then strip off the parts we don't need now and look at how to use the rest... call mycontainer.setstem [stemname] [, "[+/-]SELECT"] | [,"[+/-]MARK"] | [,"FORMAT" | 0 | item] This syntax in a more readable way is made up of only two parameters: call mycontainer.setstem [stemname] [,action] [stemname]</tt> Is the name of the stem variable that holds the values for the action to be done. If you don't specify an explicit variable on your own, a stem variable named STEM is expected to hold the values, and this is why the parameter is shown as "optional" (in square brackets).

As for the action to be performed, there are three possible values for it: We won't discuss the <tt>SELECT</tt> and <tt>MARK</tt> stuff now but focus on the third one. To set up a container in detail view, we need to call the <tt>VIEW</tt> statement which will give it a title (or caption if you prefer) as well as actually switch the view mode to "detail". Once this is done, there are three things left to be done to set up the entire container: And this is exactly what setstem is used for in it's third "meaning": will use the values of a stem variable named "formatstem" to set up number and format of columns will use the values of a stem variable named "titlestem" to set up the column titles will use the values of a stem variable named "datastem" to supply the data for each column of the item whose id is contained in a variable named "itemid" (without the brackets of course) So far, so good. Before going into some examples, we need to take a look at how column formats are set up. Each entry in the stem variable will carry the format for a column and this is accomplished by a format string that is made up of a combination of characters with special meaning: To make a column simply display text that is both vertically and horizontally centred, no formatting is needed as this is the default. Whereas, if you want a column to display text left-aligned, being read-only and have a vertical separator on its right edge, the formatting sequence would look like this: "<X!" ...seems like some sort of customized "smiley" at first, but it's very effective! ;)
 * 1) Specify the number of columns and their format
 * 2) Specify the title of each column
 * 3) Add the entries and supply the data to be displayed in the columns
 * 1) <tt>call setstem "formatstem", "FORMAT"</tt>
 * 1) <tt>call setstem "titlestem", 0</tt>
 * 1) <tt>call setstem "datastem", itemid</tt>

Okay, folks. Now that we've gone so far... let's check how to set up a container with detail view. I'll show the statements necessary to produce an output like the one shown in the screenshot at the beginning of this article. Note that lines having a leading sequence of <tt>"/*"</tt> and ending in the reverse way<tt>"*/"</tt> (without the quotes of course) are considered as comments by the Rexx parser. The following sample is based upon the assumption that you provide a container control named "cnt":

Comments for the above code sample:
The reason for including all the comments and writing it all in sequence is that you might copy the code sample and paste it into the "INIT" event handler of your container - given the prerequisite of naming your container "cnt" of course.

The first time I tried to mess with a detail view, there were no bitmaps shown, no matter what I tried. I then found out, that all was my fault - of course... As the second column was intended to show a bitmap, I didn't feel the need to supply the bitmap in the <tt>add</tt> statement, as this bitmap won't show up in detail view anyway. This was wrong. Well, not actually wrong, but it had the side-effect that the container control seemed to conclude that no bitmap was present. To sum it up: If you intend to use bitmaps somewhere in your container - regardless of view type - always specify a bitmap in the <tt>add</tt> statement of an item.

Comments for the code sample file that you can download:
[cntsamp2_en.zip This sample (cntsamp2_en.zip)] is a slightly enhanced version of what you just read above. It uses a subroutine to do the whole <tt>add / setstem</tt> process, which makes it more difficult to understand at first but resulting in much better structure and readability. In addition, it shows how one <tt>add / setstem </tt>- process is sufficient to supply all types of views.

Next month we'll take a look at the "rest" of the container stuff like removing / selecting items reacting to several events.

If you have any question (or you found some bugs / have some suggestions) regarding containers... don't hesitate to mail me!

Sorry to leave you now, but I have to translate this article to German before my editors shut down their office cursing my name for letting them down again...

[Editors note: We would never do that to Thomas. But it would be nice to have the articles in a bit earlier.]