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

From EDM2
Jump to: navigation, search

By Thomas Klein

After a long, long break (exams in IT security and ITIL) I finally found the time to write another part of the series about DrDialog. I am really sorry it took so long to get back, and taking the occasion given here, I'd like to thank those who wrote me an email asking whether there will be any more article. Well - here it is!

As for the series itself, I remember having planned a programming project. I decided to not do the programming project. You see, the problem is simply a lack of time. I am, however, thinking of doing it based upon a well-documented open source piece of software. But that's another story. In regards to this series here on VOICE, I feel it to be more useful to show common programming "tricks" for REXX with DrDialog or extended techniques (like database access) or maybe--talk about extensions, just like what I'm about to do today.

Adding "controls" to DrDialog

You know the standard and special controls that come as part of DrDialog like push buttons, containers, billboard, etc. We have also talked about the rexxutil library and the functions it contains. Altogether, this provides you with an impressive collection of tools you have at your hand to create applications. But sometimes, that's just not enough...

Once your program is doing what you want it to do, you go on and improve its robustness, error handling or performance. You also might extend its features. Of course you could go for online help and tooltips first to improve user support. But at some point, you notice that it actually still looks damn ugly.

Okay, I must admit I can't say about the online help as I've never done that up till now. My own quick-n-dirty stuff never got that far developed that it made sense to add online help. Most of it was self-explanatory--at least to me. And as those applications were never meant for public, there was no need for online help. If someone feels the need to explain how to write help files for use in DrDialog, you're welcome to submit an article to the editor! I'm thankful if it wouldn't be up to me to first learn how that stuff works and then write about it.

But talking about ugly programs--what to do about them? How about showing progress bars for time-consuming operations? What about tooltips? And if your program deals with graphic files, why not enable a preview image instead of just handling the files via their filenames? I can show you how to do that. Right now.

If you're a "contemporary Warper," you should at least have heard of Chris Wohlgemuth. Besides giving us the great CDR-tools, the CWUSB driver suite, and WPS Wizard, he also created a set of controls and functions that can be used to enhance the feature-richness of DrDialog. From the first time I saw them, I was convinced that they are a must-have for every programmer that uses DrDialog.

How extension controls work in DrDialog

Just to let you know: I never have developed an extension on my own. What I am describing here is based upon best-practice efforts, my experience as a developer in other areas and the way I imagine the stuff to work. Okay, here we go.

When Dave Morrill created DrDialog, he also provided a so-called "user control." If you ever tried to use it without knowing what it's about (like me before I knew Chris' controls), you notice that it seems to do nothing. adding a user control Once dropped on a dialog, there's nothing much you can do with it. Hm.

The reason behind this is that this little gem only provides a kind of "universal placeholder for extensions." The problem is that DrDialog does not provide a means to seamlessly integrate extensions into its design-time development environment (like VisualBasic). This means that a User control actually represents all the other possible controls not built into DrDialog.

For an extension to be usable with DrDialog, it must provide special means that enable communication and control between DrDialog's execution environment and the extension itself. The link between the two parts is the User control.

On one side, it plugs into DrDialog's message and event handling system just like all other controls. On the other side, it provides standardized mechanisms for extensions to react to events and communicate with DrDialog's system.

Chris' controls and functions library

In brief here's what it contains (this is an excerpt from the 0.1.5. version's FILE_ID.DIZ):

  • Progressbar control for DrDialog with settable colors, font and label
  • Fly-over help for DrDialog with settable colors, font, delay, etc.
  • Image control for DrDialog to display any image file OS/2 can read. And an additional selection function
  • Directory picker
  • Functions to change parent<->child relationship of dialogs
  • Histogram control for imagefiles

Wow! That's a lot. And it is all contained in a single DLL. Don't worry if there's something you can't figure out right now--we'll talk about it later.

