Program Organization and Event Driven Applications

Older Program Organization

With older versions of Zim, all windows were modal. Once a window was opened, all user interface events occurred within and applied to that window until another window was opened or until the initial window was closed. As a result, a typical Zim program was written to manage a single window and looked like this:

Procedure TypicalProgram ( )
%% Start
  window open MyWindow  % open the window
  form open dMyDisplay   % open form/display
  form set exit ...   % set exit and/or transmit keys
% other processing such as initializing the forms
  form display
 
 %% Middle
  while
  form input
% code here would react to different user interface
events. E.g.,
  case
  when $transmitkey = "Escape"
  break
  when $transmitkey = CtrlF4
  break
  when ...
  endcase
  endwhile
 
%% End - other clean-up code here
  window close MyWindow
EndProcedure

As illustrated, the code can be broken down into three major sections. In the "Start" section, the window and form or display is opened and initialized. The "Middle" section is composed of a loop that gets the next user interface event (using FORM INPUT) and then acting on that event. Finally, the "End" section closes the window and does any other clean up before the program is completed.

Impact of Event Driven Applications

In event-driven applications, several windows can be open at any time and the user can select the window with which they wish to interact. The older organization for programs does not handle this situation because the "Middle" section assumes that each user interface event is targeted at the window opened in the "Start" section. As a result, the organization that is best suited to event driven applications is to create a single program that contains an event loop and have separate programs to manage each window. Programs that manage a window are organised as servers to respond to a particular event and then to exit immediately.

Organization for Event Driven Applications

If our application has three windows that can be opened (MyWindow, ThatWindow, and AnotherWindow), then the event loop program looks like this (we have also replaced FORM INPUT with pGetAction since pGetAction can be used in both the event driven and application driven cases):

Procedure EventLoop () \
  local(vlStatus, vlAction, vlTargetWindow, vlCurrWindow)
...
  while
   pGetAction (vlStatus, vlCurrWindow, vlAction, vlTargetWindow)
 
%% Dispatch action to appropriate window
   case
   when vlTargetWindow = "MyWindow"
    HandleMyWindow (vlStatus, vlAction)
   when vlTargetWindow = "ThatWindow"
    HandleThatWindow (vlStatus, vlAction)
   when vlTargetWindow = "AnotherWindow"
    HandleAnotherWindow (vlStatus, vlAction)
   endcase
   let vlCurrWindow = vlTargetWindow
  endwhile
...
EndProcedure

After each call to pGetAction, the appropriate window management program is called. Now getting the next event/action is centralized in one program which dispatches the actions to the appropriate window management program.

Note: There is a problem with this event loop program. As written, it does not handle getting the first window open nor does it provide any means to stop the application. We resolve this problem in the next section.

A window management program for MyWindow can be organised as follows:

Procedure HandleMyWindow (out vlStatus, in vlAction)
  on error
   let vlStatus = "ERROR"
   return
  endon
 
  let vlStatus = "OK"
 
 %% This code corresponds to Start section above
  if $IsWinOpen ("MyWindow") = $false
   window open MyWindow  % open the window
   form open dMyDisplay  % open form/display
   form set exit ...   % set exit and/or transmit keys
   % other processing such as initializing the forms
   form display
  endif
 
%% Exit action contains code from End section above
  case
  when vlAction = "Exit"
  %% other clean-up code here
   window close MyWindow
 
%%Other cases correspond to code from Middle section
  when ...
  endcase
EndProcedure

Handling State Information

Typically, while a window is open, the application needs to maintain state information about the window. For example, if the window is designed for data entry, you need to keep track of whether you are in "add" mode (that is data being entered is for new database records) or "change" mode (you are editing existing data). You also need to keep track of any changes that have been made. In the older program organization, this state information was usually stored in local variables. In the new organization, this does not work since the application enters and exits the window management program for each user interface event. Thus, it is usual practice to create structures (i.e. forms intended for data storage rather than display) to hold any state information.

More General Action Dispatching

As written, our event loop needs to know, in advance, which windows can be open. This makes this code very application specific and it also means it must be changed when another window is added to the application.

We also need to resolve the problem of how the event loop starts and how it ends. Applications typically have a main window -- that is, the window that first appears when the application begins. We'll assume that MyWindow is the main window of this application.

The following code resolves this problem as well as dealing with the issue of how the application begins and ends.

Procedure EventLoop () \
   local(vlStatus, vlAction, vlTargetWindow)
 
  %% Launch the main application window.
  HandleMyWindow (vlStatus, "ENTERWINDOW")
 
  let vlAction = ""
 
  while
   %% Make sure main app window is still open.
   %% If not, then exit the loop and clean up.
    if $IsWinOpen ("MyWindow") = $false
     break
    endif
 
%% Event loop exits when main window is no longer open.
    pGetAction (vlStatus, vCurrProcess.WindowName, \      vlAction, vlTargetWindow)
%% pDispatchAction makes the event loop independent
%% of window names.
    pDispatchAction (vlStatus, vlAction, "", 0, \
     vlTargetWindow)
  endwhile
EndProcedure

The changes in the event loop program are

The window management program is as follows:

Procedure HandleMyWindow (out vlStatus, in vlAction)
  on error
   let vlStatus = "ERROR"
   return
  endon
 
  let vlStatus = "OK"
 
  if $IsWinOpen ("MyWindow") = $false
   window open MyWindow % open the window
   form open dMyDisplay % open form/display
   form set exit ... % set exit and/or transmit keys
   % other processing such as initializing the forms
   form display
   %% Register the window with the process manager.
   pRegisterProcess (vlStatus, "Main Window", \
     MyWindow", "", "App'n Window.", "HandleMyWindow", \
     cChildClass, $NULL, stMyWindow.stProcessId)
  endif
 
  case
  when vlAction = "Exit"
  %% other clean-up code here
   window close MyWindow
   pUnRegisterProcess (vlStatus, \
    stMyWindow.stProcessId, "")
 
  %% Remove this window from the process list.
  when ...
  endcase
EndProcedure

The major changes here are as follows: