Mastering Dialog Windows in Xbase++
Part 2 - Techniques for MDI Style Applications


Copyright © 2002-2008 Clayton Jones
by Clayton Jones

Revised November 19, 2008

Introduction

The material presented in this article was demonstrated at the first US Xbase++ Developers Conference in New Hampshire in October, 2001.  The presentation was well received and I decided to expand the material into a series of web page articles.  The intent of this article is to help new Xbase++ users get productive more quickly by clarifying a subject that is not well documented and by providing some good programming tips and techniques for working with dialog windows.  

In Part 1 we established the terminology and definitions for the different window types and how they are used.  Here we will look at the pros and cons of using these different types to achieve control of multiple layered windows and other designs.

I am assuming that the reader is familiar with the how and why of running dialog windows in separate threads.  If not, I recommend first reading the series of two articles on that subject, "Dialog Windows in Recycled Threads" and "Data Entry Dialogs in Event Driven Applications".  Both can be found on the same Articles page as this series.
 
The Central Challenge
The most fundamental window construct uses the three primary window types: Main, Child and Modal.  Typically the Main window has a menu from which Child windows are called.  If another window is needed below a Child, then a Modal is called.  More than one child can be open at once, but only one Modal can be open at any time, because a Modal disables the rest of the application and prevents any other activity until it is closed.  This diagram shows a Modal, called from Child 1, as the only active window.  All others are disabled:

                                                                     Main            
                                                          /              |               \
                                               Child 1          Child 2         Child 3

                                                    |
                                             
 Modal


In a document-centered application (MDI means Multiple Document Interface), such as a word processor, this architecture is adequate.  Child windows display the documents, and Modals are used for pop up message boxes and various utility windows.  Database applications, however, are an entirely different matter, and more levels of windows are often needed. 

In DOS it was just a matter of drawing another box on the screen, but here everything has to be a window object, subject to MDI rules.  Suddenly we come up against the fundamental challenge: How to have more than three levels?  In addition, we must be able to have multiple levels under more than one Child window at once

There are a variety of solutions for multiple levels, such as calling another Modal from the first one, but many have undesirable side effects.  I will first present the technique which I find to be both the easiest and most powerful, and then discuss the pros and cons of others.
 
Multiple Event Loops and Threads
There has been much debate about whether it is advisable to have more than one event loop in an application.  Some people adhere to the one-loop philosophy and predict dire consequences for using more.  The one-loop idea, however, is rooted in the document-centered architecture which we have already found to be unsuitable for our needs.  Using multiple event loops is perfectly safe as long as one understands how they work.  Not only are they safe and easy to use, but they are a necessary part of using threaded windows and multiple layers.  Here are the guiding principles:

a) It is desirable to use threaded windows because it is the easiest and most trouble-free way to avoid record pointer/alias conflicts when opening data entry windows more than once (assuming that data files are opened each time the window is opened).  Each thread has its own Work Space and encapsulated, protected Work Areas.

b) Each thread has its own event queue, and therefore requires its own event loop.  So any window opened in a new thread must have an event loop.

c) There are no event loop problems as long as there is only one active window with event loop in a thread.  The key word here is "active".  Problems occur when more than one window with an event loop is active in the same thread.
 
Procedural Behavior and Loopless Windows
The term "procedural" is generally used in the phrase "procedural programming", to describe a  program architecture where only one window can be open at a time (often in emulation of DOS programming style).  I am using it here in a different sense to describe "procedural behavior", when a routine in a window must call a function which opens another window.  We want the program flow to stop where the function is called, and resume after the function closes its window and Returns:

 | executable code  - done
 | executable code  - done
 
> WindowFunction() - in progress (program flow stops here until window closes and Returns)
   executable code  -
   executable code  -

There is a problem if we use the common dialog window model which has no event loop, in which the function sets up the window, gets everything working, and then Returns.  This model is based on the idea that there is a single event loop which handles all events for all windows open in its thread.  This is fine in a document-centered application, but creates havoc in a database program where we need procedural behavior within a data entry form and its subroutines.  In the above diagram, the WindowFunction() would Return and the following lines of code would be executed while the window is still open.

