DrDialog, or: How I learned to stop worrying and love REXX - Part 2
Well - summer holiday's are gone and elections around here in Germany are done also. Time to get back to more important thing! ;) First I'll start by checking your homework (did you forget that!?) I have to take this occasion to express my thanks for all those great mails that have reached me since last month's issue was published. Never did I expect my series on Rexx would be that "interesting" to a greater public.The amount and quality of feedback proves the OS/2 community is more alive and kicking than some others want to make us (or themselves) believe. At least this is true for you people out there who are reading the VOICE newsletter.
Fine. Okay - now for the homework: The question was how to get our textbox to be "empty" at program startup. There are two ways of doing this:
- At design time:
We simply use an empty string as an initial value for the textbox contents. This is done by invoking the context menu of the textbox at design time and selecting "Text" from it. An entry field will appear, containing the current value "Text". Just remove this by using the [del] key; then either click the green check mark or press the [Enter] key to confirm your input.
- At run time
We'll use an explicit command to clear the textbox contents from within our program. The command itself is not *that* complicated to imagine, as it can easily be derived from the one we discussed last month:
- call output.text("Hello world!") We simply change this into: call output.text("") Okay, that was pretty clear, but the more complicated part of it is knowing "where to put" this command. And that was where I left you a little puzzled last month - again, sorry for not being very fair. Again, you can choose among two ways of doing it: Either the command is executed at dialog load time (when the appropriate EVENT is taking place - we'll see details later) or you make use of the "global main routine". This doesn't make things more easy, right? It's not easy to find a good start into the whole matter - let's try to put it this way...
The classic approach of "procedural" programming languages is to provide one specific point where processing starts and following a sequential top-down way of doing one thing after another, making use of different means of logical flow of control (conditional branches, loops, etc.) up to a final stop. Maybe you already took a glance at the most simple way of procedural programming: Batch files - for example AUTOEXEC.BAT. The control of processing is done by appropriate commands used by the programmer. Invocation of sub-routines are always done by explicit commands in a higher-level routine (e.g.: gosub, PERFORM, call).
With "event-oriented" programming this is different: There still is only one starting point for your program of course, but as soon as your dialog is displayed, you're programmatically left somehow "nowhere": There's no idea of what happens first and what might happen next. If your dialog for example is made of multiple text entry fields, list boxes and command buttons, you'll never know if the user is scrolling the list box first or typing some text or closing your window or clicking "ok" or "cancel"... This is, why small parts of source code exist for each possible event and why they are kind of "lying around in your source code" without any special link between them. Actually, each of these is nothing but a procedural sequence of commands, but the difference is that these parts are not called by the programmer but by operating system (or the window management sub-system) itself. I ask the technically more sophisticated readers among you to forgive me for not being accurate in details, but I think that this series is not intended to explain window messaging and all those techniques behind it, as this is not mandatory for our audience (at least for the moment).
The whole sequence of a program in DrDialog is based upon events. There are events which take place upon user actions only - like clicking a pushbutton or dragging a slider. However, there are some events that are independent from user actions, like the initial display a dialog window. These events are provided by DrDialog to enable us to control certain stages in program execution. In our example, we could make use of the "OPEN DIALOG" event to clear the textbox contents.
By now, we know that we're dealing with an EVENT concerning a DIALOG window: So let's check out the EVENTS that are provided for the DIALOG window. Click mouse button two on an empty part of the dialog window to bring up the context menu - then, select "Events". This will pop up the submenu holding a list of all events that are provided by DrDialog for use with dialog windows. Hmmm. There's one called "OPEN"... sounds good, right? This is the event that is "triggered" when the program opens the dialog window - exactly what we need. Go ahead and select this menu entry.
Selecting the open-event of a dialog window can be done in a more comfortable way by simply double-clicking on an empty part of the window - but I wanted you to go through the context menu to see how to "reach" the events overview. You might want to check the result of the command we just entered. Do you remember how to use the "running man"...?
What's actually going on here?
We just discovered how to make use of the dialog's OPEN event to control things at run time. That's certainly nice, but... hold it! Didn't you ask yourself, WHERE the program actually starts? Where's the initial point in source code? And why is our dialog being displayed regardless of the fact, that we didn't tell DrDialog to do so!? The answer is found in DrDialog's "execution model": The whole concept is based upon DrDialog doing *nothing* by itself but waiting for events. There is only one exception from this rule: Program startup. DrDialog provides a built-in method for startup control that acts like this:
1) If there is no subroutine called "INIT" in the global procedures area go on by internally providing it (without any contents)
2) All dialogs specified in the "INIT" subroutine are loaded and displayed subsequently
3) What? Still no dialog windows displayed? Okay, go on by loading and displaying the dialog having the smallest ID number of all dialogs in the program. (Do you remember DrDialog identifying all controls by a unique ID number? Well - dialogs have such unique IDs too.)
4) Oh c'mon! Still no dialog displayed? That's enough:
Display error message "No dialog to load".
As we didn't provide a subroutine (or "procedure") called "INIT" anywhere in our sample program, rule #3 above is taking effect: DrDialog starts by loading the dialog window having the smallest ID number. This method is both simple and rock solid: At design time startup, DrDialog automatically provides an empty dialog template. As soon as you're not specifying an "INIT"-routine, DrDialog will make use of rule #3 to load the first dialog window and enter it's "normal" state of operation: Wait for events There's another feature built into DrDialog to ensure it's flow of process, which does not seem to make sense at first glance: If you remove the last (or the only) dialog from your application, DrDialog will immediately create an empty template. This way, DrDialog makes sure it can always enter its normal state of operation.
Global and INIT
But let's get back to what I said above: "Global main routine" and "INIT"
Q1: What are "routines"?
A: Actually, from our point of view, this is everything that's contained in the code editor. If you want to react upon the clicking of a pushbutton for example, all the commands you want to execute are written into the appropriate notebook page of the code editor. Such "page" is called an event-handler; it's a routine called by the system each time that it's according event takes place. To sum it up: Each notebook page actually represents a procedure (or subroutine).
Q2: What does "global main" stand for in this context?
A: As said above, there's an events handler (routine) associated with each events. In addition, the programmer can provide his own functions or routines. Usually, this is done to avoid having identical command sequences in multiple places of your program: The parts being used are packaged into a single subroutine that can then be called from where it's needed. If the programmer discovers an error or needs to change/expand the routine, he only needs to do it once, except keeping track of the changes in all occurrences of the code. Sometimes, such procedures are used to simplify the reading of more complex processing sequences. Anyway - such routines can be defined on dialog level, that's to say, that the routine can be called from anywhere *within the dialog* code. Quite useful, eh? But what if I have a program with multiple dialogs that requires my routine to be available to ALL dialogs - e.g. routines doing error handling or retrieving settings from an INI-file? In this case, the routine must not be defined on the dialog level, but GLOBALLY. Global in this context means, that the routine can be called from 'anywhere within the program' code.
Now, the "global main" routine is a special routine used to tell DrDialog what to do at program startup. The presence of such routine (must be called "INIT") tells DrDialog to avoid loading dialogs automatically. This routine might seem to be another good place to put our clear textbox contents command. It is, yes, but the global INIT routine requires deeper knowledge of DrDialog's execution model concepts: The way that DrDialog triggers and reacts to events is certainly worth a special detailed discussion (in a separate part), but first, we should get to know what all those controls are used for and very first of all: How the heck do we stop the program once it is up and running. ;) We'll talk about the "INIT"-routine later.
Once you made use of either method 1 or 2 to clear the textbox (design-time or "open"-event at run time), we should figure out what to do to stop the program. Once again, there are multiple ways to do so: We could provide a Quit-button, a "file/quit"-menu entry or by using the system's functions (system menu of window, close button (Warp4 and above), Alt-F4 or double-click on system menu icon in upper left of window frame). Let me start by saying, that menus require additional steps and knowledge - this is why we'll only discuss the other methods for now.
The advantages of the system menu or close-button (or Alt-F4) is, that we don't need to provide any additional code to make it work - thus it's kind of "built-in", as you might have noticed last month: Alt-F4 terminated the program. This leaves us with the question of "why does our program lack a close button?". This is due to the properties of the dialog window - which we didn't change but simply took the settings that are provided right from the start. This defaults to a dialog window that can be changed in size, contains a title bar and a system menu but no window buttons for minimize/maximize. To change these settings, select Style from within the context menu of our dialog window. This will bring up a settings window. We don't care of all the options, except for those grouped under optional controls:
If selected, the window contains a title bar. The contents of the title bar can be provided by the programmer.
Specifies whether the dialog window will show a button to minimize it. This equally takes effect on the contents of the window's system menu: If this option is selected, you'll find "minimize" being selectable in the system menu. Otherwise, it will be grayed (not selectable).
Specifies whether or not the dialog window will contain a button to maximize it. As with the minimize button described above, this setting also effects the contents of the windows system menu. The according "maximize" entry will either be selectable or not (thus grayed). In addition, the maximize button is used to toggle between the maximize and "restore" function. If the window is maximized, the button will show the "restore" symbol, used to reset the window to its size before maximize was used. Again, this takes effect on the system menu contents accordingly.
Specifies that the window will show it's system menu icon in the upper left corner of the window frame. The system menu provides several window functions as described above, as well as an entry to bring up the window list.
Vertical/Horizontal scroll bar
This will add according scroll bars to the window, which is of no importance to us at the moment. ;)
Starting with Warp4 (or prior by using several WPS extension/enhancement tools), there is another button added to the window frames: The "close" button.To make our dialog window show such a button, we simply need to include both system menu as well as at least the maximize or minimize button. This combination will tell the system to show the close button too. As you might remember from last months issue, ALT-F4 will close the window - this is the same for all other methods mentioned here (close from within the window's system menu or the close button. They all act the same way. The only thing left is a close pushbutton within our dialog window, altogether with an appropriate command in the code.
So let's create another pushbutton:
Either we make use of the controls collection by dragging and dropping a push button onto our dialog window or we can take advantage of the dialog window's context menu: Select the controls submenu and choose the push button symbol from there.
Okay, this is the push button. We might want to provide a "useful" name for it - by invoking the buttons context menu and selecting name. Now enter something like "btn_quit". There is no need to follow any rules for the names, but I strongly recommend to do so. My preferred scheme for naming controls is "cmd_..." for push buttons ("command buttons"), "lst_..." for list boxes and so on. This will make all controls of the same type being grouped in the controls list of your application. In addition, it provides a simple means of self-contained documentation. Believe me: You'll appreciate any naming convention once you get back to work on a program after - let's say - two months. ;)
Well, we now have the button and we gave it a new name. But "push" being the text on the button? Hm. It might be quite clear that one must PUSH the button to use it and it appears to be preferable to give a clue on what happens once we did. Again: Context menu of the push button - select Text and enter "Exit" or "Quit" or something equivalent - you can even use "bye-bye" if you want.
Now only the actual command is missing. To tell the program what to do, we must bring up the code editor and select the correct place - or simply double-click on the push button.;) Now all that's left is one extremely complicated statement we need to type in: exit
That's it. ;)
Of course this is not the "politically correct" way of terminating a program, as it will immediately stop execution regardless of opened files or dialogs. In our case it's okay, but usually one would prefer to bring up "save changes yes/no" or save some settings in INI-files. All this needs to be done before using the "exit" command.
By now, you might be wondering why ALT-F4 did do it's job although we didn't have an "exit"-command prior to now. A good question, indeed. And the answer is as follows:
Besides the "exit" command, there's another way of termination built into DrDialog: Closing the last opened dialog will terminate the program. And this is why ALT-F4 works in our program: Actually, it will "only" close our dialog window, but due to the fact that this is the ONLY window we've got it will equally terminate our application. So instead of using exit we can equally close the window from within our program code. In order to tell the program to close our dialog, we must provide the dialog's name, which is still "D100" (as far as I remember), as we didn't specify a name for it up to now. Time to change! Bring up the dialog window's context menu and select "name" from it. Enter the name for the dialog, e.g. "dlg_test" or something you prefer. Now let's jump into the appropriate part of the rexx code by double-clicking the pushbutton used to terminate the program. Next, simply replace our exit command by the following one:
This statement actually means, that we want to call the CLOSE method of the control DLG_TEST. Check to see if it works by running the program - it works, does it? Now let's face the question of advantages and differences of both ways to "exit" a program:
Using the "exit" command has the advantage of immediate termination - regardless of open dialogs. Closing the window of course has the advantage of not closing all other dialogs - in addition, using the "close" method will trigger the dialog's "exit" event. Make sure not to mess up the "exit" statement of Rexx and the "exit" event of the dialog. They have nothing in common, except their name of course. By using the close method, we're making use of the same internal processes as ALT-F4 and so on. Thus, we're having one central point of code that is used to terminate our program, regardless of HOW the program is terminated: The "exit" event. However, this event takes place right AFTER the dialog closing was invoked. This means, that we can't avoid the close happening - anything like "Really exit the program yes/no?" comes too late, as we can't get back. But something like "Save changes before exit yes/no" still makes sense - and can't be placed anywhere else better than here: Just before the (inevitable) closing, we're able to do some clean-up. For testing purposes, let's sound a little beep before the program terminates by hooking into the exit event:
- Click mouse button 2 on a free area of the dialog window
- Select events, then exit from the context menu
- In the code editor window, type: call beep 500,100
This will sound a beep tone of 500 hertz frequency for 100 milliseconds. Okay, that's not a really sophisticated close method, but it will help us to find out all ways of closing a program which make use of the "close" event. Try it out: Run the program multiple times and use different ways of closing it (ALT-F4, system menu, "quit" button...).
That reminds me - I complete forgot that one - of the fact, that DrDialog's code editor window provides a nice mean of showing WHAT events were already reacted upon by the programmer: As soon as you start writing anything in an event handler routine which was previously empty, its notebook tab will be changed by adding a "*" (asterisk) to its name. (Example: "EXIT" -> "EXIT*").
That's it for this months issue.
If you're still not fed up or like to play around a little, I would suggest you try to use different settings in the Style-Options for the dialog to see how they effect both appearance and behaviour of the dialog window. If you prefer a melody instead of simple beeps in the exit event, check the "beep" commands documentation within the rexx manual of OS/2 (usually installed during installation of OS/2). As each and every location has changed on my system since I installed OS/2, I'm afraid that I can't tell you the exact folder names to look for simply search for a file called REXX.INF. Usually, it should be placed in the x:\OS2\BOOK path; "x" being the drive letter of your OS/2 installation.
I'm sorry that we did not proceed a lot with the sample program in this issue, but I think that some of the basic concepts of DrDialog require more attention at this stage. Once you understand how things work, you won't run into problems later, even when dealing with more complex applications that might use multiple dialogs.
Next month, we'll extend our sample application by a text entry field, a list box and a second dialog to see what we're able to do with them and what to take care of. Later on, we'll look at the other basic components of GUI dialogs and explore their behaviour by some simple example programs. After doing a small tour on "pure rexx", we'll start with a "large scale" sample application. If you have any idea(s) on what this application could be about, let me know! Up to now, there are two suggestions I prefer: A PIM application (personal information manager; address/date/to-do list) or a GUI frontend application for handling ZIP files (and/or other types of archives). Personally, I prefer the ZIP-frontend, as it does not require special knowledge and it represents a "typical" purpose of rexx or DrDialog.
So stay tuned!