In addition to providing the entire source code to the DLL, Chris has created sample applications to demonstrate the use and look of the controls. All this is contained in a zip file, which... ehh, wait--let me just start a new chapter.

Where to get it

The file you should go for is the 0.1.5 version whose official filename is drdialog-controls-0_1_5.zip. As of the time of this writing, it was available through Chris' website(s) at either one of the following locations:

Follow the links on the site accordingly, download the file and unzip it into a (preferably empty) directory.

How to install it

Actually, the DLL file drctl015.dll is all you need. Make sure it is loadable both from within your DrDialog environment as well as by your compiled programs that might reside somewhere else. This can be achieved by copying it into a directory that is part of the LIBPATH. And this is also what I recommend.

Reason: If you put the DLL into DrDialog's directory, everything is okay as long as you run your program from within DrDialog's development environment. At that time, it's the DRREXX.EXE run-time module that actually loads the DLL. However, as most folks (like me) prefer to store not only their sources outside the DrDialog directory but also the compiled programs, you get an error if you start your compiled program with a double-click. Why? Because now there is no DrDialog environment anymore and the working directory is not [necessarily] the one where the DLL is. If OS/2 can't find the DLL either in the current directory or in a LIBPATH directory: Error. (Unless the DrDialog directory is part of the libpath, but..., come on...)

To circumvent this problem and prevent duplicate DLLs (or even with different versions!) spread over your hard disk(s), simply drop the DLL into a directory where your other REXX DLLs are. Personally, I put such DLLs in the same directory where REXXUTIL.DLL is--by default it's the \OS2\DLL directory which is also part of the LIBPATH.

To check out the sample programs you may prefer to create an appropriate (sub)directory where your other sources or samples are and move the files there. That's all up to you. Unless you're eager to speed-start your own stuff, check out Chris' sample applications first. This also makes sure your installation is correct.

Open DrDialog, then open the file called DEMO.RES that came as part of the zip file. Don't look too much at the code and stuff--just run the program from within DrDialog. Cool. This sample demonstrates both the progress bar control, the flyover help ("tooltip") control and the image control.

What's special about the image control (when compared to DrDialog's billboard control and so on) is the fact that while DrDialog only knows how to handle BMP files, Chris' control can handle all types of pictures that OS/2 has an I/O-proc for (BMP, TIF, GIF and even JPEG if you have the corresponding ioprocs installed--which should be the case if you have the base MMOS/2 package installed).

A small note for those who don't know: JPG file handling in OS/2 is part of MMOS/2--not "base OS/2." To use JPEG files in OS/2 itself (as folder backgrounds for example), you must have MMOS/2 installed--even if you don't have or don't need sound support. This doesn't affect graphics programs however (like Embellish) because they usually come with their own graphics file handlers.

If you have problems with .jpg files in your (MM)OS/2, you might want to also check out Chris' website for the JPEGIO package. This is a drop-in replacement class for MMOS/2 that supports progressive encoding types of JPEG files which the standard classes that come with OS/2 cannot handle. Well, enough of that, let's get back to the controls.

How to use it

Now that we've downloaded and installed the extension library and we already checked out the first sample application, let's take a look on how these controls and functions can be used in your own programs and what to avoid. First, note that the DLL contains both controls with associated functions as well as a set of functions that are not "tied" to controls--like the directory picker for example.

1. Loading the library

Now, as we already know from other "extension libraries" like rexxutil, we need to "load" its contents (functions) to make them available to us. This is the same with Chris' library--except for the fact that it not only includes functions useable with REXX but also controls for use with DrDialog. Thus, you can--or rather must--load both components of the library separately.

If you intend to use one or more of the controls (progressbar, image, histogram, tooltip) you must register the controls:

/* Register all of Chris' controls */ rc = RxFuncAdd("DRCtrlRegister", "drctl015" ,"DRCtrlRegister") call DRCtrlRegister

If you intend to use one or more of the functions which are not "tied" to a control, (e.g. the directory picker) you have to load the functions. To do that you're free to either load only the function(s) needed or all of the functions at once.

And you know what? I would recommend to KISS. :) "Huh? KISS, you say?" Yes, (K)eep (I)t (S)imple, (S)tupid: Just load the whole bunch of functions, don't care about "details," and you're done...

/* Register all of Chris' functions */ rc = RxFuncAdd("DRCtrlLoadFuncs", "drctl015" ,"DRCtrlLoadFuncs") call DRCtrlLoadFuncs

So let's recall that:

  • To use the controls we call DRCtrlRegister
  • To use the functions we call DRCtrlLoadFuncs

And now for the master question: What do we have to call to use both controls and functions? Well...? Right: Both calls!

/* Register all of Chris' library (all controls + all functions) */
rc = RxFuncAdd("DRCtrlRegister", "drctl015" ,"DRCtrlRegister")
call DRCtrlRegister
rc = RxFuncAdd("DRCtrlLoadFuncs", "drctl015" ,"DRCtrlLoadFuncs")
call DRCtrlLoadFuncs

And while we're at it: The DrCtrlLoadFuncs loads all functions. Thus, it also loads the DrCtrlRegister--right? Right! So let's edit that previous paragraph to make it become:


/* Register all of Chris' library (all controls + all functions - short version) */ rc = RxFuncAdd("DRCtrlLoadFuncs", "drctl015" ,"DRCtrlLoadFuncs") call DRCtrlLoadFuncs call DRCtrlRegister

Fine, but... where to put these lines in your program?

This depends upon the type of execution model your program will use. Remember that DrDialog has two ways to start a program? Here's a short reminder on the two ways of "starting" a program in DrDialog:

  1. There is a global procedure named INIT.
    I prefer this type of structure for it allows me to run things before the first dialog is loaded. And in addition, it allows me to control which dialog is loaded first (if there is more than one).
  2. When there is no such procedure, DrDialog simply invokes the dialog which has the lowest ID number (100 by default for the first dialog). Also, if there is no other dialog loaded "manually" from within the INIT procedure, DrDialog automatically loads the first dialog (lowest ID) after the INIT procedure was completed.

While you're free to put the load function into some event handler (like the one that handles the INIT event of a dialog), I would recommend to use the global INIT procedure instead. This is merely for "logical" reasons. In theory, an event handler routine can be called more than once within the "life cycle" of a program. And this even applies to the INIT event of a dialog. Why? Because if you have a program with multiple dialogs, it's possible to load, close and re-open dialogs from within code as needed. Thus, the INIT event might occur more than once which executes the load functions more than once.

Okay--to be honest, I must admit that I did a quick check to see what happens if you load the library more than once: Nothing. No lockups, no errors. But let me just mention that while REXX and DrDialog might forgive you for loading the same DLL more than once, I won't ! ;-) I mean... this is where sluggish programming starts. Keep in mind that loading a library involves the operating system to "do something." In theory, this "something" needs to be done only once--so why do it multiple times just because we're too lazy to care?

Well, in a perfect world, you would create a global INIT procedure, load that library and you're done. But life's far from perfect and theory is sometimes far from practice. Or at least, the cost for perfection is too high. Although I urge you to deal with the principles of the global INIT procedure to fully understand DrDialog's execution model and to use all of its power, I feel okay if you load the library from within a dialog that is opened only once in your program (e.g. it's the only dialog). [sigh]

2. Using the Directory Picker

Well, you can turn the subject any way you want but at the end of the day, programs mostly deal with files.

If the user has to select an existing input file or must specify an output file, that's feasible with the standard file dialog. (I'm afraid we didn't talk about using it with DrDialog--did we? Well, we will...)

Unfortunately, sometimes you might want to just let the user specify a directory only, because the "rest" is being taken care of by your program. For example: The configuration file for your program is always named MyProg.INI and you just want to ask the user "Where do you want the program configuration file to be stored?"

