Modal or Modeless?

Whether you choose MDI, SDI, or your own brand of DIY-DI (do-it-yourself document interface), you’ll need to think about modality. Modality is one of the most critical issues in the design of your window-management scheme, since it can significantly affect program complexity. Using modal forms wherever possible helps to control complexity, but it can also get in the way by imposing artificial restrictions on users. Although modality is one of the more contentious issues of user interface design, the evidence in favor of radical modeless design is far from conclusive. Suffice it to say that in this chapter the concern is with the implications of modality on implementation rather than with the psychology of interface design.

When you show a form, Visual Basic lets you specify whether you want to show it modally or nonmodally, using the constants vbModal and vbModeless. This isn’t a very flexible way of implementing modes, however; a vbModal form is task-modal, which means it locks out all user input from the rest of the application. This type of modality is really suitable only for pop-up dialog boxes. When you specify vbModal when you show a form, the only way you can show two forms together is if they have a parent-child relationship. This restriction imposes a particular set of design restrictions on your application, and it might prevent you from doing what you want. It’s also impossible to display a nonmodal form from a modal form, another potentially intolerable situation.

Consider the example shown in Figure 14-1, which is a non-MDI application with several distinct functions invoked from a main menu. Perhaps it’s a database maintenance program, and you would like to be able to refer to display functions while using update functions. In Figure 14-1, I’ve shown two functions executing at the same time; forms A and C can be considered parent forms for Function 1 and Function 2, respectively. Parent form A is also displaying a child form, form B.

Figure 14-1 An example of function modality: form A is not accessible here.

Although the forms shown in Figure 14-1 are relatively simple, it’s likely that you’ll want form A to display form B modally, or more specifically, for form A to be inaccessible for as long as form B is on the screen. The conventional way to code this is for form A to do FormB.Show vbModal, but this locks all user input from any form except form B—including the main menu. Hence, it wouldn’t be possible to reach the situation shown in Figure 14-1. The alternative, FormB.Show vbModeless, doesn’t prevent you from accessing multiple functions at the same time, but it interferes with the design of each function and greatly increases the complexity of the program. Clearly, you need to find something in between.

Visual Basic’s built-in support for modal forms is geared toward simple pop-up dialog boxes, but that doesn’t stop you from building modes by other means. Forms have an Enabled property that, when set to False, effectively mimics what happens to a parent form when it shows a vbModal child. Now that you’re in control, however, you’re free to enable and disable forms at will, without the restrictions imposed by vbModal.

Returning to the example in Figure 14-1, all you need to do is to disable form A when form B loads and reenable it when form B unloads (or possibly on Show and Hide instead of Load and Unload). This implements a new kind of mode that’s more appropriate to your requirements; you might call it “function modality,” since you’re creating an architecture in which it’s permissible to hop back and forth between functions yet each function is effectively a modal cascade of forms. This architecture is only one possibility; a less orthodox architecture is shown in Figure 14-2.

Figure 14-2 The Create New Publication and Review Publication forms swap with each other.

Figure 14-2 shows a database application that’s used to keep records of technical publications. Users can choose an existing entry from the list and edit it using the Review Publication form, or they can enter a new publication by calling Create New Publication. Notice that the Create New Publication window has a Review button, and the Review Publication window has a New button. This arrangement could imply multiple instances of each screen, but let’s say that the design calls for screens to be swapped when these buttons are used. For example, the user could call up the Create New Publication window to enter the details for a new publication and then press the Review button to move immediately to the Review Publication window to enter a review of it. As the Review Publication window loads, it replaces the Create New Publication window, which is unloaded. The Select Publication window is disabled when either the Review Publication window or the Create New Publication window is displayed.

There is no elegant way to implement this architecture using Visual Basic’s standard modality features. You would somehow have to defer your request for the review form to be displayed until the Create New Publication form was unloaded. You could make it work, but it would be tricky and it would be ugly. You’d be much better off devising a general mechanism to support the kinds of modes you want to enforce.

Forms Are Classes Too

Forms are really classes in disguise. Once you realize this fact, you can start using it to your advantage. The similarity isn’t obvious because you don’t have to define instances of forms before you can use them. However, you can use a form’s Name property to create new instances of the form at run time, just as if it were a class. What’s a little confusing is that if you don’t create any instances at run time, you always get one for free—and it has the same name as the class. Thus, referring to Form1 at run time means different things in different contexts:

Form1.Caption = "My Form"         ' Form1 is an object name.
Dim frmAnotherForm As New Form1   ' Form1 is a class name.

The fact that forms are really classes is why defining public variables at the module level in a form appears not to work—trying to assign to these variables causes “Variable not defined” errors. In fact, you’re defining properties of the form, and these work in exactly the same way as class properties do. To refer to such properties in code, you need to qualify them with the object name, which, you’ll recall, is usually the same as the class name. (This is confusing if you do actually create multiple instances.) Even more interesting is that you can also define Property Let and Property Get procedures, Public methods, and even Friend functions in forms, just as you can in classes.

Because Visual Basic doesn’t support inheritance at the source code level, you can’t build value-added form classes; the best you can do is to build value-added form instances by adding custom properties and methods to your forms. You can do this by exploiting the classlike nature of forms and writing a form base class that contains extra properties and methods you’d like to see on every form. This works very well in practice, although it relies on you adding some standard code to every form you create. To see how this works, let’s build some methods to save and restore a form’s position when it loads and unloads.

The first thing you need to do is define a class, named CFormAttributes. You’ll create a Public instance of this class in every form you create, and this instance will appear as a property of the form. When you store the form positions with SaveSetting, it would be nice to use the form name as a key; unfortunately, there isn’t any way for an instance of a Visual Basic class to refer to the object that owns it. This means you’ll need to define the owner as a property in your CFormAttributes class and arrange to set it when you create the instance. Here’s the class:

Private frmPiSelf As Form

Public Sub SavePosition()
    SaveSetting App.Title, "Form Positions", _
                       frmPiSelf.Name & "-top", frmPiSelf.Top
    §
End Sub

Public Sub RestorePosition()
    §
End Sub

Public Sub LoadActions(ByVal frmiMe As Form)
    Set frmPiSelf = frmiMe
    RestorePosition frmPiSelf
End Sub

Public Sub UnloadActions()
    SavePosition frmPiSelf
End Sub

Notice that the LoadActions and UnloadActions methods are also defined. These make the class more general for when you add to it later. To add new properties to a form, you need to adopt certain conventions. First you need to define an instance of the class as a form-level variable:

Public My As New CFormAttributes

The variable is named My because it’s pretty close to Me, and semantically the two are similar. For example, you can now refer to My.UnloadActions. The only other thing you need to do is to make sure the LoadActions and UnloadActions routines are called:

Private Sub Form_Load()
    My.LoadActions Me
End Sub

Private Sub Form_Unload()
    My.UnloadActions
End Sub

You do have to pass the owner form reference to LoadActions to initialize the class’s Self property. You can find the complete class on the companion CD in CHAP14\atribcls\pubatrib.cls, and CHAP14\attribs\atr.vbp is an implementation of the program shown in Figure 14-2 on page 583.