The only way around this is to put an event loop in the subroutine window.  This keeps program control in the function until it destroys its window and Returns.  But now we have two active windows with event loops in the same thread - that means trouble.  We could open the second window in another thread but then the subroutine couldn't access the data tables.  We could pass the work areas into the next thread via the Zero Space, but that's a lot of extra work and complexity.  The solution needs to be simple and easy.  What to do? 

The answer is to de-activate the first window.  There are several ways to accomplish this.  The easiest is to simply disable the calling window, and re-enable it when the subroutine Returns (the other techniques will be discussed later).

Here is a typical example which could be in a keystroke handler for the first window:

  ELSEIF nKey == xbeK_F5
    oDlg:disable()
    CallSecondWindowWithEventLoop()
    oDlg:enable()


This works beautifully - the first window is disabled while the second one is active.  In addition, if a third window is required, the same thing can be repeated - the second window is disabled while the third one is open.  This technique is part of the answer to our question of how to have multiple layers.  As long as all of these windows are in the same thread, and each one has an event loop with procedural behavior, any number of layers may be created.
 
Streams
I call this structure a Stream, where the first window opens in a new thread, and a stream of sub-windows emanates from it, each one disabling itself and calling the next.  The only active window in a Stream is the most recent one.  The Main window is always active and a new Stream can be opened at any time.  The first window in a Stream is a Child and is called from the main menu.  Any number of Streams can exist at once, each in its own thread.    Note that there are 10 open windows in this diagram, each with an event loop, but only 4 are active - one for each thread:

                                                           Main  (Thread 1)         
                                                 /                      |                      \
   
                 Stream (Thread 2)        Stream (Thread 3)        Stream (Thread 4)  
                      Layer 1 - Child              Layer 1 - Child             Layer 1 - Child 
                      Layer 2 - ???                 Layer 2 - ???                Layer 2 - ???
                    
 Layer 3 - ???                 Layer 3 - ???                Layer 3 - ???

This is a very powerful architecture.  Imagine the following scenario:

You have a Customer window open (Stream 1) and are processing an order that came in over the Internet.  You are down in the 3rd layer (Customer/Invoice/Inventory) and are about half way through the order.  Suddenly the phone rings and it's the Big Boss demanding to know ASAP how many Blue Widgets are in stock and what is the discount price schedule.  You go to the main menu and open an Inventory window (Stream 2) and drill down several layers to get all the information (the Inventory table is now open a 2nd time, but there are no conflicts).  While you're doing that someone from accounting rushes in from the next cubicle needing you to change the credit rating for a certain customer.  You go to the main menu and open another customer window (Stream 3), locate the customer record and make the change (you are now editing two different customer records at the same time).  Then you Close Stream 3, phone Big Boss with the Blue Widget information, Close Stream 2, and go back to work right where you left off.

I'm sure you can think of other scenarios.  This design is extremely flexible and can be used in a wide variety of ways.  Control over what Stream can be opened, or how many and how often, can be maintained by disabling and enabling menu items.

Note that the sub-layer windows should not be Modals.  If a Modal is opened in any Stream, it disables Main and any open children downstream from Main (in effect, the entire application).  It is the only active window.  Everything else stops until the Modal closes:

                                                           Main  (Thread 1)         
                                                 /                      |                      \
 
                  Stream (Thread 2)        Stream (Thread 3)         Stream (Thread 4)  
                      Layer 1 - Child              Layer 1 - Child             Layer 1 - Child 
                      Layer 2 - ???                 Layer 2 - ???                Layer 2 - ???
                     
Layer 3 - Modal            Layer 3 - ???                Layer 3 - ???


Using a Modal for one of the working layers would prevent us from working in, or opening, another Stream.  Therefore, Modals should be reserved for windows that are open for brief periods of time, such as pop up message boxes and picklists, or any purpose for which it is necessary to prevent other activity.  The only remaining question, then,  is what window type to use for the sub layers?
 