In such cases, dealing with the standard file dialog is clumsy because it allows the user not only to specify drive and directory but also a filename. Whatever was entered through the dialog must then be "validated" (i.e. to remove a potentially entered filename) to get only the drive/directory.

Now if you already have designed such "directory dialog" on your own, you'll certainly reuse it in your new programs. Fine. If you don't have such a dialog in your existing code repository and you don't need that dialog to perfectly fit the "look and feel" of your programs user interface (or you simply don't care), I recommend to save a lot of work by simply using Chris' directory picker.

The directory picker actually comes as a simple function call: You pass it an initial path and title string and it returns you a string variable that is either empty (if the user canceled the selection) or it contains the drive and path of the directory that was selected. If you did not already load the whole set of functions and you only require the directory picker, you can load that function only by using this code:

rc = RxFuncAdd("DRCtrlPickDirectory", "drctl015", "DRCtrlPickDirectory")

Once the function is available, you invoke the dialog by the following call:

thedir = DRCtrlPickDirectory('c:\temp', 'Select directory for temporary files')

At run time this brings up the directory picker's dialog and the function call does not return until the user either cancels or completes it. To know how the dialog was exited, simply check for the return value:

thedir = DRCtrlPickDirectory('c:\temp', 'Select directory for temporary files')
if thedir =  then
  say 'Dialog was canceled'
else
  say 'You selected ' || thedir

Well, that example is quite useless in practice, but it gives you an idea how the directory picker might be used in your applications.

The main question that might pop up with the directory picker properties is:

What if the directory specified in the call (the 'start' directory) does not exist?

Well--Chris' directory picker is almost "foolproof": If the directory passed to the function does not exist, it simply uses the current directory, whatever this might be at the moment. At least, you won't find yourself with unpredictable behavior. ;) If, however, the directory is on a drive which does not exist (e.g. disconnected network drive or removable drive which is not ready), you'll see a "drive not ready" error message popping up before the directory picker dialog appears... so it's not as predictable as I said.

That concludes the directory picker. And no: You don't need to unload the function... there's no function call for that anyway except for unloading all functions from the library.

Using progress bars

Okay, now we go for the user interface extensions that come with Chris' library. The first one we'll discuss is the progress (or percentage) bar. As mentioned before, we need to load the library in the program first:

rc = RxFuncAdd("DRCtrlRegister", "drctl015" ,"DRCtrlRegister")
call DRCtrlRegister

So far so good, but... how the heck can we drag-drop the progressbar onto our dialog? There is no "progress bar" control available in the controls toolbox at design-time! Wait... ehh... what was it we said above? User controls!

Drag-drop a User control, size it and move it to the desired location.

Now right-click it and select Style....
user controls style
In the class entry field type DRD_PERCENTBAR and hit <return>, or click OK.
setting class for user controls
That's it for the user interface design.

Hm. Fine--but how to set color and other properties of it? By code! You have to use the functions from the library to "communicate" with the control. This means that you won't see anything on the screen unless you run your program from within DrDialog. As a matter of fact, let's take a look at what to code. There is a total of only 3 functions available with the progress bar--but they do all we need:

  • color
  • text
  • font

To have all information in one place instead of referring you to the DrDialog INF file, I'll do a short summary of the color and font functions (the text requires a little more detailed explanation, so we'll get to that later).

Color is used in two calls to set

  • the foreground color (the color of the text displayed on the bar)
  • the background color (the color of the percentage bar)

Colors are specified by using the RGB-style. This is a set of three values ranging from 0 to 255 each for the (R)ed, (G)green and (B)lue amount of the color in question. Black equals 0 (zero) for all values and white equals 255. In addition to that, you need to indicate whether you refer to the foreground color (parameter equals "+") or the background color ("-"). Here's an example:

call color "-", "#255 255 255" call color "+", "#0 0 255"

