Over the past two years the Information Technologies and Methodologies Department at Sandia National Laboratories has developed two major information systems using Windows 4GL (W4GL). These systems were used to track the configuration management of the nuclear weapons stockpile and the characterization of weapon components for dismantlement. During this development effort, common user interface components were identified and developed independent of the two projects. These user interface components were developed as an included application.
This paper will discuss two of the larger components of this shared application. The first is a context-sensitive menuing system. This menuing system controls the W4GL pull down menus based on the current frame bias and the identity of the user. In the context of frame bias, the system handles the dimming of menu choices as appropriate. In the context of user identity, the system enables/disables access to menu items base on the user's membership in groups. This menuing system was implemented such that each frame required only a single call (within the initialization block) to a W4GL procedure. After being set up by this call, the state of the menu was changed through the bias facility.
The second component to be discussed is a facility named the "Event Dispatcher." Both above applications were built to make extensive use of user events. As data items are selected by one frame, user events are sent to other frames so that they are loaded with the current data. This dispatcher was designed to act as a single point for the handling of user events. When an event occurs, the frame calls the event dispatcher with the name of the event. The dispatcher is responsible for sending the event to the other (currently open) frames which need to receive the event. This architecture made adding new frames to the application much simpler. If the new frame need to receive a given event, no code changes were required in any other frames. All that is required to have an event sent to a frame is to add a row to the user event database table.
The design of both components and the associated database tables created to support them will be discussed.
Over the past 2 1/2 years, the Information Technologies and Methodologies Department at Sandia National Laboratories has been engaged in developing client/server information systems to support efforts in nuclear weapon stockpile dismantlement and configuration management.
During the design process, a conscious decision was made to design as much of the user interface code as possible, reusable. This reusability was accomplished through two mechanisms; "table- driven" code and included applications.
By "table-driven" code I am referring to code which retrieves its runtime parameters specific to an application from database tables. Although these tables are part of the implemented database, they are "owned" by the developers and exist only to support the application software.
An example of one of these developer tables is the application_constant table. All applications use this table to store pseudo-constants that are read into the application during initialization. I call them pseudo-constants, because although they are stored in global variables, their values do not change during the execution of the application. For a typical application, the application_constant table might contain values for the current database version, the name of the local site (for those applications installed at multiple sites), the application version, and the application name.
Because these developer tables are considered part of the application, they are often replaced when a new version of the application is released. Although this does result in some additional complication to a new application release, the added flexibility achieved makes it well worth it.
The "included application" capability of Windows 4GL was used extensively. Included applications were used to minimize duplication of code and application element definitions. For example, all the global variables used in an database application are defined in a small application. This application does not actually contain any frames or procedures which can be executed; its only purpose is to make the globals available to the other applications that require them.
Another characteristic of Windows 4GL applications made included applications desirable. It was discovered that a large application with many (>100) components loads much slower than the same number of components that had been divided into several included applications. In the case of one large application, it took about 10 minutes to load. However, after the globals and user classes were moved to an included application, the load time was reduced to about a minute.
The diagram shown in Figure 1 shows the basic hierarchy of included applications. A given application may include other specialized applications. In the diagram, shaded applications are the ones which are shared between all databases and applications. These applications are actually developed and maintained in a database created expressly for that purpose. Figure 2 gives brief descriptions of the purpose of each included application.
One element obviously common to all Windows 4GL applications is the pull-down menus. These menus are ubiquitous with graphical user interfaces. Properly used they help guide the user through correct use of the application. Much error-handling code can be avoided by using the "dimmed" attribute of a menu item to prohibit the user choosing inappropriate items.
There are several possible ways to implement a context-sensitive menu. The simplest to develop is by creating the menus in the frame editor and controlling the attributes by applying the CurFrame.CurMode method within the script. This was the typical method used in early efforts as we became familiar with Windows 4GL. This approach has several drawbacks:
The other extreme would be to build and manage all the menus dynamically. Using this approach, developer tables would be created that modeled all the information required to create and alter the pull-down menus during program execution. While this technique would provide for ultimate flexibility, it would be much more complex to develop.
The approach we decided upon was one where the menus were created in the frame editor. Developer tables were created and loaded with the information needed to set the frame bias attributes. A 4GL procedure is called at application initialization that loads this information. During program execution, changing the bias of a frame automatically changes the menu appearance.
Windows 4GL provides six different frame modes. These six modes are:
These six modes were aliased to represent six different locally defined frame states. The reason that the W4GL modes were aliased to frame states is because these states more closely reflect the functional flow through the applications. Figure 3 shows the mapping of the W4GL modes to our frame states.
In a typical frame, the states would transition as described below.
As you can see by examining Figure 4, another path exists through the frame. If users chooses the "New" menu item, they are placed into the blank state and allowed to enter a completely new piece of information. It is important to understand that this example is but one of many possible state transition schemes for a frame. This particular one would be appropriate for a data entry frame where updating existing data was the expected dominant activity. The important point here is that in each state, the appropriate menu items are made available or dimmed as appropriate.
The other requirement of the menu system was a mechanism to provide access control to the various frames based on the identity of the user. The applications were designed to make restricted functionality or frames accessible only through the pull-down menu. Access was controlled by making invisible (as opposed to dimmed) the menu items that a given user should not have access to. In this way a user with limited access is not even aware that the additional functionality exists.
To implement this menu system, five developer tables were created. The structure of these tables is described below and shown graphically in Figure 5. The contents of these tables is maintained through the dba_utility included application. This is a W4GL application used by the DBA to manage the menu item permission matrix (among other things). The dba_utility application itself is outside the scope of this paper.
The appl_users table contains the login names (login_name) of the application user along with an internal identifier (appl_user_id) and an identifier that points to the user's preferred output device. The output_device_id is used by the "Reporting System" (another shared application) and is outside the scope of this paper.
The group table contains the list of valid user groups within a given database. This list is defined by the DBA to reflect the requirements of a particular database. Some examples of group definitions are:
The user_group table provides for a many-to-many mapping between application users and groups. The DBA populates this table to reflect the user/group membership matrix. Some user might be a member of only one group, while others could be members of several.
This table provides for a many-to-many mapping between menu items and groups. The DBA populates this table to reflect the menu item to group permission matrix. Some menu items might be available to only one group while others could be available to several.
The menu_list table is the heart of the menu system. The key to this table is the two columns frame_name and menu_name. The menu_id column is a surrogate key to permit the easy renaming of frames or menu items. The other six columns contain the appropriate menu bias for each frame state. The possible values for these six columns are:
Within an application, the menu system is set up in two phases. The first phase occurs when the application is started (before any frames are initialized). The second phase occurs at the initialization of each frame. When an application is initialized, the identity (login name) of the user is used as the key to retrieve his or her appl_user_id from the appl_users table. With the appl_user_id, a select query is executed to join information from the user_group, menu_group, and menu_list tables. This retrieves the list of all menu items in all frames that the user has access to. This information is loaded into a global dynamic array named gl_frame_menus, an array of the user class FRAME_MENU_CLASS. The structure of this user class is shown in Figure 6.
Notice that the second attribute (menus) of the user class FRAME_MENU_CLASS is itself an array of a user class. The structure of this user class ( MENU_CLASS) is shown in Figure 7.When a frame is initialized, the menu tree for the frame is traversed. As each menu item is found, the procedures searches the gl_frame_menus.menus array. If the menu item is found in the array, the mode bias method is used to set the menu bias for each of the six frame states. If the menu item can not be found in the array (indicating that the user does not have permission to use that item), its bias in all modes is set to MB_INVISIBLE
When a new frame is created, the menu_list table must be populated with an entry for each menu item. This can be done manually, but to speed up development and reduce the chance of transcription errors, a 4GL procedure was created that traverses the menu of a new frame and inserts entries into the menu_list frame with all biases set to MB_ENABLED. This 4GL procedure is in the script of a menu item. When a new frame is created, the Populate Menu List menu item is copied from another frame. When the frame is tested the first time, the call to setup_menus is commented out (effectively enabling all menu items). The Populate Menu List menu item is chosen, and the entries are made into the menu_list table. The frame is then edited, the Populate Menu List item is deleted, and the call to setup_menus is uncommented. The biases are then set to the appropriate values via the dba_utility application.
The Windows 4GL applications developed in the Information Technologies Department made extensive use of user events. These user events are used primarily to handle communication between frames. For example in the Stockpile Dismantlement Database (SDDB) application, one frame displays to the user a scrolling table field of part records. The user selects the part he or she wishes to work on in this frame. When selected, a "New_Part" user event is sent to the various detail frames which then load the related detail information for editing.
Initially, these selection frames were written with the SendUserEvent methods included within the frame scripts of the "selection" frames. A separate SendUserEvent existed for each frame that needed to receive the event. This approach was a maintenance problem. Whenever a new frame was created that needed to receive a given user event, every frame that could originate the event required editing. In some cases this meant that the addition of a single new frame could require that up to four other frames would need editing. To address this problem a facility, named the Event Dispatcher was created.
To implement the event dispatcher, a single developer table was created. The structure of this tables is described below and shown graphically in Figure 8. The contents of this tables is maintained through the dba_utility included application.
This table is used to define the many-to-many relationship between frames and user events. A row exist for each user event that each frame receives.
When the application is initialized, a global dynamic array named gl_user_events is loaded with the information contained in the user_events table. This is an array of the user class USEREVENT_CLASS. The structure of this user class is shown in Figure 9.
In order for the event dispatcher to do its job, it needs to know information about each open frame. To accomplish this a global dynamic array called gl_frame_list was created. This is an array of the user class FRAME_CLASS. The structure of this user class is shown below in Figure 10. Within the initialization block of each frame, a call is made to a 4GL procedure called setup_frame. One of the functions of this procedure is to make the appropriate entry into the gl_frame_list array. When a frame is called the first time, the setup_frame procedure adds a row to the gl_frame_list array. When the frame is closed, the row is removed from the array.
When a frame needs to send a user event, a call is made to the dispatch_event procedure. This name of the user event is passed as an argument. The procedure searches through the gl_user_event array. When the user event passed as an argument is found, the frame_names array is scanned and compared to the currently open frames in the gl_frame_list array. When an open frame is found that needs the event, the SendUserEvent method is invoked.
By developing the event dispatcher, event code in existing frames was greatly simplified. All the SendUserEvent methods were removed and replaced with a single call to the dispatch_event procedure. When a new frame is created that needs to receive a user event, all that must be done is to add the appropriate row to the user_events database table.
We have been very pleased with the utility of these table-driven modules. They have allowed us to concentrate on core of the user interface programming task without getting mired in the details. Some other modules we are considering are:
I want to thank John Hatley for his insight and talent in making these ideas successful. This work was supported by the United States Department of Energy under Contract DE-AC04-94AL85000.