Layered Siblings
We have already ruled out Modals.  Looking at our chart of window types in Part 1, we are left with StepChild, SubMain, and Layered Sibling .  StepChild and SubMain, discussed further below, have behavior characteristics that are undesirable for this purpose.  Layered Siblings, on the other hand, behave exactly as needed. 

Note: Child and Layered Sibling windows are technically the same and have identical behavior.  The term "Child", however, along with its technical definition, is universally understood to mean a window called from Main (or other top-level window), either with or without an event loop.  So we use "Layered Sibling" (or just Sibling for short) specifically to mean a Child window with an event loop that is called from within an existing Child window that is disabling itself.  It is a term which becomes synonymous with the technique itself.

Siblings are ordinary Child windows in every respect.  They have title bars with system menus, icons and buttons.  They can be moved, resized, minimized and maximized.  They respond predictably to all the different ways we have of modifying their appearance and behavior.  When one is closed, focus automatically goes to the next one up-Stream.  If a Stream is closed, focus goes to another Stream.  In short, they behave in all the predictable ways which we are used to and expect.  There are no strange surprises.

So here we have our completed model - any number of layers in a Stream, any number of Streams.  Complete control with predictable behavior - and it's the easiest of all the techniques.

                                                          Main  (Thread 1)         
                                                 /                      |                      \
 
                  Stream (Thread 2)        Stream (Thread 3)         Stream (Thread 4)  
                     Layer 1 - Child              Layer 1 - Child              Layer 1 - Child 
                     Layer 2 - Sibling           Layer 2 - Sibling            Layer 2 - Sibling
                   
 Layer 3 - Sibling           Layer 3 - Sibling            Layer 3 - Sibling

In so many areas of life things that produce the best results usually require more work.  How nice to find this happy convergence, where the easiest way is also the most powerful and flexible.
 
Using StepChild Windows
Can we use a StepChild for these layers?  Yes, but it is not recommended.  There are several problems, all caused by its parent being AppDeskTop():

1) If there is a Child active in another Stream, the StepChild will cover the Child, even when the Child has focus.  So in order to use StepChilds, they must all be Stepchilds, so they can compete equally.

2) When a StepChild has focus, the Main title bar and menu bar are grayed.  Main looks disabled, just like it does when a Modal is open.  This can be disconcerting to the user, who may think that the main menu is not usable.

3) If a Stream made of StepChilds is closed while another Stream is open, focus returns to Main, not to the other stream.  This is a relatively minor quibble, but it is unexpected behavior and can be annoying.

4) A keystroke handler attached to a Stepchild window will not receive any number or letter keys.  Only "system" keystrokes are passed to it by its event handler (Esc, Enter, Tab, Backspace, etc).  There is a clever workaround for this, but it requires extra work and care.  On a complex form it can add an extra layer of difficulty to the control logic.

5) If a Modal, such as a message box, is called from a StepChild, the StepChild is not disabled (because the StepChild is not a child of Main).  That leaves two active windows with event loops in the same thread, so the StepChild must be disabled.  There are basically three ways to do this:

  a) Disable/enable the StepChild when calling the modal, just like our technique of calling layers.
      This can present a problem when calling popups from in-line expressions such as in validation
      codeblocks, or deep within lookup routines - it can be a lot more work.

  b) Send the StepChild into SetAppWindow() so that it becomes the owner of the Modal and gets
     disabled.  This has the same problem as a), and, even worse, Main (and the rest of the
     application) is not disabled, defeating the purpose of the Modal.  There is potential for trouble
     here.  As stated in Part 1, it is a general principle in GUI applications not to pass other windows
     into SetAppWindow() because it is expected this will always return a reference to Main.  In this
     StepChild scenario, Main is still active while the Modal is open.  The user could start a routine
     from the main menu which calls SetAppWindow(), and it would receive the StepChild instead of
     Main.  This is a poor programming practice and should be discouraged.  There are better ways
     to do things.  Passing windows to SetAppWindow() should never be done in a GUI app.

  c) Pass the StepChild into the Modal routine as a parameter to override the default owner.  This
     avoids the difficulty of a) and doesn't change SetAppWindow(), but it still has the problem of not
     disabling Main. 

     Note: There are legitimate situations (discussed below) which call for specifying alternate owners
     for Modals, and using a parameter has proven to be the easiest way to do it.  For that purpose,
     all Modal routines in Top-Down Library have an oOwner parameter.  Unfortunately,  the
     Xbase++ function MsgBox() does not have one.  Confirmbox() does, though, so obviously the
     author was thinking about this.  Why it was omitted in Msgbox() is a mystery.  For anyone who
     would like to have a message box with the parameter, I have written a generic message box
     function called Gmsgbox(), which can serve as a replacement.

