DrDialog, or: How I learned to stop worrying and love REXX - Part 7
By Thomas Klein
Welcome to the seventh part of our "little" series on DrDialog. Today, we'll complete the subject of dealing with container controls by taking a look on how to react upon user actions that take place in a container. But first, let me mention some facts that I forgot to tell in last months issue...
The 'style'-settings of containers
Like all other controls that are usable by DrDialog the container control too has some style settings that can be used to control both appearance and behaviour. Those kind of settings are not specific to DrDialog by the way - you'll happen to find them in other development tools for OS/2 as well. This is due to the container being an object provided by the System rather than "only" by DrDialog.
Even if taking the risk of not being very accurate in matters of backgrounds and concepts, I'll try to explain it this way: All programs that are run under control of the Presentation Manager are able to make use of its objects, as it is provided by the system itself (or the PM). Thus, it won't matter what development tool you are using to create your PM application (DrDialog, VX-REXX, VisualAge for [whatsoever] and so on), because all it does is pass certain parameters and values to the system-provided object internally.
The reason I'm explaining this is that DrDialog's online help is quite "thrifty" on information regarding style-dialogs... to say it more politely. At least, I couldn't find any information about the meaning of the settings (except for the paragraph that explains about style settings being stored as specific bits within a 32-bit value and how to toggle specific bits by using the REXX function bitor). But because we now know that it actually doesn't matter if we're using DrDialog or e.g. VX-REXX to use a container control, we're able to refer to another development tool's manual to get the information we need.
But before you start complaining why the author didn't simply do that prior to write his article: I'm actually too lazy to install VisualAge C++ 3.0. I could have used two other development tools that are lying around here somewhere, but... sigh... I simply didn't have the time to take the necessary efforts to get it up and running. And (to be honest) I didn't feel the need to know all the details about it, as DrDialog's defaults and those settings I was able to figure out myself were sufficient for those programming task I did up to now. Well, one never knows - maybe I'll try some time later...
Single means, that the user is able to select a single entry only. If another entry is selected, the previously selected one will be unselected.
Using Multiple will enable the user to select more than just one entry by toggling the selection state of an entry via a simple mouse click without changing the current selection state of the other entries.
Extended finally is the "full-featured" version of selecting entries that provides different combinations of mouse and keyboard inputs. A full range of entries can be speed-selected by clicking on one entry and shift-clicking the other (upper or lower respectively) entry of the range. This will select all entries from the first to the other one according to the sort order of the container. A single entry's selection state can be toggled by Ctrl-clicking on it (that is, clicking while holding down the Ctrl key). But beware: A simple click (without one of the modifying keys) will unselect all previously selected entries.
Container Styles does include two settings that I am not able to tell you anything about it. I tried both Mini record and Verify pointers but didn't notice any impact on either look or behaviour. Perhaps that was because of these options being intended to change some "internal" behaviour of the object that aren't supported by DrDialog or simply didn't show up in my rather "simple" kind of program. So, let's focus on the other two options then:
Auto position is something that you are urged to leave in its default activated state when working with DrDialog. Some features of OS/2 objects are simply not supported by DrDialog directly, like specifying an entry's position within a "icon view" container when adding it (that's to say a container with view="Bitmap"). Such containers actually require the programmer to specify where to put the entry, because - as you might know from WPS folder - entries can be placed freely within a folder (as long as you're not using column view or auto-arrange by grid). Thus, a container in bitmap (icon) view is quite the same as a WPS folder using icon view. In addition, such container objects behave like an auto-arranging WPS folder if Auto position is activated, which means that the container itself will take care of arranging the entries according to its grid. This might all be a little confusing. Let me explain what actually happens if you're using a bitmap-view container withoutAuto position. In this case, the system relies on the programmer to specify each entries position within the container when adding it. But as we don't have the means to do so in DrDialog, the container will pile up the entries on the default starting position - one on top of the other which will leave only the last one (the uppermost) being visible. That's bad. Note that Auto position only effects containers in "Bitmap"-view. All other views already take care of positioning the entries automatically due to the nature of their view type.
Read only prevents users from being able to change entries by ALT-clicking on them. This takes effect on all types of view and also prevents the user from changing column values in a "Detail"-view container, even if a column was set up to be not read-only. The developer however still has complete control over the entries, that's to say you are able to change whatever you like "from code". This is useful to make sure that the end user is required to use a developer-provided function to change entries instead of using the containers built-in capabilities. Note finally, that Read Only does not take any effect on the events being triggered, thus, clicking on an entry will still generate the appropriate event for example.
There's not much to say about the Basic styles options. Visible is used to either show the container or not. If it is made invisible, the user can't operate it of course - thus no events will be triggered. Maybe you'll come up with a scenario where this might be useful, but currently I can't figure out what that should be like.
Disabled is rather suitable for use. If you use this option (by checking), the user will actually "see" the control and its entries but won't be able to do anything with it and no events will be triggered. This is useful in cases where you might want to show something that can't be changed or handled - except if using specially provided functions (like menus or pushbutton) if any. In general compliance to the ethics of GUI this should be avoided, as the user is presented a well-known control that was stripped off all it's visible and functional capabilities. Changing an entry for example will become quite clumsy to the user, as he is somehow required to specify the entry that he wants to change and won't be able to use either mouse or keyboard within the container. You should try to figure out if there is no other programmatic mean to achieve what you want you application to do.
Group actually doesn't mean anything to me. Due to the lack of help text I'm in doubt if this is of any usable meaning in DrDialog.
Tabstop (if enabled) will make the control accessible by keyboard (the TAB key to be exact). I haven't mentioned this up to now if I remember correctly, because I assumed everybody already knows, but nevertheless here we go: By using the TAB key, the user is able to "tab" from one control to the next one (and backwards by using SHIFT-TAB). This of course is only applicable to control that were set up with tabstop enabled. The currently selected control can be operated by either using the SPACE BAR (e.g. clicking a push button or (un)checking checkboxes or radio buttons) or by using arrow keys in listboxes or entering data into entry fields.
By the way: Tabbing order...
There is something interesting to be mentioned concerning the tabbing order: I am part of the (apparently endangered) species of users that are able to operate GUI programs using the keyboard - as far as this makes sense at least, because e.g. trying to use a graphics or drawing program without mouse doesn't actually make much sense. Even in these modern days it comes in handy to be able to use the keyboard as I'm using an optical cordless mouse whose rechargeable batteries tend to fail at the most unsuitable moments. Besides faster operation due to the use of hotkeys or accelerator keys, it's often easier to use the keyboard in typical programs... however it largely depends upon the program.
More than once I have gotten angry about "handmade" controls that weren't designed to be either accessible or usable by keyboard although their appearance and behaviour indicated them to be. But this is nothing compared to programs that in fact are usable by keyboard but that were designed without taking care of the tabbing order, which results in ridiculous random jumping from one control to another when using the TAB key. I know that Visual Basic for instance provides means to freely assign tab indices to controls at design time. When adding controls to a dialog the tab index is increased automatically... which appears to be okay at first sight. But as soon as the developer starts adding, removing or arranging controls, trouble starts as these controls retain their tab index. Apparently, many VB programmers seem to forget to do a final check of the tabbing order before starting to deliver their stuff (or test it at all) which results in such symptoms.
The reason for me telling you about this is that with DrDialog you won't have to mess with tabbing order at all, because it will be automatically determined at run time based upon size and position of the controls within a dialog window. Roughly speaking it's top-to-bottom and (within this order) left-to-right, except for control being grouped in a group frame: A group is handled separately as one large control but with the group, the same rule applies.
To sum up: Don't waste any time worrying about tabbing order in DrDialog! Its mechanism described above is the best example I've seen so far when thinking "Actually, this should be quite simple...". It's perfectly suited for 99% of all cases and works not only like you expected but like a charm too.
Let's get back to the container control. Last time, we discussed setting up container views and adding entries. Today, we'll learn how to get rid of them. The function (or "method") in question is called delete and uses the following syntax:
rc = [dialog.][control.]Delete([item])
On return, a variable called rc contains the number of deleted entries. Again - as you might recall - we're allowed to use the CALL type notation as well, if we don't mind the number of deleted entries... in this case, syntax would rather be:
To give an example, assume to have a dialog called dlg_test and a container called cnt_mine. The appropriate commands would look like this (anything between "/*" and "*/ " is just a comment):
rc = dlg_test.cnt_mine.Delete(entry-id) /* Function notation */ call dlg_test.cnt_mine.Delete entry-id /* CALL notation */
This example is based upon the assumption that there is a certain variable called entry-id which holds the ID of the item to be deleted. We'll get right back to this - let me first mention that delete can be used without specifying a specific entry. This will result in all entries of the container being deleted. This is done by simply not using the parameter item . According to the example given above, this would read:
rc = dlg_test.cnt_mine.Delete()
Or when using the CALL notation:
...and whoops - they're all gone. Now that's great: A whole lot of work only to create one single entry, but a single simple statement to delete everything! ;)
Of course (I know you may know this) the optional parameter of [dialog] can be left off, if delete is called from within the dialogs own code context. In addition, the second optional parameter [control] can be omitted as well if delete is called from within an event handler of the container itself.
Assuming that you would have created a pushbutton underneath your container to delete all entries, you would code the following statement in the pushbuttons Click event handler:
And your container will be empty. By using the function notation like in
removed = cnt_mine.Delete()
...you could make use of the variable removed to know how many entries were deleted (if this would be any of use for you).
Fine, but let's take a look at [item] parameter now. Being able to delete all entries of a container might be useful in some cases, but honestly - mostly we'll deal with deleting specific entries, right? As I stated above, delete can be used with specifying an entries ID. The ID is provided by the system as a return value of the add function used to add an entry to the container.
Well... this means that we must save that ID somewhere to be able to delete the appropriate entry later, right?
Well, actually "quite right", but don't step into technical details: Let's start by figuring out, what can actually cause us to delete a specific entry. Usually, the user would select one or more entries to be deleted by first clicking on them. These cases can easily be handled by delete if using additional functions that we'll get to know right in a moment.
You might have decided to prevent the user from clicking around in the container for some good reason by specifying the appropriate style for your container. This won't mean that you won't be able to delete entries of course, but retrieving the necessary IDs to do so will become quite difficult: There is no way of collecting IDs from code at a later moment! This scenario now would really require you to store the IDs returned by the add-function in a separate list, table or whatsoever to associate them with another data value of an entry that's suitable to act as an initiator for the delete action. For instance, You want to provide a way to delete entries based upon their names. While this is quite okay, you'll have to take care of the fact, that containers can hold any number of entries with the same name (except that you took care of preventing this "manually"). Technically speaking, the container doesn't mind any data of any entry because internally it's all about IDs only. So, if you want to do things like deleting all entries whose names start with "A", you need to have a table that holds each entry's name along with the appropriate ID. This enables you to identify all entries whose names start with "A" and use the associated ID in a call to delete. Don't forget to update your reference table too. Think about using a listbox control for this purpose, because cleaning up the list will be somehow easier than.
You might already get the picture: This requires quite some efforts and thus should be another reason to use a container (in compliance to GUI ethics) only if you provide all of its capabilities to the end user too. Okay, let's take a look on how an entry that was selected by the user can be deleted.
This is about something quite complex that many people aren't aware of at first sight: The difference between a selected entry and the current entry (the one that the cursor is on). Dealing with this matter requires to take some factors into consideration:
- The selection Style that is chosen (single, multiple, extended)
- Usage of mouse and/or keyboard
The bottom line of all this is, that the current entry doesn't need to be a selected one and vice versa. Things are quite simple when dealing with a container that was set up to use a selection Style of type single: There will be only one entry in the container that can be selected. Regardless of using mouse or keyboard the current entry will always be the only selected one. Using themultiple and extendedStyles will result in multiple different items being selected or unselected with the current entry being possibly again another one.
This raises the following question: If the user requests a delete action - what are we intending to delete? The current entry or the selected one(s)? Hmm... that's not easy, right? This has actually nothing to do with the IDs that are needed for the delete function, but it's rather a matter of basic understanding. Like I said, with a Style setting of simple you won't find it hard to react upon a delete request, as the current entry is the only one that is selected. If the container uses one of the other two more complex selection styles, you might...:
- Explain the difference to the user:
Either you provide different options in a (context) menu like "delete THIS entry" and "delete selected entries" or use pushbuttons. In the latter case, note that clicking a pushbutton will take the "focus" away from the container and move it to the pushbutton, which will result in the container having probably one or more selected entries still, but no current entry any more.
(Context menus again are somehow special and require a separate discussion)
- Make use of the de-facto standard: Deletes (as well as all other actions) referring to multiple selections will only affect the selected entries.
Think of WPS folders for example or even VIO-"only" programs like file managers (e.g. FC/2): No matter what entry the cursor is on, only selected entries will be deleted. If there are no selected entries, the current one is deleted.This is definitely the preferable answer to the question stated above.
This behaviour can be observed in your container as well, when reacting to a select event by using the eventdata() function:
Eventdata() will return two values when being used in the context of a select event: The ID of the entry that the event has occurred for and the change of state ("emphasis") for that entry. If you downloaded last months sample application, you're able to easily check this on your own: Load the .RES file into DrDialog, bring up the code editor tab for the containers select event an enter the following statements:
call eventdata say "----" say "id:" eventdata.1 say eventdata.2
Now, run the program and watch for the messages appearing in the run time monitor (the list beneath the "running guy" icon) whenever you click or move to an entry. Next, change the containers selection Style into extended or multiple and run the same program again with taking a look at the messages generated by selecting one or more entries or moving from one entry to another. You'll notice that eventdata will report different states for the entries according to Style-settings and usage of either mouse or keyboard. The only actual state that you can "rely" on is the selected/unselected state of entries...
Now, let's see what means are available to the end user in a typical scenario to delete entries and how to react upon them. I took the chance to verify this by using a WPS folder that - gladly - didn't contain important files...
- The "del" key was pressed with a container having the focus (thus, pressed "in a container"):
This only affects the selected entries. If no entry is selected, then nothing is getting deleted either.
- A context menu is brought up and "delete" was selected:
Now, context menus include two different meanings: Either, they are called for an entry (mouse button 2 clicked on them) and they refer to that entry only - except you provide a menu option that explicitly states to deal with theselected entries. Or, the mouse click occurred somewhere in the free space of the folder. In this case, all actions affect the container itself. Thus "delete" would delete the folder/container itself. (Again, only if the menu option chosen didn't explicitly mention something else like "delete selected entries".)
- A pushbutton was clicked:
That's just the same as with the "del" key and only affects the selected entries. Again, if no selected entry exists, nothing is deleted.
Now, how to make appropriate delete-calls out of that?
Well, let's not deal with the context menu and the "del" key stuff. Both require additional knowledge and I don't want to puzzle you with more details, taking the risk of getting too fuzzy with explanations... let's instead figure out a pushbutton somewhere beneath or above the container that we want to use to delete entries.
As we decided, this refers to selected entries. In a container using simple selection this would equal one entry whereas with other selection styles this could equal one or more entries. So we need to find out which entries are selected in the container first (if any). To achieve this, we'll use the getstem function that we already discussed (in parts) in last months issue. It is used to retrieve information about several entries at once via a stem variable. Its syntax looks like this:
call [dialog.][control.]getstem [stemvar] [, "Select" | "Mark" | "Cursor" | 0 | entry-id ]
In order to un-mystify what it's about: This is "my" version of it:
call [dialog.] [control.]getstem [stemvar] [, type]
If getstem is used right within an event handler of the container, you won't need to specify the[dialog]and[control]parameters. But as we said that this all is intended to be part of a pushbutton's click event (with the pushbutton being part of the same dialog as the container), we need to specify the control we're referring to, thus only [dialog]can be removed from the call. Using it however won't hurt at all. Assuming the containers name to be cnt_test would result in the following call syntax:
call cnt_test.getstem stemvar,type
stemvar is the Name of a stem variable we want the results to be placed in. Note that - as always when dealing with stem variables - we need to provide its name in quotes (single or double quotes doesn't matter) for rexx to understand that this is a variables name instead of a variable that holds the name. As you might have noticed by the first two syntax notations for getstem, the stem variable name is an optional parameter - thus, it can be omitted. In this case, getstem will provide a variable on its own called "stem".
The Type parameter specifies which data we want to retrieve about the entries of a container. Valid values for Type are as follows:
|if Type was set to...||...the stem variable entries will contain:|
|"Select"||- the IDs of all container entries which are currently selected|
|"Mark"||- the IDs of all container entries which are currently marked|
|"Cursor"||- the ID of the container entry which is the "current" one, thus the one that has the cursor|
|0 (this is a zero)||- the column titles used in detail view|
|an entry-id||- the column values of the entry specified by entry-id|
So far so good. Now, to get to know the IDs of all entries in cnt_test which are currently selected and put them into a stem variable called "selection", we need to code:
call cnt_test.getstem "selection","Select"
...and if we prefer to not specify a stem variable on our own (thus using the provided one called "stem"), it should read:
call cnt_test.getstem ,"Select"
Note that the comma in front of "select" needs to be retained in this case for rexx to understand that the first (optional) parameter was omitted.
The next (and final) simplification to getstem is the fact, that there is a default value for the Type parameter as well (the value used if the parameter was omitted) which is "select". As we wanted "select" to be the value of type, the same stuff as above can also be coded by using the default (omitting the type parameter):
call cnt_test.getstem "selection"
And again, if we don't want to provide a stem variable name on our own, we could even code:
Well, that's a quite handy syntax, isn't it? ;)
Fine. Now we have a stem variable whose entries contain each ID of a selected entry of our container. In addition, getstem provides us with the total amount of selected entries (equaling the number of stem entries) by its entry #0 (you remember that mechanism from last month?). In all, this enables us to build a loop processing all entries from the first one up to the last (because we know how much there are). We didn't discuss loops up to now, I know, but you might want to just take a look at what's coming up:
do i = 1 to selection.0 [enter the statements to be processed for each entry here] end
This code (actually not processable due to the bracketed line) makes the variable i being set to 1 and getting incremented by 1 as long as this value won't exceed the value of the variable selection.0. Then, the loop sequence will end. Each incremental step will process the stuff that is contained "between" the header (do-line) and the footer (end-line) of the loop. To give a "real" example showing how to print out all IDs, this is how it's done:
do i = 1 to selection.0 say selection.i end
Because the value of i is incremented by 1 at each pass of the loop, the first pass would print (or "say") the contents of selection.1, the next pass would print out the contents of selection.2, then selection.3 and so on, until the last entry (=value of selection.0, the total amount of entries) was processed. Selection.0 (the contents of the "0th" entry of the stem variable selection to be exact) contains the total amount of stem entries, thus equaling the total amount of selected entries in the container.
With that in mind, getstem is well suited to provide a kind of status line for your container, which could be used to show information on the number of selected entries. This of course doesn't make quite sense if your container is using a selection Style of single, as there'll always be only one selected entry. ;)
But hey - do we like to know the IDs of those entries? No Sir - we want to get rid of them! Now that we know that selection.i within our loop contains the appropriate ID of a selected entry, why not finally combine it with the delete function we discussed above to make it become...:
do i = 1 to selection.0 call cnt_test.delete selection.i end
If you think that the whole stuff is getting too complicated due to the ID mechanism, let me explain something:
A listbox uses a different way of handling its entries, as this is accomplished by an index (starting with 1 for the first entry and so on). Deleting an entry from a listbox provides a totally different problem due to the index mechanism: If you want to delete the entries 7 to 10 from a listbox for example, you might want to do this using a loop ranging from 7 to 10 and deleting each entry within that range. Basically this is correct, but... the listbox control is designed to automatically rearrange entries to avoid "empty slots". after you deleted entry #7, the list will be "compacted" internally which makes all subsequent entries scroll up 1 place. The former entry number 8 now becomes number 7, entry 9 will become 8 and so on...
In practice you'll be deleting entries 7, 9, 11 and 13 (instead of 7, 8, 9 and 10). And if this takes place you're even lucky, because if the list is actually made up of 10 entries only, your third call to delete (which then would delete entry #11) would give you an error message for deleting an entry which doesn't exist (any more) - not to mention that you actually didn't even wanted to delete it. ;) Well, bad luck then. So the only way to get around this problem is either using a listbox by means of selected entries as well or coming up with another clue... (maybe we'll talk about such things in a later part - don't hesitate to remind me if you're interested in it).
This problem would arise for containers as well, if they were handled by indices instead of IDs. But as DrDialog doesn't simply provide us with that means, there's no need for worrying about such matters. Excuse me for this little tour but maybe you've got the picture of the advantages of using IDs.
Okay. The user is able to select entries and click the pushbutton. The entire event handler for the Click event of the pushbutton would thus consist of the following lines:
call cnt_test.getstem "selection" do i = 1 to selection.0 call cnt_test.delete selection.i end
Not too bad what we get out of that little piece of code, right?
Fine. We're adding, we're deleting... but what about editing?
In a later part, we will discuss Menus and context menus in detail. Especially when dealing with containers, those menus are extremely useful to end-users. Put yourself to the test - how many times are you working with context menus? Unfortunately this is a subject to deal with the properties of Drdialog's menu editor first in order to know how to achieve things with the given capabilities.
I would like to finally introduce you to the subject of handling edit requests for container entries, which will complete our "container tour" for the time being. A users edit request can either refer to the name of an entry or to a column value if the container is in detail view. Let's take a look at name changes first - this is done by (as you might know) ALT-clicking on an entry. The title (name) display will then be switched into an entry field. How about an example?
That's what it looks like. The container control comes with all necessary capabilities built-in. The only thing you need to do is actually react to the changed event which is triggered upon completion of the change. But take into consideration, that the changed event does not automatically mean that the actual value was changed - it's rather used to signal that a change request was processed and that data entry is completed (the container has switched back from entry field to "usual" display for that entry).
Of course we want to know the new name, after the user has (possibly) changed it - to store it on our address book file (last months example) for instance. In case that you are using a container control to represent a directory on your hard disk, you now may want to rename the actual file according to the users input. To retrieve the (new) name of an entry, we require two functions: eventdata and item.
- Eventdata enables us to identify the actual entry and the kind of entry data that was changed (entry name or column value of detail view).
- item is used to retrieve the new value for the appropriate entry data
As we want to react upon a users change request, we already know where to start in code - the changed event handler of the container control. In this routine we'll start by calling eventdata. According to what is stated in Drdialog's online help, eventdata (when called within a container's changed event) will return two values by using a stem variable. If we don't specify a stem variable on our own, it'll use a built-in default to provide a stem variable called "eventdata". Because we happen to be real lazy guys and don't like to invent variable names over and over again, we'll gratefully accept this offer of course. BTW: You're free to use the sample program from last month of course (the one that was contained in a separate .ZIP file for download). To get a first impression of what eventdata is doing, we'll code the following statements into the containers changed event handler...
call eventdata say "---" say "Change occurred for entry id" eventdata.1 say "The changed entry data was:" eventdata.2
...then run the program. Now change any entries name by ALT-clicking on it and entering a new name. The run-time monitor will show:
The ID displayed for you will certainly be different from the one in the screenshot above, as IDs are dynamically created by the system upon creation of an entry. But the last printed line should actually contain "VALUE", as this is used by DrDialog (or eventdata) to signal that the name (title) of an entry was changed. Don't think about this too much now, go on and run the program again, change the containers view type by selecting "Detail" in the dropdown list named View: and change a column value for an entry, like...:
Once you completed your input, the run time monitor will show (more or less):
Ahh! Despite the fact that in your case, most probably the ID (and according to the column you chose the second lines value too) will differ from the screenshots value, there is one BIG difference from the previous run: eventdata will not return "VALUE" but rather a number. This number indicates the column whose value was changed. According to these two examples, we now know both entry and data that was changed - either name or column value. Now let's see how to use the item function (or method) to retrieve the actual new value that was entered in the change request. The syntax is as follows:
content = [dialog.][control.]item([entry-id [,"VALUE" | "BITMAP" | "DATA" | column] [,new-value) )
Or to put it "my" way:
content = [dialog.][control.]item([entry-id [,type] [,new-value) )
The function can be called to assign a new value and/or retrieve the current one. This is done by using the last optional parameter to assign a new value, while content will be set to contain the previous value upon call completion. If new-value is omitted, there'll be no change but it will just retrieve the current value.
As you might have noticed by to the syntax diagram, all parameters are optional. If you omit all of them, item will retrieve the total number of entries within the container into content. This could be useful. But now let's finally find out how to determine the kind of change that happened to an entry by using item. This is done with the type parameter:
|Setting type to...||...will give you:|
|"VALUE"||the entry name (title)|
|"BITMAP"||the name of the bitmap assigned to the entry|
|"DATA"||the data value that optionally can be assigned to an entry|
|a column number||the column value assigned to the given column of an entry|
So we're able to retrieve almost every kind of information about an entry. Note that item does provide some more features but as we either did already discuss them or they are not of importance to us now, we won't go into details about it any further.
As always, we're free to omit the leading qualifying parameters [dialog.] and [control.] if the item function is called directly from within the changed event handler of the container. Due to the possible information returned by eventdata in matters of edit requests, there are two ways of using item accordingly - in the first case, this deals with retrieving the new entry name by
content = item(entry-id, "VALUE")
and in case of a column value that was changed by coding
content = item(entry-id, column-number)
...with entry-id being the first value returned by eventdata (the entry id) and column-number (in the second example) being the column number that is returned as the second value by eventdata. But before starting to think about a way how to code two different calls in order to react to what was returned by eventdata, let's take a close look on what is returned by eventdata in its second value: Either this is VALUE or a column number... get the picture? There's a close match between the second returned value of eventdata and the type-parameter required to call item. In short, we're able to code the following command:
content = item(eventdata.1, eventdata.2)
This will make us retrieve the new value in either case, regardless of whether an entry name or a column value was changed. Of course this won't help having to code an appropriate routine to distinguish between the two cases, but that's not really hard - we only need to test if eventdata.2 contains VALUE. If so, then an entry name has been changed, while all other cases would equal to a column value change.
The entire event handler thus could look like this for example:
call eventdata say "-------------------" who = eventdata.1 what = eventdata.2 if what = "VALUE" then say "Entry with ID" who "is now called" item(who, what) else do call getstem "title", 0 say "For" item(who, "VALUE") "there was a change in" title.what "which now contains" item(who, what) end
Please note that your browser possibly will wrap lines - each of the two say -statements must appear on one line
Some comments to the above routine:
- In line 1 we're calling eventdata without using a stem variable of our own. Thus, a stem variable named eventdata will be used
- Line 2 is only used to display a dash line for better readability of the run time monitor outputs
- Line 3 stores the value of eventdata.1 in a variable called who
This is only done for better readability and to make it fit to what's done in line 4 ...
- Line 4 stores the value of eventdata.2 in a variable called what
This is done in order to get a value we can use in pointing to an entry of a stem variable called title... see the paragraph below for additional information
- Line 5 marks the beginning of an IF statement: We're checking whether the contents of what equals "VALUE"
An IF-statement is made up of a conditional expression, a "TRUE-branch" (which is executed if the condition is true) and optionally a "FALSE-branch".
- Line 6 contains the TRUE case (the tested condition is true): We want an appropriate message to be displayed and that's it for the TRUE case, because...
- Line 7 starts the FALSE branch - that's to say all cases where what does contain something else than "VALUE"
- Line 8 marks the beginning of a block of statements. This is necessary because we want more than one statement to be executed in the branch. Compare it with the contents of the TRUE branch (line 6) which only contains a single statement, thus no DO...END is needed.
- Line 9 is used to retrieve the column titles used in detail view. They are stored in a stem variable called title
- Line 10 will display a nice message that is made up of both literals (fixed strings) and contents of variables.
- Line 11 completes the block of statements, thus completes the FALSE branch, thus the whole IF-statement.
Comments on line 4:
If a column value was changed, we want a message to be displayed containing the column title. All titles are retrieved using the getstem function in line 9, which will store them in a stem variable named title. In order to retrieve the actual title of the column which contains the changed value, we simply need to refer to the appropriate stem entry (entry 1 = 1st column and so on). Fine. In theory, we could code this to read title.eventdata.2 to directly refer to the appropriate entry in the stem variable. In practice, it won't work:
Rexx assumes the notation of a stem variable entry to be of the form stem . entry with the last dot indicating the two parts.
In our example this would mean that rexx will try to use the second (".2") entry of a stem variable called "title.eventdata". Unfortunately, there is no stem variable of that name. We thus need to find a way to tell rexx that we want the value of eventdata.2 to be "the part after the dot". There are (at least) two ways of accomplishing this, but I'll use the one which is easier to read:
- We use a variable to store the contents of eventdata.2 (see line 4: what).
- This variable will then be used as "the part after the dot" (see line 10: "title.what")
Phew! If you happen to be a stem variable newby, this might be quite puzzling at first sight, but - by the way - this is the case for almost every other attempt to explain stem variables too that I've found. The best way to understand what's behind those stem variables in my mind is to play around with them. You'll be surprised of the number of times that you'll hear yourself say "Ah", "Oops" or "Huh?"... ;)
Of course you're free to live a life without stemmed variables, but... actually they can't be avoided when having to deal with more complex tasks in rexx. I'll dedicate an entire part of this series to stem variables I guess. If you didn't understand the above stuff, then simply take it as it is and focus on container behaviour and usage.
And what's it doing?
Well, depending on whether you're changing a column value in detail view or an entry name in icon (bitmap) view, this routine will display appropriate messages in the run time monitor windows. For instance, changing "Paul" to "Harry" in icon view (view: Bitmap) will give us:
And changing Peter's phone number (in detail view) into "12345678" would result in...:
Okay. That's it for today - happy programming (or playing)! I hope I managed to give you some clues about working with container controls. If there are any questions about this article (or any other previous one), don't hesitate to mail me. Next month (I think), we'll deal with basic rexx commands and functions. You'll be able to use them in both DrDialog as well as "pure" rexx scripts...
See you next month!