The above code (preferrably in the INIT event handler of the percentage bar control) sets the bars color to white and the text displayed on top of it to blue. To find the values required for the colors of your choice, either use the Color Palette's color wheel, or use a graphics program; they all have such functions available.

Setting the font is quite straightforward although a special notation is required. Basically this is an integral point size value, a dot, and the font name. This is passed as a single string:

call font "8.Helv"

sets the text display font to use Helv with a point size of 8.

Hint: To find the matching names for your desired font, simply them look up the font palette.

While color and font are used just like with all other controls in DrDialog, the text function requires a special syntax to set both the percentage "bar" as well as a text that the control shows. Basically it's like

<percentage>[#text]

which means that the percentage value (e.g. '50' for fifty percent) is mandatory while the text to be displayed is optional. If you want text to appear, it must separated from the percentage value with a hash [aka: octothorpe, pound sign]. Thus, if you want the progressbar to initially show an empty bar and a text like 'initializing...', it would be coded as:

call text '0#initializing. . .'

Setting the percentage bar to 50 percent along with the text of "50%", it would be coded as follows:

call text '50#50%'

Oh, yes, talking about 'initial' values...:

One might suppose that color, font and text might be set via the context menu of the percentage bar control at design time--just like with other controls. But that's incorrect. If you invoke the context menu, you'll notice that Font and Text are available there (not Color) but once you specifiy an initial text content, for example, nothing will be seen--not even at at run-time. So let's note that:

Text, Color and Font for the percentage bar (or progress bar) control are only settable by code at run time!

And another note:

Remember that you need to fully qualify the controls name when setting (or querying) its properties!

Normally, you would set the percentage bar from within a function or routine which handles the "progress." Imagine an address book application for example: When the user requests to save all contacts into a file, you want the program to show a progress bar. To do that you might call a routine which

  1. retrieves the total number of contacts
  2. saves one contact after another
  3. calculates the percentage completed with each contact saved
    (sum_contacts_saved * 100 / num_total_contacts)

To set the progress bar from within such function, you must tell DrDialog exactly what control you're talking about by using fully-qualified control names, like this:

<dialog>.<control>.<property> = <value>

This the way it should be done. At some later stage, we might take a look on how to address controls "dynamically." The above notation is mandatory to deal with that--but for now, simply note that DrDialog supports qualifying controls also without the dialogs name. So

<control>.<property> = <value>

works too. Basically, this is because all controls and dialogs are "global" within the DrDialog environemt--thus, global to DrDialog itself. Example: If you have a pushbutton that was named btnExit on one dialog, you can't name another push button the same even if it is located in another dialog within the same project.

Assuming that your percentage bar is named prcnt_saved and it is contained in a dialog named dlg_main, your function call to initialize the percentage bar will look like this:

call dlg_main.prcnt_saved.text "0#initializing..."

or like this:

call prcnt_saved.text "0#initializing..."

but this:

call text "0#initializing..."

only works if coded in an event handler routine of the control itself like INIT, but not from within a global routine or an event handler routine that belongs to a different control (like a pushbutton for example). Okay. Back to work:

As you see, setting an actual value means 'fiddling' with strings because you need to compose the string on the fly whenever the percentage changes. Note that the actual calculation of percentages is up to you! As an example, let's just code a small demo program. We need:

  • a percentage bar (of course!)
  • a "run" button
  • some code ;)
Step 1
Start a new project in DrDialog. You get an empty dialog. Name it dlgMain by using the context menu -> Name....

Step 2
Drag-drop a push button onto the dialog, name it btnRun and move it to any location you prefer.

Step 3
Drag-drop a User control onto the dialog, name it prcMain and move it to the desired location and size it to a thin horizontal bar. Then from its context menu, choose Style... and in the class field enter DRD_PERCENTBAR.

