Second in a two part series
Copyright © 1999-2006 Clayton Jones

by Clayton Jones
In the first article in this series we discussed how to run a GUI dialog window in its own thread, and the importance of ensuring that the program flow exits the routine properly so that the thread’s memory is released.  In this article we will see how our ability to do this provides an elegant solution to a sticky problem: data access in event driven applications.

     As developers, we are familiar with the techniques for allowing multiuser access to a database.  But what happens when an event driven system allows a single user to open the same table several times at once within the same application?   It does not work, of course, without some special handling. There are essentially three ways of doing this: a data dialog class of some sort, unique aliases, and threaded dialogs.

The Data Dialog Class

     There is an example of a data dialog class in the Xbase++ sample code.  It is in datadlg.prg, located in the "\source\samples\apps\mdidemo\" subdirectory.  An examination of this class reveals an :area instance variable which holds a reference to the work area, and a rather complex set of notification routines.  This class works well but has several drawbacks.

     First, this particular design can only handle opening one table.  In real world applications it is common to have data entry routines in which several tables are open, often with complex interactions taking place in the underlying business logic.  In many cases, therefore, this design will have to be modified.

     In addition, this data dialog uses the behavior model of being automatically in the edit mode, with the active editable fields predetermined.  This is a simple behavior model which works well in certain narrowly defined circumstances or particular design strategies, but is often unsuitable in real world database applications that fit within a broader design.  In my experience, it is more often necessary to have the data entry form initially displayed in View mode, with the user initiating the Edit, which in turn offers specific "Save" and "Cancel" options.  Furthermore, it is not uncommon to need more than one edit option, each one activating a different subset of fields within the form.

     In my early years of Clipper work I designed a template which contains all of the view/add/edit/ delete logic commonly needed in a data entry window.   It works well, and in many cases is adequate just as it is.  I simply plug it in and add the particulars.  However, having  created many different kinds of applications over the years, I can say from experience that I often have to modify that template.

     The point here is that there is no common behavior model which can serve unmodified in every situation.  Because of the complex interactions of the notification routines which are entertwined with the behavior logic, the sample data dialog does not easily lend itself to modification.  If the Xbase++ user is just getting started, perhaps coming from a Clipper background without previous OOP experience, this model can be confusing and difficult to understand.  For these reasons, in my opinion, this is not a good choice to use as a foundation for data entry dialog design.   Fortunately, Xbase++ offers other solutions.

The Unique Alias

     In order to help manage data access in an event driven application, Xbase++ has introduced the the unique alias. When a table is opened in a work area, Xbase++ assigns a unique alias for that table. In Clipper, if we open a table like this:

USE customer NEW
alias() -> customer

the alias automatically assigned will be the same as the DBF name, "customer". If we wish to use a different alias, it may be specified using the ALIAS command:

USE customer ALIAS cust NEW
alias() -> cust

In Xbase++ we have the same two options except that a unique alias is assigned, so the same table may be opened more than once at the same time, each instance receiving a different alias.  This example shows a table being opened four times:

USE customer NEW
alias() -> customer

USE customer NEW
alias() -> customer_2

USE customer ALIAS cust NEW
alias() -> cust

USE customer ALIAS cust NEW
alias() -> cust_2

Therefore, in order to allow a data access routine to be run multiple times at once, all we have to do is assign that unique alias to a variable using the alias() function, and then use that variable with the alias operator for data manipulation:

USE customer NEW
cAlias := alias()
(cAlias)->(dbappend())
(cAlias)->lastname := "Smith"
(cAlias)->(dbcommit())
CLOSE (cAlias)

If such a routine were run several times at once by the user, each instance would have its own unique alias and there would be no conflict.  Of course the usual record locking and error handling methods should be employed, as in any multiuser application.

     It would seem as if we have the perfect solution for the event driven environment.  Indeed, this method works extremely well in very simple routines with one or perhaps two tables opened. Consider, however, a more complex routine with five tables open and a temporary table created to hold a batch of data which will be resolved later.  If subroutines are called, we must be careful to pass the alias variables to them.  We become totally dependant on the variables because we don’t know what the actual alias names are. It can quickly become a tangled mess that is confusing and difficult to maintain.  Fortunately, in using our threaded dialogs we find an even better solution

Using Threaded Dialogs

     Along with the unique alias, Xbase++ gives us the concept of the "work space".   The work space is a "container" for work areas.  When we launch a new thread, as we learned how to do in the previous article, each thread implicitly has its own work space.  Each work space is a completely encapsulated environment which, for event driven data access purposes, functions like a separate application.  If data access routines are run in their own threads, we can dispense with the burden of multiple unique alias variables and write code in the manner to which we are accustomed:

USE customer NEW
CUSTOMER->(dbappend())
CUSTOMER->lastname := "Smith"
CUSTOMER->(dbcommit())
CLOSE customer

     I have not found any problems using this method so far in my testing.  During one experiment I had sixteen instances of a small data entry dialog open at once.  I was able to switch among them at will, editing different records in the table, leaving edits active while switching to others, coming back later to save or cancel them, all without any problems.  They performed as if they were sixteen different applications.

     If a subroutine is called from a threaded routine, the subroutine will be executing its code within the same thread, and will be encapsulated and protected by it.  We thus have what seems like an ideal method.

Conclusion

     In my own work with Xbase++ to date, I have found the threaded dialog to be the easiest and least cumbersome way to ensure problem free data access in an event driven application.  The unique alias method also has its merits, and I use that in simple "black box" type routines of which, because of their very nature, we have no foreknowlege of how they will be used.  It is possible that they may not be called in a separate thread, and so must have their own protection built in.

     Using the LaunchPad() routine described in the previous article, it becomes a relatively easy matter to create a main application window and menu running in the primary thread, and have most routines launched from the main menu executing in their own threads.  By keeping the data access solution separate from the data dialog behavior logic, we are free to design a behavior model without that extra burden, one which is more easily modified to suit particular needs.

Copyright © 1999-2006 Clayton Jones
All rights reserved.

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