Managing Windows

The most striking feature of the majority of commercial Visual Basic applications is the set of windows they create and manipulate, including the way the user gets around these windows and the ways in which the windows interact with one another. Often a window-management scheme is something that simply evolves during implementation: the developer might not have a clear idea about how each window will behave with respect to other windows, and the window-management features built into Visual Basic might be the factor that most influences how the design turns out. This isn't so much a poor design strategy as the lack of a strategy, and the resulting problems can be anything from poor usability to insidious bugs.

Visual Basic has a mixed bag of window-management tricks, the simplest of which are MsgBox and InputBox. These are modal dialog boxes, so you must deal with them before the program will remove them and continue its processing. There isn't much to say about MsgBox and InputBox except that they are inflexible. In particular, you can't change the button captions, and you don't have complete control over size and position of the dialog boxes. For added flexibility, you can, of course, write your own Visual Basic functions named MsgBox and InputBox to override the Visual Basic ones. Interestingly, doing this allows you to make nonmodal message boxes, the consequences of which will become clear later.

Visual Basic also has features to support multiple-document interface (MDI) applications, and the decision to build with MDI will have a major influence on the way your application works. MDI has some advantages—chiefly that it is well defined and that Visual Basic will implement some of the features for you (menu management, window arrangements, and so on). On the other hand, adopting MDI for these reasons alone is futile if the application you want to build doesn't fit the rigid MDI model. MDI supports a document model and usually makes sense only if your application is going to work with multiple instances of things that are somehow document-like. Document-like qualities include serialization (binding to permanent storage) and size independence, which gives meaning to the familiar MDI window-arrangement functions Tile and Cascade.

On the other side of the fence from MDI is single-document interface (SDI). Because Visual Basic has no specific functions to support SDI, however, a more accurate description would be "not MDI." You have much more flexibility when building non-MDI applications, but you lose out on some of the free functionality such as window arrangement, child window menu handling, and Ctrl+Tab to switch between child windows. On the other hand, you have more control over your application's appearance, and you can choose whether to make your child forms modal or modeless.

Finally, you can build hybrid applications that borrow some features from MDI without using built-in functionality. You can, for example, create multiple instances of an ordinary form (forms behave just like classes in many ways), and you can even create MDI parent forms dynamically within a non-MDI application. It's important to consider these issues in advance and to plan a window-management scheme appropriate to the application you want to build.

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 in 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 13-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 13-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.

Although the forms shown in Figure 13-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 call 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 13-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.

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

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 13-1, all you need to do is to create form B as modeless, then 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 13-2.

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

Figure 13-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.

Toward a General Modality Class

You can both create and manipulate value-added forms by building a CFormAttributes class (see the "Forms Are Classes Too") and adding the function modality mechanism to it. The central requirement for such a mechanism is to associate a parent with each form you create. You can do this by adding a Parent property to the CFormAttributes class:

Public Parent As Form 

Now you have somewhere to store a reference to the parent form, so you need to arrange for this reference to be set when the form is loaded. Since you can't pass parameters to a form's Show method (or to the CFormAttributes instance), you need to do this manually from outside the CFormAttributes class. You want to be able to do something like this:

Public Sub ShowChild Child:=frmReview, Parent:=Me 

You could make this a global procedure and give it its own BAS file, but it's better to keep all the code in one place by making ShowChild a method of the CFormAttributes class. Obviously, this means you can't invoke ShowChild to display the first form in a hierarchy, but the only implication of this is that you need to make sure that the CFormAttributes class recognizes that it has no parent when it is destroyed. You can also dispense with the Parent parameter since you already have a Self reference in the CFormAttributes class. Here's the method in the CFormAttributes class, which is named NewChild:

Public Sub NewChild(ByVal frmiChild As Form)     frmiChild.Show     Set frmiChild.My.Parent = frmPiSelf     frmPiSelf.Enabled = False End Sub 

The last statement is the significant one because it's the one that disables the parent form and creates a new mode. You need a reciprocal action to reenable the parent when the form unloads, so you need to define another method:

Public Sub EnableParent()     If Not Me.Parent Is Nothing Then Me.Parent.Enabled = True End Sub 

Unfortunately, there's no elegant way to bind this to a form unload; you must ensure that you call this method from each Form_Unload event:

Private Sub Form_Unload()     My.EnableParent End Sub 

In fact, the sample code in CHAP13\atribcls\pubatrib.cls has a generic UnloadActions method, which takes the place of EnableParent, but this discussion is clearer if I continue to refer to an EnableParent method.

That takes care of modal child forms, as long as you invoke them with My.NewChild and include the appropriate reciprocal call in the Form_Unload event. You can now build on this to extend the mechanism. To cope with the swapping in the sample program, for example, you need to do a couple of extra things: pass on the outgoing form's parent reference to the new form and then prevent the parent from being reenabled when the old form unloads. You can do this by adding a new method and modifying the EnableParent method slightly so that the two communicate through a module-level flag:

Private bPiKeepParentDisabled As Boolean Public Sub SwapMe(ByVal frmiNewChild As Form)     frmiNewChild.Show vbModeless     If frmiNewChild.Enabled Then         Set frmiNewChild.My.Parent = Parent         bPiKeepParentDisabled = True     End If     Unload frmPiSelf End Sub Public Sub EnableParent()     If Not bPiKeepParentDisabled Then         If Not Parent Is Nothing Then Parent.Enabled = True     End If End Sub 

