VOICE Home Page: http://www.os2voice.org |
March 2003
[Newsletter Index]
|
By Thomas Klein© March 2003 |
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.)
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 centered 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...
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:
where stem is the NAME of a stem variable. If you want to receive the EventData information into a stem named "myvar", you must specifycall EventData (stem)
Yes - including the quotes!call EventData ("myvar")
Then, you would put the upper bound of that array (the number of entries) into the entry hash>0:myvar.1 = "first"
myvar.2 = "second"
myvar.3 = "third"
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 hash>0 to specify the amount of entries a stem is made up of. This way, the programmer can use entry hash>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).myvar.0 = 3
Okay, let's check the EventData syntax again... this is what is stated about it in Drdialog's help:
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".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 [snip]
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.
Second, as I mentioned above, stem entry hash>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 combobox 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 is used instead.
for the container control event... | CALL EVENTDATA will give you... | ||||||||||||
Changed The user has changed the value of a container item field |
|
||||||||||||
Enter The user has either pressed the ENTER key or double-clicked in the container |
|
||||||||||||
Select The "emphasis" of a container item (select, mark, cursor) has changed |
|
||||||||||||
ShowMenu The user has requested a context-sensitive menu for the container (by clicking mouse button 2) |
|
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:
Init | The container control is initializing (during the dialog open event) |
Scroll | The container control has scrolled |
GetFocus | The container control has been given the focus (= has become te "active" control within the dialog) |
LoseFocus | The container control has lost focus (= another control in the dialog has become "active") |
Drop |
An object was dropped on the container control This requires you to use the EVENTDATA call in conjunction with the DROP event to retrieve more details about what's going on - we'll deal with the drop events in a later part of the series... |
A final example on how to use The eventdata stuff: If you want to react on a changed event, you need to
Note that you optionally only need to specify the first letter of the view type - thus, if you code downcall mycontainer.view "Text"
will do the same as the statement above. Setting up a view type ideally is done during the INIT event of the container. In addition, when using the containers INIT 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 tocall mycontainer.view "T"
Now that you know how easy the VIEW function can be, let's check its entire syntax:call view "T"
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 useoldview = 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).call container.view [type], [title], [bitmapwidth, bitmapheight], [expandbitmap, collapsebitmap]
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:
< | Title will be left-justified |
> | Title will be right-justified |
| ("pipe" character) | Title will be centered |
_ (underscore) | A separator line will be drawn between the title and the contents (items) of the container |
indicating the end of the formatting character sequence, all following characters
will be used as title text This is optional - if not specified, the title text will begin with the first character that is not recognized as a formatting character (a character which is not included in this list) |
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"
Okay, the next parameter is [bitmapwidth, bitmapheight]. 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]. 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...
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])
label | is the label (or title, name, caption) that shows up for the entry in the container |
bitmap | is the bitmap displayed along with the items label in name, flowedname and bitmap view |
where |
determines the sequence for inserting the new item. This can be either "First", "Last" or "Next" and it is partly used in conjunction with the next parameter reference-id. If reference-id is not used, "first" will insert the new item as the first one in the container and "last" will make it be the last one (this is the default if you don't specify where). If however reference-id is used, "next" will make the new item be inserted after the one specified by reference-id, whereas "first" and "last" used in conjunction with reference-id will make the new item become the first or last child item of the one specified by reference-id |
reference-id |
Refers to an item that is already in the container This must be the id that the already existing item was given by the system at the time it was added to the container |
data |
This is an optional parameter useful for storing any kind of data along with the
new item. Hey! This can be used to store the database record key (or equivalent) for the new entry... |
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 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.
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...
This syntax in a more readable way is made up of only two parameters:call mycontainer.setstem [stemname] [, "[+/-]SELECT"] | [,"[+/-]MARK"] | [,"FORMAT" | 0 | item]
[stemname] 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).call mycontainer.setstem [stemname] [,action]
As for the action to be performed, there are three possible values for it:
"[+/-]SELECT" |
means either "+SELECT" or "-SELECT" and is used to select or
unselect all items of the container whose ids are specified in the variables STEM.1
through STEM.n STEM.0 specifies the number of stem "entries" - the array boundary if you want to put it this way |
"[+/-]MARK" | means either "+MARK" or "-MARK" and is used to mark/unmark all items of the container whose ids are specified in the variables STEM1. through STEM.n - again, STEM.0 specifies the number of stem "entries". |
"FORMAT" | 0 | item |
This parameter specifies the actual "action" to be performed using the
stem data.
|
We won't discuss the SELECT and MARK stuff now but focus on the third one. To set up a container in detail view, we need to call the VIEW 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:
the character... | will result in... |
= (equal sign) |
...the column display a bitmap instead of text. If this character is not found within the format string for the column, the column will display text. (Thus, the default for the column is to display text, except if this format character was used) |
~ (tilde) |
...the column being invisible. This is quite useful if you need to store data along with a container item but don't want this data to be visible to the user |
X |
...the column being read-only. By default (if this character is not used), the column value of an item can be changed by the user by holding down the ALT key while clicking on it. However, editing a column value in such way is applicable for columns only that don't show a bitmap. |
. (dot) |
...the container having a movable, vertical split bar to size two parts of the container
with this column being the last (most right) one in the left part of the split window. If this character is not used in the formatting string, the container won't show a split bar in detail view. If there is more than one column that is formatted with a dot, the most right one will be the one to show the split bar. |
^ | ...the contents of the column being top-aligned. |
V | ...the contents of the column being bottom-aligned. |
- (minus sign) |
...the contents of the column being vertically centered. This is default - if neither "^" nor "V" nor this one is used, the contents of the column are vertically centered within each row. |
< | ...the contents of the column being aligned to the left. |
> | ...the contents of the column being aligned to the right. |
| (pipe character) |
...the contents of the column being horizontally centered. This is default - if neither "<" nor ">" nor this one is used, the contents of the column are horizontally centered. |
_ (underscore) |
...the column title having a separator line below it. (note: the separator line will not cover the entire column width, but just the width of the title text) |
! (exclamation mark) |
...the column having a vertical separator line drawn on its right side. |
To make a column simply display text that is both vertically and horizontally
centered, 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! ;)
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 "/*" and ending in the reverse way"*/" (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" :
/* this is all intended to be used in the 'init' event handler of the container */ /*------------------------------------------------------------------------------*/ /* setting up the container: detail view, title with separator */ call cnt.view "D", "_buddylist"
/* Now let's got for the columns. */ /* We need to set up a stem variable to hold the formatting strings. We'll call it "formatstem". */
formatstem.0 = 5 /* five columns = five entries for the stem */ formatstem.1 = "<X!" /* 1st is left-justified, read-only, with separator at right */ formatstem.2 = "=!" /* 2nd column shows a bitmap, with separator at right */ formatstem.3 = "<." /* 3rd is left-justified with split bar */ formatstem.4 = ">!" /* 4th column is right-justified with separator at right */ formatstem.5 = "|!" /* 5th column is centered with separator */
/* the actual call to submit the formatting data to the container */ call cnt.setstem "formatstem", "F"
/* set up the column titles */ /* we use a stem variable called "title" */ title.0 = 5 /* again: five columns = five stem entries */ title.1 = "#" title.2 = "symbol" title.3 = "Name" title.4 = "Phone" title.5 = "e-mail"
/* the actual call to submit the data to the container control */ call cnt.setstem "title", 0
/* now we create the 'objects' that are in the container */ /* make sure the 'bitmap.dll' is in the same path as the sample applications .res file */
/* the add function will "only" create an object with a label */ /* the column values need to be submitted with a subsequent call to setstem that */ /* makes use of the new items id - this is why we're going to use the function */ /* notation to retrieve the new items id... */ newitem = cnt.add ("Peter", "bitmap:#66")
/* for the 'simple' view types, this is already sufficient */ /* they'll use the data submitted via the 'add' function *//* in detail view however nothing would show up, as the */
/* columns didn't receive their data yet - let's submit the */ /* column data via a stem called 'data' */ data.0 = 5 /* five columns = five stem entries */ data.1 = 1 /* value for column 1 */ data.2 = "bitmap:#66" data.3 = "Peter" data.4 = "555-12345" data.5 = "pete.doe@somewhere.biz" /* now the actual call to submit all column values for the new entry */ /* this is where we need to supply the items id which we retrieved */ /* in the add function above ("newitem")... */ call cnt.setstem "data", newitem
/* That's it for the first container item... to add another one: */ newitem = cnt.add ("Paul", "bitmap:#64")
data.0 = 5 data.1 = 2 data.2 = "bitmap:#64" data.3 = "Paul" data.4 = "555-45678" data.5 = "paul.jones@emailser.ve" call cnt.setstem "data", newitem
/* end of code sample */
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 add
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 add statement of an item.
;)
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. ;-)]
References:
|
[Feature Index]
editor@os2voice.org
[Previous Page] [Newsletter Index] [Next Page]
VOICE Home Page: http://www.os2voice.org