Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Writing User Definable GUI's


September 1996: Writing User Definable GUI's

Writing User Definable GUI's

The trick is in extending Visual Basic

Troy A. Schauls

Troy, a software engineer for Seattle Systems, can be contacted at [email protected].


The VB line output, comma-delimited ASCII format, is fast to write and fast to read

When used in conjunction with third-party custom controls, Visual Basic (VB) can solve many complex programming problems as elegantly as any other language. At Seattle Systems, for instance, we used VB and other tools to build an information-management system for cardiovascular medicine called the "Apollo Clinical Database System" (see Figure 1). Apollo, which is in use at approximately 120 hospitals in the U.S. and Europe, is a complex data-warehousing, patient-reporting, and data-analysis tool for cardiovascular medicine.

To build the system, we used a number of off-the-shelf tools, starting with Microsoft Access for the back-end database and VB for the user interface. We then employed controls such as Distinct's TCP/IP, Desaware's SpyWorks, Sheridan's DataWidgets, Videosoft's VSFLEX/OCX, and StorageWorks-VB to handle connectivity to hospital mainframes and patient-monitoring machines, retrieving and presenting stored data, building user-defined screens, performing on-the-fly data validation, and the like.

Covering all of these topics is beyond the scope of a single article, so I'll focus on one of the more interesting parts of the project-the user-defined screens that allow users to create and modify data-entry screens. This capability is possible because all our Visual Basic forms are created, parsed, and compiled into a proprietary format. Only a "system form" is in the app's executable file. The configuration files are then read in at run time and the controls are loaded from code.

This feature underscores one of VB's most seductive features-its extensibility. Using VB's built-in form object and control arrays, you can create new forms and load controls from code, much as you can with C and the Windows SDK. We used this technique in developing the Apollo system.

There were a number of factors that led us to this approach:

  • Many sites requested the ability to arrange the layout and define the content of their data-entry forms.
  • It was expensive and troublesome to customize new forms for every site. When a hospital requested new forms, that also meant a new executable file. Having dozens of different versions of a program in the hands of customers was a maintenance nightmare.
  • The scope of our clinical coverage was exploding. The nearly 700 data-entry forms in a full installation was far more than could be compiled by VB.

Though this was a good solution for us, this approach is not without drawbacks. For instance, it requires the user base to be fairly savvy. Users have to grasp the concepts of properties, forms, and controls, and they need a heap of thorough documentation. It also requires some sort of design environment. We considered writing our own, but quickly concluded that rewriting the VB IDE wasn't worthwhile. We decided to bundle VB with Apollo for those sites that paid for this feature. This was a major design issue that required extra coding.

If your user base does not need or want this capability, there are still perks to this approach that make the extra coding worthwhile. For one thing, the implementation is modular and highly reusable. Even though you may never need to let users tweak or design their own forms, you could still load some forms from a configuration file. If the need for user-defined forms arises in the future, you'd already be most of the way there. External form storage also means a smaller executable file, and only those forms currently in use are in memory.

Setting Up the Base Form Class

The first step to implementing user-defined forms is to determine which controls you will support. This is probably the single most difficult decision. You need to consider the following:

  • How many types of controls will the base class support?
  • Which properties will you support?
  • Which properties will be set by you at design time and which will the user be able to define?

Figure 2 is a VB 4.0 form with a few controls on it. This is all you need to make up the GUI part of the base form class. To try this example, insert a form into a VB project, and name it FUDS-a Form object with a class name UDS. Place the controls on the form, setting each control's Index property to 0 and its Visible property to False. This will create the control array to be added to when the form is loaded. Set the appearance properties of each control to whatever defaults are appropriate. In addition, you will need some way of calling the public functions of FUDS and the compiler module, such as another form with a couple of command buttons on it. (The source code in these examples uses the VSAwk parsing control from VideoSoft. If you do not have this control, remove the references to it and create your own token parsing routine.)

Place the code from Listing One (listing available electronically) in the code window for the FUDS form object. This will encapsulate in the Form object itself the code for reading the configuration file and loading the form's controls. Note that the one publicly accessible method, Create(), takes a string. This is the path to the compiled configuration file for the form you want to load. At run time, one call to the Create() method is all it takes to construct and show the form.

Creating the Compiler