So the best solutions are a) and disable the StepChild yourself, or c) and disable Main yourself - these are kludgy at best.  Clearly, StepChilds are not good general purpose windows in MDI applications.  They simply aren't worth the trouble.  What, then, are they good for?  They seem to be especially made for two purposes:

1) Applications that have small Main windows with StepChilds floating around them.  These tend to be special purpose utility programs.  A perfect example is the Xbase++ Form Designer.  The Delphi IDE also uses this design.  Note, however, that while sometimes used, this design is not absolutely necessary for these types of applications - it really is a matter of personal preference.  The Visual dBase and Visual Objects IDEs both use full screen Mains with Child windows, and I chose that design for the Top-Down Form Designer.  I find that seeing background objects between the application's working windows to be extremely distracting.

2) Applications with floating toolbar windows.  When the :tasklist attribute is False, a StepChild window will have the narrow title bar and small "x" button commonly seen on toolbar windows:                                    

The best example I have seen of this is Adobe Photoshop.  There is a resizable Main window and four small floating StepChild tool windows.  Images are displayed in Child windows, so a tool window can never be covered by an image.  The user is free to resize Main to suit the need.  It is an excellent design.
Using SubMain Windows
Can we call a SubMain as a layer?  Yes, but there are several things which make it unfit for this purpose.  As a top-level window it competes equally with Main.  If it is a smaller window and you click anywhere on Main, Main comes to front and the SubMain disappears behind it.  It can be regained by clicking on its taskbar button, but it is annoying to have to be so careful about where to click.  The disappearing can be prevented by setting its StayOnTop attribute, but then it covers all windows in other Streams and dominates the entire desktop as well, including other applications.  In addition, because its parent is AppDeskTop() it has the same keystroke handling limitation as a StepChild.  Clearly, it is not worth the trouble.  So what are some good uses for it?

SubMains are excellent for special purpose windows which need to be full screen, such as a Print Preview.  A full screen preview window offers the largest possible view, and at this size, clicking on Main is not an issue.  An example of this can be seen in the Print Preview window in the Top-Down Demo.

Another good use is for having separate modules within a large application.  I once converted a large accounting program into Xbase++ using this design.  Each of the major modules, such as General Ledger, Accounts Receivable, Payroll, Inventory, etc., has its own full screen window with menu bar and task bar button.  Each SubMain is running in its own thread with its own event loop, just as if it was a Main window in another application.  To the user, each module seems like a separate application.  It allows working in more than one module without a cluttered screen.  Main is programmed not to close until all SubMain module windows are closed (Top-Down Library has a template for creating these module windows). 

One of the primary reasons I chose this design is because of the program's extensive menu system.  Each module has its own menu choices, some two or three levels deep.  When they were all on the Main menu, it required drilling down two to four levels for every item needed.  Initial tests showed it to be tiring and annoying to the user.  A separate menu bar for each module made it much easier to use.

In these modules the Layered Sibling and Modal techniques work just the same as they do in Main, but with one essential difference: all parent/owner references to Main must now become SubMain.  Every Stream window called in a module must have the SubMain:drawingarea specified as the parent and owner, and every Modal must have the SubMain specified as the owner.  All of my window functions have parameters for overriding these default settings, so it's just a matter of passing in the correct values.  I created a Get/Set function in each module to make it easy to obtain a reference to the SubMain.  So it just requires a small amount of extra typing whenever a window function is called.  In this case, SubMains are an excellent solution, and well worth the extra bit of effort.
 