Step 4
Create a global routine named INIT and add the following code:
rc = RxFuncAdd("DRCtrlRegister", "drctl015" ,"DRCtrlRegister")
call DRCtrlRegister
Step 5
In the INIT event handler of the percentbar control code:
call text "0"
call color "-", "#0 0 255"
call color "+", "#255 255 255"
Step 6
In the CLICK event handler of the push button code enter:
call Process
Step 7
Create an additional global routine named Process and paste the following code into it:
call btnRun.disable

maxval  = 205
currprc = 0
oldprc  = 0

do i = 1 to maxval
  currprc = trunc(i * 100 / maxval)
  if currprc \= oldprc then
    do
      call dlgMain.prcMain.text currprc || "#" || currprc || "%"
      oldprc = currprc
    end
  call sleep 10
end

call sleep 1000

do i = maxval to 1 by -1
  currprc = trunc(i * 100 / maxval)
  call dlgMain.prcMain.text currprc || "#" || i || " steps remaining"
  call sleep 5
end

call dlgMain.prcMain.text "0#demo completed!"

call dlgMain.btnRun.enable
Step 8
Run and watch it.

What is does is show a combination of what is feasible with the percentage bar. In the first run it simply performs a loop by increasing a variables value from 1 by 1 until the maximum value of 205 is reached. Each time the loop is passed, the current percentage is calculated.

When the current percentage differs from the previously displayed percentage, the bar is updated and the current percentage is moved to the "old" percentage to compare with future values.

Wait - why compare?
Because you don't want to repaint the progress bar if the percentage hasn't changed, do you?

When the first run is completed, the program pauses for two seconds. Then within the second run, we simply "go backwards" with the percentage bar but we don't display percents but rather the actual counter. To achieve this, we "internally" still need to compute the percentage (to satisfy the needs of the percentage bar control) but the textual display does not care about that.

The "bonus value" of the demo program: Did you notice that the run Button is disabled during the processing? This is a very simple yet foolproof way of making sure that the process can be started only once. Imagine that the button is not disabled. This would make it possible for the user to start the processing over and over again while it is already running. Now, don't ask me what the result would be... you might give it a try yourself. As far as I'm concerned, I'd call that "unpredictable behavior." But as this demo shows, it's quite simple to avoid.

In addition, this is what I call "a good style" of UI programming because the user interface reflects the program's state, thus it gives feedback to the user and also self-explains that this processing can not be restarted until it has finished.

The demo is just to show what kind of options you have with the percentage bar control and it gives you insight on how to use it. Note that I used the trunc() function in the percentage calculation to get integer values: Trunc() simply returns only "the part in front of the decimal point"--like the int() function in Basic.

If you're curious: Resize the percentage bar to make it be taller than wide--Chris' control automatically displays a vertical percentage progress! Great stuff though there isn't enough space anymore for long text. It is however sufficient to display the numerical value of the percentage in most cases.

Final hint:
Chris' control always centers the text display both vertically and horizontally.

4. Using the image control

While DrDialog by default only deals with bitmap (.BMP) files, this control enbales all image types that are supported by OS/2. Well, actually it's the image types that are supported by MMPM/2, but that doesn't matter for us. The initial process of usage is quite similar to the percentage bar.

Besides the need to load the library, you also need to drag-drop a User control onto your dialog, size and move it as required. Then invoke its context menu and select Style.... Enter DRD_IMAGE into the class field. Next, you should assign it a name. As I prefer to prefix the name with the control type, I would name it "img"-something like imgPreview for example if the image control previews graphics.

The image loaded into the control is specified via the text() function of the control--you simply provide the fully qualified file name and you're done. To load C:\MMOS2\IMAGES\ARTDECO.JPG, the call to the text() function would look like: call text "C:\MMOS2\IMAGES\ARTDECO.JPG"

And of course, if you load the file from a global routine, it would look like:

call imgPreview.text "C:\MMOS2\IMAGES\ARTDECO.JPG"