The code for compiling a VB 4.0 form file into a tighter configuration format is available electronically. Simply place this code into a regular VB 4.0 code module. This code is also fairly modular and encapsulated. It exposes only one top-level function, SaveForm(), to the outside world. This function takes several arguments: the full path to the location in which to store the compiled file; the file name; a PAGEINFO structure; and an array of CTLINFO structures.

Now take a look at the format of the configuration file itself (demo1.dat, also available electronically). You could choose any number of ways to store the file, but for this example, we chose the VB line output, comma-delimited ASCII format. It is fast to write, fast to read, and is far more compact than the original VB form file itself. Why wouldn't you want to read in the original VB form file? In one test example, the raw VB 4.0 form file was 35.4 KB, and the compiled form configuration file was approximately 1/10th the size at just 3.2 KB.

The CompileFormFile() routine is a good starting point to explore what's going on. First, two structures (UDTs) are created. The variable tForm of type PAGEINFO holds the general form properties. Each element in the array of structures called atCtls() holds the properties for one control. The raw VB 4.0 form file is opened and is looped through, line by line, until EOF. Each property line is parsed and stuffed into the appropriate variable in the structure.

Once this method returns, if no errors were encountered, the structures in memory are written to disk in the WriteFormFile() routine. In this routine, it becomes clear why we chose this format for the configuration file. Using VB's Write# statement, variables in the appropriate structure are given in a list and written in that order-automatically comma delimited-to the configuration file. Conversely, the compiled file can be read in again and again using the Input# statement, as seen in the ReadFormPage() routine.

The output of WriteFormFile() is simple and compact. The first line of each file contains the general-form properties such as size, caption, and number of controls on the form. Each successive line represents one control on the form; each comma-delimited field represents one property of that control.

By using the Write# and Input# statements, VB does a whole lot of parsing for you. VB file I/O is generally quick, but this extra parsing may not provide optimal performance. Currently, we are experimenting with other I/O techniques, such as fixed-length ASCII input and OLE Structured Storage (binary).

Testing the Compiler

To test the compiler, create a form in VB the usual way by sizing the form and placing controls on it. Note, however, that for this test you don't want to place any controls on the form that weren't also placed on the FUDS form created earlier. It should compile, but errors will occur when trying to load it from the configuration file.

Save the file the usual way. VB will generate a file with the .frm extension. Then call the SaveForm() routine, passing the path and name of the VB form just created. Once SaveForm() has returned, the file will be ready to load.

Loading and Displaying the Compiled Form

When the form is needed for display, ReadFormPage() fills the PAGEINFO and CTLINFO data structures, and LoadUDSForm() does the work of loading the controls on the form. There are two items worth noting about the LoadUDSForm() routine and the format of the compiled form file. First, the order of the control information in the compiled file is very important, as it denotes the parent-child relationships between controls on the form. At run time, this relationship can be preserved with each control's Container property. The other aspect to note is that while looping through the CTLINFO structure array, each time a parent control is reached (in this case, the parent control will likely be a Frame) it becomes the parent until the next time a Frame control is loaded. This newly loaded Frame then becomes the parent, and so on. If this is not clear, take a look at a compiled form file. The first field in each line denotes its depth. For example, a parent control might have a 1, and the next five controls may have a 2 in this field. All the 2s are children of the control at a depth level of 1. This pattern continues until EOF.

Granted, this example is simplistic. If your controls are several layers deep and you have several possible parent controls, your control-loading routines have to become a little smarter. This increased intelligence, however, is left up to you.

Conclusion

This is definitely not the speediest method available for loading a form. I ran some rough benchmarks on a 60-MHz Pentium with 24 MB of RAM running Windows NT 3.51. My test form (demo1.dat) covered about 2/3 of the screen area and had 50 controls. It took, on average, 0.9 seconds to be read in from the disk, loaded, and displayed.

On the other hand, if multiple instances of the form are loaded in a for/next loop, the average loading time increased significantly. Five forms took 1.5 seconds each to load in succession, while loading ten instances of this test form increased the average to 1.9 seconds. These benchmarks were all taken using the Win32 timeGetTime() API function.

The obvious moral of this story is that forms should be loaded one at a time, or in small groups, as users request to view them.

The approach described here has proven to be quite successful and very popular with our users-despite the slightly longer loading times. If maximum flexibility is your application's aim, then this technique may be your answer.

Figure 1: Typical Apollo Clinical Database screen.

Figure 2: Minimal VB 4.0 form.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.