VOICE Home Page: http://www.os2voice.org |
June 2004
Newsletter Index
|
By Thomas Klein © June 2004 |
In this part of our series, we won't directly get back to DrDialog programming (contrary to what I said in the last part - sorry). I think it's best to provide you with a "smooth return from pure REXX" to DrDialog's development facilities: A long time has passed since we've dealt with the development environment and the principles of how the whole matter works. In addition, today we'll have a rather "theoretical" discussion about specific properties of DrDialog and - yes - I'll introduce you to what I've chosen to become our sample program that we'll be creating together in the upcoming parts of the series. For that reason, it's a good idea to take another look at how DrDialogs workspace is organized. It'll save us time and "gray cells" if I can assume you to be familiar with where the right buttons are....
Before proceeding, I strongly recommend that you read Chris Wohlgemuth's letter in the previous issue of the Newsletter concerning the rexxutil functions! Thank you Chris for sharing your wealth of knowledge and experience with us! Especially when dealing with computers and programming, we all should know that the there's one thing that applies for you, me and all of us - regardless of how "good" we consider ourselves to be in whatever matter: You live and you learn. This time, thanks to Chris who gave us the chance to do so. And again, I can't keep myself from repeating it again and again: Feedback is always welcome! If you're an experienced programmer (or user) and come across something that you feel is not accurate or that you could provide us with additional knowledge, you're welcome to go for it by writing a comment to either me or the editor - like Chris did. By the way: Did I mention that he's the only developer left "out there" who still extends the functionality of DrDialog? We'll talk about his set of functions in an upcoming part of this series.
In the very first parts of the series we already learned about how to use DrDialogs development environment. For this part (and all remaining parts) we first need to settle on a common ground for dealing with the development environment.
I assume that you know how to access DrDialog. Let's make sure that you also know how to access the following parts within the development environment:
the
run time monitor window
the controls window
the
code notebook (or "code editor" if you prefer)
All of the above windows are available from DrDialogs main window menu item called Tools:
Note that you can select to leave each of them opened (floating) and even let DrDialog open them automatically when the IDE (integrated development environment) is started. This is done for each window by using its system menu:
The dialogs window:
In addition, when dealing with multiple dialogs within the same program, you'll need the dialog selection window as well. This one is accessed by using the button (shown to the left) from either the tools menu or the tools window. The other windows (like color selection etc.) aren't necessary in general for now.
So far, we learned about the style settings for a dialog and what they mean. But this is
so-called "design-time" stuff. In order to handle multiple dialogs in our program, we
need to know what can be done with a dialog at "run time" - from code that's to
say.
In order to understand DrDialogs behavior, we first need to understand, that almost everything we
code in DrDialog is attached to an event handler in one way or another. What happens within
DrDialog when a program is started? Where does it start within all those event-related
"loose ends" of code? How does it terminate? Again, in order to understand, we need to
distinguish between two "types" of code in DrDialog:
As its name implies, the event handler code parts are attached to an event and are executed
automatically each time that the event occurs. More importantly (unlike in VisualBasic for example),
an event handler code cannot be called from somewhere manually unless you actually
"trigger" the event if possible (like opening a dialog window to trigger the
INIT
and OPEN
events).
This means that while we can refer to a specific method or property of a control from anywhere in
our code, we can't call an event handler directly... "Pardon?" you might say...
okay, here's an example:
We want a dialog that displays the current time in a text field. So at load time of the text
field, we simply make it contain (display) the current time by using the REXX function call
time()
. In the INIT
event handler of the text field (assuming its name
to be txt_tst
) we thus code:
call txt_tst.text(time())
You might give it a try: Wow! It works.
Now at some point in the program, we discover that quite a bit of time possibly has elapsed since
we started the program and decide to include an "update time" button beneath the text
field. All it should do is do the same as the INIT
event handler of the text field
does: Make the text field contain the current time.
Fine - we're programmers and we're lazy and we're thrifty - so why not simply call
the INIT
event handler in the pushbuttons CLICK
event? Oops...
that's where we're stuck: There's no call to do so. The INIT
handler is
only called by DrDialog's event handler queue system (or "message queue" in brief).
Okay, okay...: So let's do it the other way round and include the actual code in the
pushbuttons CLICK
event. Then, change the INIT
handler of the text
field to call the CLICK
event of the pushbutton... and: Darn - stuck again...
Well, this is not tragic: It's only a single line of code. We could simply duplicate it
and put it in both the text INIT
and the buttons CLICK
event... but...
what if there's more code than a single line? Do you really want multiple copies of entire
paragraphs in your program? Looks somewhat silly, right? In addition, if you find a bug in that
routine, you'll have to correct it multiple times. That's not "smart." And
that's where it comes to the global code part:
The global code contains subroutines and/or functions which are not attached to events. In
addition, they can be called from code manually and are accessible from everywhere in your code -
regardless if you find yourself in an event handler somewhere or in another global code part.
The solution is to put the time()
-code in a separate subroutine. Then, in both event
handlers, we simply call that routine. In order to create such a global function (or subroutine),
we need to switch the code editor from the event-related parts to the global parts by clicking
the "globe" icon:
The code editor will now display tabs for all global routines defined so far. If there are none, you'll see an empty window. The entry field at the top of the window is used to type in the name of the function that you want to edit or create. Type in the name you want the function to be called - e.g. "set_curr_date" (without the quotes) and hit return. You'll notice the code editor to create a new tab with that name.
In the code entry field you then can type the code for that function. In our above case it would read
call txt_tst.text(time())
...wrong!
This routine is global. Thus, it can be called from anywhere in our program. In
effect, this means that the routine doesn't know about the current "control
context" that it'll be run in: It needs to know the "full qualified name" of
the control it should deal with - that's to say: The dialog which contains the control
plus the controls name in order to be able to access that specific control. We thus need
to prefix the controls name with the name of the dialog that it's contained in. Assuming the
dialog being named MyDialog
, it must read
call MyDialog.txt_tst.text(time())
Fine. Now you might remember that we don't need to name the dialog. In this case, we can still refer to it by its ID number in the form of "D100" for example ("D" is the prefix for a dialogs, 100 is the ID number). The id number of a control (or dialog) is displayed in the bottom status line of DrDialogs background panel once the mouse pointer is on top of the corresponding control:
If the text field has no name, it can be accessed by its ID number as well: Controls wear a prefix of "C" along with their ID number. If the text fields id number would be "101", the full qualified name would be:
D100.C101
and thus, the global function code would read:
call D100.C101.text(time())
Now that your global function code is complete, you don't need to "save" it someway (except of course before you close DrDialog or edit another program). DrDialog stores it automatically once you switch from the function to another part of the code editor. Note that if you don't enter any code, DrDialog will automatically remove the (empty) function from the list of global functions. You'll need to recreate it - but as there was no code in it... no problem.
Before going on, let's see how to call that new function from within both event handlers: All you need to code at this point is to put
call set_curr_date
in both event handlers. Done.
This might not be the perfect example to demonstrate all benefits of a dedicated function. Simply
imagine that there's dozens of lines of code instead just one. Also note that we have
complete control of that function and that this allows us to make it support arguments if we want
to do so. This means that we can modify the function to behave in different ways if called with
different parameters.
This was an example for a global routine/function and how it is created. Now that you know the differences between event handler code and global routines... let's get back to the initial question: How does a DrDialog-made program actually start?
Well, DrDialog first looks in the global part, if there is a routine called INIT
.
If there is one, it'll be executed and thus will become the "initial" event handler
if you want to put it that way.
If there is no such function, DrDialog will load the dialog window with the smallest "id
number". As the existence of a dialog window starts with an INIT
event (because
the dialog window is first INIT
ialized before being displayable) the dialogs
INIT
event handler will be the "initial" event handler of your
program.
How about the program end? There's two ways: a "manual" way and DrDialog's
"built-in" way.
The built-in way consists of DrDialog checking the dialog window list. When the last existing
dialog window is closed, the program will be terminated.
The manual way consists of a single command: "Exit" (without the quotes) will make
the program terminate. Caution: This will explicitly terminate the program immediately - so
please behave in regard of opened files if any! Don't issue the exit command from somewhere
deep inside the programs code. That's what sluggish programmers do. You should always try to
quit your logic in a "controlled" fashion.
One never knows of course, but actually with DrDialog you shouldn't find yourself in the need
of that command if your program's logic is "intact".
But as with every rule, there's an exception:
Assuming you don't want any dialog windows in your program because it simply does batch
processing, you should rather use "pure REXX" instead of DrDialog. If, however, you still
prefer to use DrDialog, you're faced with a special property of it, that we already discussed
in one of the initial parts of the series: DrDialog automatically creates a default dialog
window. If you remove it, it will create a new one.
This is due to DrDialog being entirely event-driven, thus relying on a dialog to start from.
You're free to set its style to "not visible" and simply do all the processing
based upon a set of "global" routines and the global INIT
routine as your
starting point. But this won't work, because the program won't terminate with the
completion of the last command of the INIT
routine. Instead - once the last command
was completed - the "neglected" dialog window will appear, although initially being set
invisible. Oops. In order to overcome this "problem" use the EXIT
command
as last command in your command sequence: That'll learn it! Or better: Simply rely upon
"pure REXX" if you don't need any dialogs.
So this is what needs to said about working without any dialogs. But what, if you need
multiple dialogs in your program - like a main screen, an options dialog and maybe a
"splash" page (or logo/about screen if you prefer)? What needs to be considered?
In this case, we need to know how to control dialogs from code: opening them, closing them,
making them "lock" the program to a dialog shown in front of others. To be able to
cope with our expectations, we must know how DrDialog behaves in matters of dialog control and
event handling. (This is the "theoretical" part mentioned at the beginning. Although
being not really easy to read, you should work yourself through it to understand some basic
principles of how DrDialog works...
DrDialog is event-driven and uses a message (or "events") queue. In general this
means that events are stored in a queue and are processed based on the order that they appear in
the queue. That's what other programming languages will do in a multitasking graphical
enviroment as well. What's special about DrDialog is the way in which this queue is
"embedded":
I'll try to explain it my way - if there is somebody out there who knows a better (or
"right?") way to describe it, he's welcome to let us know. Well, okay... DrDialog
uses the operating systems rexx interpreter, which means that there's only one single queue
for it. Because the GUI-related events are processed by DrDialogs internal runtime which runs in
the same environment, the same queue is shared for processing rexx-commands as well as events.
This leads to some kind of different behavior in matters of "synchronicity". If you
take VisualBasic for example, you'll notice that commands and events are treated the same way
in matters of program sequence: If in a global routine or somewhere else you OPEN a dialog from
code, VB will immediately invoke the whole processing in a nested way and then return to the
command following the OPEN. To give you an impression of how this would look like, here's an
arbitrary command sequence:
- some command 1 - OPEN Dialog "A" - some command 2
VB would process it like this:
- process some command 1 - Call to OPEN dialog A: -- process INIT event handler of dialog A -- process OPEN event handler of dialog A - process some command 2
Don't worry - you'll see what this is about if we now check the sequence that DrDialog
will use.
DrDialog uses a different approach. Because both the REXX code of your program and the possible
events share the same queue, the REXX commands of the current procedure have "priority"
over the events triggered. This means, that DrDialog will queue all GUI-related events that refer
to different dialogs for later execution - while going on with the REXX statements of the current
event handler. In our above example, we had the following sequence of commands:
- some command 1 - OPEN Dialog "A" - some command 2
DrDialog goes like this:
- process "some command 1" - append "OPEN dialog A" to the queue - process "some command 2" >handler finished< - Retrieve "OPEN dialog A" from queue -- process INIT handler of dialog A -- process OPEN handler of dialog A
You get the difference? In practice, this behavior leads us to the following problem:
As we're used to think of processing in a "procedural way", we assume it to be
feasible to make a program sequence wait for a user input. While this is right, it won't work
that way with a dialog of DrDialog. So if you require user actions (like inputs) from a dialog in
order to proceed a specific sequence of commands, you can't simply invoke a dialog, wait for
the user to click some "ok" button and then return to the calling procedure, because
DrDialog will first complete the current procedure before displaying the dialog
window.
Making a program wait for a user input is batch programming by the way, and DrDialog is event-driven. Thus, the right approach to solve the problem is not to find out how-the-heck you can make the processing wait for the user input... but rather start the processing from off the user input, thus: From off the dialog!
Another reason behind this is, that we are running multitasking and
multithreading operating systems. Instead of having a program that waits and waits for a
user input (which means that it somehow consumes cpu resources) it should rather do
nothing (giving cpu time to other tasks) until the user requested some kind of action.
In practice, this would mean that you display the password dialog and stop. That's it. The
remaining process will be triggered once the user clicks the <OK> button
.
Thus, the processing is not part of some routine that implicitly displays the password input
dialog, but rather part of what happens when the user has completed password input. Know what I
mean? ;)
Okay. So far, this also somehow applies to having to deal with a single dialog. Now here we go with something more interesting....
Imagine that you came up with the idea of a new personal information manager program. You actually don't have any clue what it will work like in the end but you already know, that you need
You should start by figuring out, how these dialogs depend on each other and the sequence in which they will appear when the program is started. In order to complicate this task, we assume that you want to do the following:
That's a lot. And that's just a subset of the stuff compared to the "boring"
parts like reading, writing and the presentation of data.
And that's execatly what we will be doing as our sample program.
Gee... poor me! That'll cost me another dozen of articles! Why didn't you come up with
something easier? ;)
Before leaving you with great expectations and lots of questions, let's have a look at
another bit of knowledge about DrDialog:
Now that we know that we can't call event handlers "manually" like other functions,
it might lead us to another question: How to pass information from one dialog to another? This
would be great like for example opening a dialog and passing it some parameters like position,
size, title and so. No way. At least, no direct way to do so.... The answer is: Global
variables.
In most programming languages you will find special ways of "telling" if a variable is available to the current function only or from anywhere within the entire program (= being "global"). In DrDialog this is kept very simple: All variables declared by you are "global" automatically. It doesn't require specific commands or syntax to make a variable become "global". But beware - there's no way of making them become "local" in return: If you alter the contents of a specific variable in one event handler or function, it'll be changed "everywhere".
Would you like a quick-n-dirty sample? Here's one:
Start a new program in DrDialog. The default dialog will be created automatically. Now, we'll
create an INIT
function in the global code part (see above how this is done) and
make it contain the following command:
MyDataField = "Hello"
Then, we'll dragdrop a pushbutton onto the dialog. It'll read Push
. Now
double-click it to bring up the code editor with the event handlers for that button. Switch to
its INIT
event handler by clicking on the appropriate tab. Then enter
call text MyDataField
and run the program. You'll notice that the pushbuttons text will become the contents of
the "global" variable we set up in the INIT
routine of the program.
That's it for this part. I hope you're looking forward to next month. If you ever wanted to know how to make a dialog become "application modal" (locking the program to one dialog only) in DrDialog make sure to be part of the party! ;)
Feature Index
editor@os2voice.org
< Previous Page | Newsletter Index | Next Page >
VOICE Home Page: http://www.os2voice.org