That covers the basic usage. Once you have completed your first tests with using the image control, you noticed that it automatically stretches or shrinks the image to fit the control's size. Okay--in most cases, this is not desirable. So how are we supposed to change that behavior?

Well usually, you might want to get the image displayed proportionally at least. This implies the need to know the images dimensions--width and height. Phew. And how do we get to know these? There are--as far as I currently know--three ways to solve the problem which actually means three additional DLLs to load. They provide the necessary functions:

  1. gbm (the generalized bitmap module) by Andy Key
    It provides VIO commands (text-mode programs) to retrieve the dimensions. This means that you need to execute them by "piping" their outputs to retrieve the data. This works but needs a considerable amount of "workaround." Next, you would have to calculate the controls dimensions according to the what GBM has returned for the image to maintain an aspect ratio that fits. Then resize your control and load the image.
  2. rexxweb by Igor Pool
    This is a DLL (just like what we are talking about here) which provides a lot of useful functions. While primarily designed for OS/2 web server backend processing, the getImageSize() function contained in it does just what we need. The really interesting point is that you can pass it maximum dimensions. In return, it gives you the calculated proportional ratio of the width and height of the image that you specify. This means that you pass it the image file name along with the width and size of your control. The return tells you the file format (GIF, BMP and JPG are supported) along with the width and height that fit into your control. All you need to do is resize your control to the dimensions returned by the function and load the image. Great stuff.
  3. XtraRexx by Martin Kiewitz
    Again a DLL including lots of functions. The one I reference in this context is XtraGetPictureInfo() which also returns a string holding the picture type, the picture's width and height in pixels and the color depth. This is represented as, e.g., "JPG 600x480x24". Supported formats are PNG, JPEG and GIF.Note that the calculation of the "target" dimensions of the image control by matching the aspect ratio again is up to you, just like with gbm.

In addition, Chris' image control also supports selection of a part of the image displayed. I won't discuss the selection feature here in detail. If you're interested, simply look at Chris' readme file for the controls, there is sufficient information available. I'll just do a basic introduction to that subject.

Basically, you first have to enable that feature with a special syntax for the text() function. Then upon selection by the user, you retrieve the coordinates of the rectangle that was selected--again by using the text() function. This feature lets the user select a part of an image displayed and crop that selection into a new image by using appropriate functions (like those from GBM for example).

5. Using the fly-over help ("tooltips") control

They're everywhere. And frankly speaking--they're quite useful. So why not incorporate them into our own programs?

Or like Balmer would put it: "We need tooltips! Tooltips! Tooltips! ..."

Chris' implementation of tooltips works by using the Hint-feature of DrDialog. A hint text can be specified for almost every control type within DrDialog and is limited to 127 characters. Using a special call of DrDialog, you make a specific control become the default output "area" for hints. An example for how that works is available right in DrDialog itself: Whenever you move the mouse pointer over a control of your dialog or the controls selection window, an appropriate info text is displayed in the status line of the DrDialog background window. Now what Chris' control does is, to

  1. become the default output area for hints
  2. create the visible output area dynamically at the mouse pointer
  3. handle visible aspects and duration of the display

But how to set the hint text for a control?setting a controls hint text

Invoke the controls context menu at design-time and select Hint.... Then enter the text desired. This is the text that is displayed in the "output area" whenever the mouse pointer is over the control (at run time). Note that since version 0.1.3 of Chris' library, the hint texts can be multiline. But to achieve this, you need to include a line-feed character in the text which is--unfortunately--only possible from code thus not possible at design-time. Anyway, here's how to do it from code:

call hint "This is a two-" || "0A"x || "line hint."

Now that we know how to specify the hint texts for controls, let's see how they are displayed as tooltips.

And again: Yes, you have to load the library to use the flyover help unless you already loaded it for one of the other functions. And you have to put a User control onto your dialog, select Style... from its context menu and enter the class of DRD_BUBBLEHELP. While you're at it, name it something like hlpTooltip.

Next, as I said, you have to make the control become the default output area for hints.

There's two types of hints in DrDialog (dialog hints and control hints) so there's two calls to make:

call IsDefault "D" call IsDefault "C"

Preferably, you'll code them into the INIT event handler of the DRD_BUBBLEHELP user control (or the hlpTooltip). That's it.

Well, basically. The more detailed function calls deal with the delay before the tooltip shows up and the time it is displayed before it disappears again. The popup delay is specified in milliseconds like this:

call text "#delay 250"

This makes the tooltips appear of a second after the mouse pointer stays over a control. The display duration also is specified in milliseconds but uses the parameter prefix #show:

call text "#show 3000"

This shows the tooltip for 3 seconds.

Remember that the actual "text" of a tooltip is derived from the Hint... that was specified for each control. The remaining features that are available deal with the colors and the font used for the tooltips. The font is set just like with the percentage bar we discussed above:

call font "8.Helv"

Oh--by the way--the height and width of the tooltip "window" is determined automatically by Chris' control through the font size and text content (the hint property).

Now for the colors--this also works like with the percentage bar: Foreground color means the text while background color refers to the tooltips window background color:

call color "-", "#63 63 63" call color "+", "#63 255 63"

This makes the tooltips show light green text on a dark grey background--at least in theory. For some strange reason, I can't make the text show any other color than black. Hmmm... seems like I should investigate that further.

As these calls are valid for all tooltips in general, why not simply code them into the INIT event handler of the tooltip control as well? Then the contents of the INIT event handler looks like this:

call IsDefault "D" call IsDefault "C" call text "#delay 250" call text "#show 3000" call font "8.Helv" call color "-", "#63 63 63" call color "+", "#63 255 63"

Yes, beautiful.

6. Using the image histogram control

To be honest, I have no idea what to do with the image histogram control. I don't know much about histograms although I know of them from image processing software. I suppose this was added by Chris upon special request from the team that gave us Tame/2, the SANE frontend application. And I guess that the image control along with its selection feature also was suggested or requested by them in the first place. Anyway, this is a nice example of how all people can benefit from a few persons' needs.

If you are interested in the histogram control, you have all the required information at your fingertips. As the histogram control is quite similar to the image control, you already know almost the half of it--the rest is contained in Chris' readme file.

7. Using the parent/child functions

This is great stuff. If you've ever "messed around" with multiple dialogs or tried to create certain a interface style with them like tree-styled notebook controls ( la Mozilla) you certainly have noticed the limits of standard DrDialog, right? I will not write about Chris' functions here. The simple reason is that to understand what it does (and its benefits), we need to discuss "pure" multiple dialogs in DrDialog first. So be patient, I'm about to take a tour on the subject, find out how to give you a start into it and then I'll be back.

Redistribution. Pardon, re-what? ;-)

If you intend to share your program with others, don't forget to include the libraries in your package! Or at least, make appropriate notes in your readme, fileid.diz or help file along with a download location for the libraries if they are not with your program distribution.

Also, if you don't use installer packages and provide the DLL on your own, you should give some short description on where to put the DLL within your setup notes. Your possible users can either keep the DLL in the program installation directory or (what I would suggest) copy or move the DLL to a directory that is part of the LIBPATH. An while you're at it - give credits to Chris for his work.

And what's coming up next?

Ehh... I really don't know. I'm thinking about taking a look at using multiple dialogs and the tricks and traps involved, which would also cover the remaining functions from Chris' library (setting parent-child relationship of dialogs). At some later stage, we either check Igor Pools rexxweb function library or we might pick up the work done by fellow writer Wolfgang Draxler and discuss database access to MySQL using the SourceForge rexxsql package (by Mark Hessling). But either way--I need to get a start on that on my own before writing about it. So be patient, please.