Notice the check to find out whether the form you're trying to swap to is enabled. If it isn't, it must already have been loaded, in which case you'll just leave the Parent property alone. This is an ad hoc test that works in the simple examples shown here, but it might not be general, and so you'll need to extend the mechanism to cope with other situations. For example, the mechanism as it stands won't prevent you from trying to swap to a form that's in the middle of a modal cascade—in fact, this would orphan any child forms in the cascade. With a little thought, you should be able to extend the mechanism to allow swapping to remove child forms of the form you're trying to swap to, to prevent swapping between forms belonging to other functions in a function modal situation, or to support any other flavors of modality you care to invent.

Extending the CFormAttributes Class

The beauty of a value-added form class is that it's a simple matter to add new features retrospectively. As an example, let's look at how you can add support for pseudo-MDI minimize and restore behavior. Because all document windows in an MDI application are contained within the client area of the parent window, minimizing that window naturally takes away all of the children too. This is convenient since it instantly clears the application off the desktop (without closing it, of course).

The MDI window feature in Visual Basic gives you this minimize behavior for free. With an SDI or a DIY-DI application, however, you have no such luxury. Because a Visual Basic form has no Minimize event, you must write code that plugs into the Resize event and decide for yourself when a form is minimized or restored by investigating the WindowState property. The behavior we're going to construct will watch for transitions from normal to minimized and from minimized back to normal. (This second operation is usually called "restore.") We'll write the code as a new method of the CFormAttributes class and then simply add a call to it from appropriate Resize event handlers.

Trapping the event, of course, is only half the story—you also need to do something to take away the rest of the forms. One possibility is to set the WindowState to follow the window containing the trap, but in practice that looks messy because Windows animates zoom boxes all over the place and you end up with lots of task bar buttons (or icons in earlier versions of Microsoft Windows NT). It's quicker and visually more effective to hide all the other forms when you trap a minimize event and to restore them when you trap a restore event. The only tricky part is to remember the prevailing state of each form before hiding it, just in case any were hidden already. Here's the code you'll need:

Public PreviouslyVisible As Boolean Private nPiPrevWindowState As Integer Public Sub PropagateMinMaxEvents ()     If frmPiSelf.WindowState = vbMinimized _             And nPiPrevWindowState = vbNormal Then         Call HideAllForms     ElseIf frmPiSelf.WindowState = vbNormal _             And nPiPrevWindowState = vbMinimized Then         Call UnhideAllForms     End If     nPiPrevWindowState = frmPiSelf.WindowState End Sub Private Sub HideAllForms()     Dim frmForm As Form     For Each frmForm In Forms         If Not frmForm Is frmPiSelf Then             frmForm.My.PreviouslyVisible = frmForm.Visible             frmForm.Visible = False         End If     Next frmForm End Sub Private Sub UnhideAllForms()     ' This is just the opposite of HideAllForms. End Sub 

To activate the new behavior, you need to choose which forms will trigger it and call PropagateMinMaxEvents from their Resize event handlers. The publication editing program referred to in Figure 13-2 has this call coded in the Resize events of all the forms, so minimizing any form hides all the others and shows a single button on the task bar. Restoring from that button restores each form to its previous state. To add minimize behavior to the example application shown in Figure 13-1, you would code a single call to PropagateMinMaxEvents in the Resize event of the main form (the one carrying the menu bar). This mimics the MDI paradigm more closely because of the definite parent form.

Visual Basic 6 has another trick that you could use here, which is to add custom Minimize and Restore events to your forms through the CFormAttributes class. You can do this very simply by making a small modification to the PropagateMinMaxEvents method shown here.

Event Minimize() Event Restore() Public Sub PropagateMinMaxEvents ()     If frmPiSelf.WindowState = vbMinimized _             And nPiPrevWindowState = vbNormal Then         RaiseEvent Minimize     ElseIf frmPiSelf.WindowState = vbNormal _             And nPiPrevWindowState = vbMinimized Then         RaiseEvent Restore     End If     nPiPrevWindowState = frmPiSelf.WindowState End Sub 

In case you didn't spot it, calls to HideAllForms and UnhideAllForms have been replaced with calls to the Visual Basic procedure RaiseEvent. This diminutive keyword is very powerful, and you'll see other examples of it later in the chapter. When you define the CFormAttributes instance on a form, a new object, My, appears in the code window's Object drop-down list box, and when you choose it, you'll see Minimize and Restore events in the Procedure drop-down list box. These events work in exactly the same way as normal events do, so selecting Minimize inserts an empty procedure named My_Minimize into the code. One caveat is that the syntax for defining the CFormAttributes instance is slightly different if you want to see the events:

Public WithEvents My As CFormAttributes 

Unfortunately, the New keyword is not allowed in combination with the WithEvents keyword, so you'll also need to add a line to the Form_Load event:

Private Sub Form_Load()     Set My = New CFormAttributes     My.LoadActions Me End Sub 

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 CHAP13\atribcls\pubatrib.cls, and CHAP13\attribs\atr.vbp is an implementation of the program shown in Figure 13-2.



Ltd Mandelbrot Set International Advanced Microsoft Visual Basics 6. 0
Advanced Microsoft Visual Basic (Mps)
ISBN: 1572318937
EAN: 2147483647
Year: 1997
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net