Calling Modals From Modals
There are situations which could require calling a Modal from within an existing Modal.  Sometimes a work window legitimately needs to prevent other activity while it is open.  An example of this could be a dialog in which application-wide settings are adjusted, and a Modal window is appropriate for this sort of task.  If a message box or other Modal popup needs to be called from this window, and an ordinary Modal is used, two problems occur:

1) Modal 2 disables Main (which is already disabled), and leaves Modal 1 active.  Not only can Modal 1 still be used, but there are now two active windows with event loops in the same thread.

2) When Modal 2 closes, it enables Main.  Now we are back in Modal 1, but Main and the entire application are now active again. 

Bad news in both cases.  The correct way is to specify Modal 1 as the owner of Modal 2.  Modal 1 then becomes disabled.  When Modal 2 closes, it re-enables Modal 1, and Main remains disabled. 

As we have already seen, sending a parameter into the Modal routine is the best way to specify an alternate owner. 
 

De-Activating Windows In Layers

 There are three ways to de-activate windows when using Layered Siblings.  The first has already been mentioned, but I will summarize it again here to make it easy to compare all three.  They are all equally effective.  Which one to use is only a matter of preference for the appearance and behavior of the deactivated window.

Disable the calling window -
  ELSEIF nKey == xbeK_F5
    oDlg:disable()
    CallSecondWindowWithEventLoop()
    oDlg:enable()


This is the most commonly used technique.  The calling window is visible, but is grayed out and totally unresponsive.  It cannot be moved or minimised.


Hide the calling window -
  ELSEIF nKey == xbeK_F5
    oDlg:hide()
    CallSecondWindowWithEventLoop()
    oDlg:show()


This technique can be handy when many Streams and layers are open and the screen gets cluttered.  Using the hide/show technique ensures that only the active window in each Stream is visible.  Hiding a window effectively disables it.


Disable the calling window's keyhandler -
This is an interesting approach.  It leaves the window alive and able to be moved or minimized, but unresponsive as far as handling any events that would conflict with the active window.  This is handy when information visible in it is helpful to the work being done in the active window, and it allows the user to move it to a good position to see everything.

However, this technique requires that all Mouse, Keyboard and Close events be channeled into a central event handler function.  This is a very effective technique for controlling window behavior,
and is described in the article "Designing For Mouse And Keyboard", also found on this web site.
I highly recommend this technique as a regular practice.  It keeps all the action code for a form in the same central location, and makes writing and maintaining the code much easier, especially when forms get very complex.

The Top-Down window class has an instance variable called :lHandleKeys, so every Top-Down window has it, and it's default setting is True.  If you need to disable a window's event handler without disabling or hiding the window itself, just wrap the entire keyhandler in a simple IF statement, and then switch it off when calling the next layer:

IF oDlg:lHandleKeys

  IF nKey == xbeK_F5
    oDlg:lHandleKeys := .F.
    CallSecondWindowWithEventLoop()
    oDlg:lHandleKeys := .T.

What is interesting is that the window's event loop is active, but any Mouse, Keyboard or Close events it might receive are sent into the keyhandler where they are ignored.  Yet it can still be moved or minimized.  It works beautifully, and I have never had a problem with it of any kind.

So now, along with the very powerful and flexible technique of Layered Siblings, we have a way of tailoring the look and feel of the system to an even finer degree.  The possibilities are enormous.
 

Conclusion

Most developers will agree that we spend more time on interface code than on business logic.  In Windows, especially for someone just getting started, we can spend an inordinate amount of time just trying to get the windows to work the way we want.  This should not be the case.  The window architecture should be a minor issue, not an obstacle to productivity.   I hope this information will smooth that part of the learning curve by giving a better understanding of the window hierarchy and how it works.  It doesn't have to be a mysterious dark art. 
 

Part 2 - The End

Copyright © 2002-2008 Clayton Jones
All rights reserved.


Return to: Articles Page |Top-Down Page | Software And Services | Home