When Windows was introduced, the way that users interacted with programs was changed forever. Gone were the days when a program ran from beginning to end with few user interactions. With Windows, users gained tremendous control over what functions they performed in a program and when these functions were performed. Although this was a great benefit to users, it provided an equally great challenge to programmers. This is because the developer of a program was no longer in control of the entire program experience. As a programmer, you now have to plan for many possible user activities, and in many cases, you must protect the user from himself.
NOTE: Although event-driven programs were around before Windows arrived on the scene (Macintosh programs and even a few DOS programs were event-driven), it was Windows that started the groundswell that has defined computing today.
The concept of an event-driven program might be fairly new to you, but the idea of responding to events should not be. Most of the world is what we would call event-driven. For example, consider your TV. You can change the channel whenever you feel like it, and with today's remote controls, you can go directly to your favorite channel instead of having to scroll through all the channels on the TV. By the same token, you can change the volume of the TV whenever you want by as much as you want. You can probably also cut out the sound altogether by pressing the mute button. This type of control over how and when things happen correlates to event-driven programming. You (the user of your TV) initiate an event (by pressing a button) that causes the TV to take an action appropriate to the event that occurred. You also control when these events happen.
If you have worked much with Windows programs, you probably have noticed that they work in a similar manner. In Microsoft Word, for example, you can easily change the font or style of a piece of text. You also can highlight a section of text and drag it to a new location. Each of these actions is made possible by the program's capability to take actions in response to user-initiated events.
As you create your own Windows programs, you will seek to model the program after the real-world tasks that the program is supposed to handle. This means that you will want to give the users command buttons or menu selections so that they can perform tasks when they want. Figure 7.1 shows an example of the interface for an event-driven program.
FIG. 7.1
Graphical user interfaces go hand in hand with event-driven programming.
Another advantage of event-driven programming is that you can use the events to provide immediate feedback to the user. For example, you can program an event to verify a user's entry as soon as the user finishes typing. Then, if there is a problem with what the user entered, he or she knows immediately and can fix it. This type of feedback is handled by code, such as that shown in Listing 7.1, which checks the age that a user has entered.
Private Sub txtAge_LostFocus() Dim inInputAge As Integer, stMsg as String inInputAge = Val(txtAge.Text) If inInputAge <= 0 Then stMsg = "You must enter a numeric age greater than 0!" MsgBox stMsg, vbCritical, "Age Input Demo" End If If inInputAge > 120 Then stMsg = "Wow! That's old! Change it if it's wrong." MsgBox stMsg, vbExclamation, "Age Input Demo" End If End Sub
In a typical program, there are many events that can occur and many user actions that can trigger these events. Program events can be triggered by such actions as the user pressing a key, clicking a mouse button, moving the mouse, changing the value of the information in a control, or switching to another window in the program. The program itself can cause events to occur. Windows events can also be triggered by system functions such as a timer, or even by external factors such as receiving an e-mail message. There are hundreds--if not thousands--of events that can occur in any given program. You want your program to be able to isolate these events and take action only for specific ones. Fortunately, this is not as hard as it sounds.
At this point, you might be wondering how in the world you are going to detect and handle all these possible events that might be occurring in your program. Well, the good news is that you don't have to do anything to detect events. This task is handled automatically by Windows.
When Windows senses an event taking place, it attempts to tell your running program about the event. Windows sends your running program a message. Your program must interpret that message, determine which event the message stands for, and then act accordingly. Still sounds like a lot of work, right? Well, this is where the strength of Visual Basic comes in. The messages sent by Windows are received and processed by the form(s) and controls that make up the interface of your program.
With Visual Basic, each control or form in your program is capable of recognizing only a select group of events. However, the key to handling events is to write code for only the events for which you want your program to take an action. This is done by selecting the particular object and event you want to handle, and then writing some program code in the Code window. The object is selected in the Code window's upper-left drop-down list box; the appropriate event procedure is selected in the upper-right drop-down list box. If there is code written for an event, Visual Basic processes the code when the event occurs. If there is no code for an event, Visual Basic ignores the event. Figure 7.2 shows an example of an event procedure written for a specific control and event.
FIG. 7.2
Visual Basic responds to an event only if you write code for it.
There are two basic types of events that can occur in your Visual Basic program--user-initiated events and system-initiated events. Most often, you program for the user-initiated events. These events let your users control the direction of the program. That is, your users can take a specific action whenever they want, which gives them almost complete control over your program.
NOTE: You can, of course, limit the actions that a user can take by hiding or disabling controls when you don't want the user to have access to them. This technique is discussed in Chapter 4, "Working with Forms and Controls."
See "Controlling User Interaction," Chapter 4
User-Initiated Events User-initiated events are those that occur because of an action taken by the user. As you might guess, these events include keystrokes and mouse clicks, but there are also other events caused by the user, either directly or indirectly. For example, when the user clicks a text box to start editing the information in the box, a Click event is fired for the text box. What you might not realize is that several other events are also fired. One is the GotFocus event for the text box. This event occurs every time the user moves to the text box, either by clicking the mouse or using the Tab key. Also, if the text box gets the program's focus, another control must lose the focus. This causes a LostFocus event to fire for the other control. The GotFocus and LostFocus events are caused by the user's action, just as the Click event is. As you will see in the later section "Understanding Event Sequences," multiple events can occur for each action a user takes. The order in which the events occur can be important. Here are some of the main user actions that trigger events in a program:
Common Events While there are a number of events to which forms and controls can respond, there are several events that many controls have in common:
Event | Occurs When |
Change | Occurs when the user modifies the text in a text box or combo box. |
Click | Occurs when the user clicks an object with the primary mouse button (usually, the left button). |
DblClick | Occurs when the user double-clicks an object with the primary mouse button. |
DragDrop | Occurs when the user drags a control to another location. |
DragOver | Occurs when an object is dragged over a control. |
GotFocus | Occurs when an object receives the focus. |
KeyDown | Occurs when a key is pressed while an object has the focus. |
KeyPress | Occurs when a key is pressed and released while an object has the focus. |
KeyUp | Occurs when a key is released while an object has the focus. |
LostFocus | Occurs just before the focus leaves an object. |
MouseDown | Occurs when a mouse button is pressed while an object has the focus. |
MouseMove | Occurs when the mouse cursor is moved over an object. |
MouseUp | Occurs when a mouse button is released while an object has the focus. |
You might have noticed that several of the events seem to correspond to the same user action. For example, the Click, MouseDown, and MouseUp events all occur when the user clicks the mouse button. Although some of the differences between the events are obvious--for example, the MouseDown event occurs when you press the mouse button--there are other differences between the events. In the case of pressing a mouse button, the Click event is fired only if the left mouse button is pressed; it does not respond to the click of any other mouse button. The MouseDown and MouseUp events not only respond to any mouse button, but the event also can report which button was pressed, so your program can take appropriate action.
The KeyDown, KeyPress, and KeyUp events work in a similar manner. The KeyPress event tells you only which key was pressed, not whether a Shift or Ctrl key was held down when the key was pressed. If you need that information, you need to use the KeyDown or KeyUp events.
With all these events going on, how do you make your code respond to any of the events? And how do you filter out the events that you don't want? The answer to both questions is the same. To respond to any event for any object, you write program code specifically for that event happening to that object. Any object/event combination that has no code written for it is ignored. So the next question is, how do you write code for an event?
To write code, you first need to access the code-editing window. Do this by double-clicking a control on your form, by clicking the View Code button in the project window, by selecting the Code item from the View menu, or by pressing F7. Any of these actions presents you with the Code window (see Figure 7.3).
FIG. 7.3
Double-clicking the command button named cmdShowDetail at design time presents
the form's Code window, opened to cmdShowDetail's Click event procedure;
here it has been partially coded.
In the Code window, you select the object and event for which you want to write code. When you make a selection, Visual Basic automatically sets up the skeleton of a procedure with the procedure name and the End Sub statement. Notice that the procedure name for an Event procedure contains the name of the object and the name of the event. At this point, you can write program statements to take any actions you desire for the event. The following code shows how the program displays a second form in response to the user clicking a command button:
Private Sub cmdShowDetail_Click() frmDetail.Show vbModal End Sub
NOTE: If you enter a Code window by double-clicking an object, the Code window automatically selects that object's most commonly used event procedure. For example, if you double-click a command button at design time, the Code window is opened to that command button's Click event procedure.
It was stated earlier that typically you would write separate procedures for each control on your form. There are times, however, when you want to write one procedure to handle the same event for multiple controls. The first step is to create a control array, as described in Chapter 19, "Advanced Control Techniques." After you have created a control array, open the Code window for one of the controls. You might notice a slight difference between the procedure declaration for the control that is part of a control array and one that is not. For a control array, the procedure declaration contains a parameter called Index, as shown in Figure 7.4. This parameter tells you which element of the control array was accessed.
FIG. 7.4
The Index parameter identifies which specific control array element
has received an event.
If you want to write different code for each of the elements of the array, you can use a Select Case block to do so. However, the most efficient use of the control array is to handle actions for groups of elements. The following two examples from a commercial program help illustrate the point.
In the first example, an array of TextBox controls is used to display membership information from a database. The information includes a person's name, address, phone number, and so on. This information is verified on a periodic basis. When certain information is changed, the application needs to set a flag to indicate that the record needs to be reverified. By using an If statement to check the index of the control array, the program can determine whether or not to set the flag. This is illustrated in the following code:
Private Sub txtMember_Change(Index As Integer) If Index >= 6 And Index <= 11 Then blCASSChange = True End Sub
In the second example, say that you want to change the foreground (text) color of all text boxes to red whenever someone makes a change to any one of them. This indicates to the user that editing is in progress. Without a control array, you would have to write code in the Change event of every text box. By using the control array, you have to write the code only once (see Listing 7.2).
Private Sub txtMember_Change(Index As Integer) Dim I As Integer For I = 0 To 22 txtMember(I).ForeColor = vbRed Next I End Sub
You also could use a different event and change the foreground color of the text box that has the focus. You would use the GotFocus event to change the color to red while the user is editing the text box, and use the LostFocus event to return the color to normal when the user leaves the text box. Listing 7.3 shows how this could be done.
Private Sub txtMember_GotFocus(Index As Integer) txtMember(Index).ForeColor = vbRed End Sub Private Sub txtMember_LostFocus(Index As Integer) txtMember(Index).ForeColor = vbBlack End Sub
Another way to handle an event for multiple controls is to use a Select Case statement to take a different action for each of the controls of the array. While you might think that doing this negates the advantage of using a control array, it does still provide you with a means to keep all the code in one place, instead of having it spread out over multiple procedures in your program. Listing 7.4 shows how this is done for the navigation buttons in a membership application. The actual form that uses this code is shown in Figure 7.5.
See "Using Select Case," Chapter 8
Private Sub cmdRecNav_Click(Index As Integer) Dim stSrchStr As String, stSrchStr2 As String, lgSrchID As Long Dim stBkMrk As String Dim rsTXARSet As Recordset, lgTXAID As Long Dim stTXAStr As String `Perform navigation operation Select Case Index Case 6 `Perform search operation If Val(txtSrch(0).Text) > 0 Then `Search on ID code - Need Member or Contact ID switch lgSrchID = Val(txtSrch(0).Text) stBkMrk = MemRset.Bookmark If optMemID(0).Value Then MemRset.Index = "Member" Else MemRset.Index = "Contact" End If MemRset.Seek "=", LgSrchID If Not MemRset.NoMatch Then StBkMrk = MemRset.Bookmark Else MsgBox "ID not found" End If txtSrch(0).Text = "" MemRset.Index = "Name" MemRset.Bookmark = StBkMrk ElseIf Not txtSrch(1).Text = "" Then `Search on user name If Not txtSrch(2).Text = "" Then StSrchStr = Trim(txtSrch(1).Text) StSrchStr2 = Trim(txtSrch(2).Text) MemRset.Seek ">=", StSrchStr, StSrchStr2 Else StSrchStr = Trim(txtSrch(1).Text) MemRset.Seek ">=", StSrchStr End If txtSrch(1).Text = "" txtSrch(2).Text = "" Else `Error message MsgBox "No valid search data was entered", vbOKOnly End If Case 4 fraNav.Visible = False fraSave.Visible = True Editing = False ClearMember EditColor Adding = True Case 5 `Delete the current record RetCode = MsgBox("Are you sure you want to delete this record?", _ vbYesNo, "Deletion Confirmation") If RetCode = vbYes Then MemRset.Delete RecordNav 1, MemRset End If Case Else RecordNav Index, MemRset `If a record was deleted, move to the previous record If Index = 5 Then RecordNav 1, MemRset End Select If Not Index = 4 Then ShowMember End Sub
FIG. 7.5
Navigation buttons are part of a control array.
Although event procedures are typically run only in response to an event, you can call these procedures just like any other procedure you would write. You might do this when two different types of controls are used to perform the same function. This is the case when you have a menu and a toolbar on a form. Because both perform the same function, you need to write only one of the event procedures to handle the event; the appropriate event procedure for the other control can simply call the first control's event procedure and pass any required parameters such as the control array index. For example, the following line of code would call the Click event procedure for the command button cmdRecNav (shown in Listing H.4), passing a parameter of 4 into the procedure's Index argument, just as if the user had clicked the Add command button (which has a control array index of 4):
cmdRecNav_Click 4
See "Creating a Toolbar for Your Application," Chapter 5
By now, you have an understanding of what events are and how Visual Basic handles them. You have seen how to write code to take action when an event occurs. But you need to dive just a little deeper into the world of events.
As stated, a user action or system event can trigger multiple events. This can be a good thing because you can use these different events to handle different situations, as in the case of the MouseDown and Click events. However, there is a flip side. (Isn't there always?) If you write code for multiple events that can occur, these procedures can interact in ways that you don't want. In the worst case, a sequence of events--each with its own event procedure--can put your system into an infinite loop. There are several keys to avoiding these problems:
One of the problems of handling multiple events is that there is almost an infinite number of ways in which the user can interact with your program. For example, did the user move to the text box with a mouse or with the Tab key? Prior to the move, was the focus on another text box or was it on a command button? And what happens when you move from one form to another? As you can see, the possibilities can be almost overwhelming.
The good news, though, is that you will not write code for most control/event combinations. Therefore, even though these events might occur, your program ignores them--and they do not cause you any problems.
Even though this simplifies the task of handling multiple events, it does not eliminate it. To get a handle on this, take a look at some simple sets of events that occur when a user performs an action.
First, look at the simple keystroke. Not all controls respond to keystrokes. But for the ones that do, every time the user presses a key, three events are fired--KeyDown, KeyPress, and KeyUp (in that order). If the keystroke happens to move the focus from one control to another, two additional events are triggered--LostFocus for the current control and GotFocus for the new control.
Next, consider the innocent mouse click. This simple user action also fires three events--MouseDown, MouseUp, and Click. If the user double-clicks the mouse, two more events occur after the Click event--the DblClick event and another MouseUp event. That's five events for what would seem like a single user action. In addition, if the mouse click causes the focus to move from one control to another, the LostFocus and GotFocus events would be triggered, for a total of seven events.
Finally, there is also the problem that different actions to achieve the same purpose cause different event sequences. For example, you know that to change the value of a check box, you can either click the box with the mouse or press the spacebar while the check box has the focus. But did you know that different events occur depending on how you check the box? Using the mouse triggers the MouseDown, MouseUp, and Click events. Using the spacebar triggers the KeyDown, KeyPress, KeyUp, and then the Click events. It can be confusing, can't it?
However, there are ways to determine what events will occur in your program and in what order they will occur.
In trying to figure out whether the interaction between events will cause a problem, the first step is to ignore every event that you don't need. If you write code for only the Click event, you don't care when or if the keystroke or mouse events occur. The only time you have to be concerned is when you are writing code for multiple related events.
NOTE: The GotFocus and LostFocus events are related to just about everything else. Because these events occur whenever the focus moves from one control to another, any action that can change the focus will trigger these events. Many programmers use the LostFocus event to handle data validation. While this is the perfect place to handle this task, the event interactions can cause problems.
See "Common Events," Chapter 7
The next step in determining the event interaction is to map the order of events in a program. I've created a simple program that stores in a list box information about most of the events that can occur to a text box, a command button, and their form. Each time an event occurs to one of these objects, a line is added to the list box reporting the name of the event that just occurred. By experimenting with different actions to the form, command button, and text box, you can see the order of events. You can also see how one user action can lead to an entire sequence of events. For example, clicking the command button can cause these events to occur in this order: MouseDown, GotFocus (assuming it didn't already have the focus), Click, and MouseUp. Figure 7.6 shows how this program records the results of these actions (the "Clear and reset" button was clicked just before taking these actions):
This program responds to common events as they happen to a text box, a command button, and a form. Each of these events has an event procedure that adds an item reporting the event procedure's name to the list box on the left side of the form. The entire program is available on the companion CD-ROM, as EVENTSEQ.VBP. You can modify this program to handle any object/event combinations you want.
FIG. 7.6
The order of many different combinations of events can be recorded in this
demonstration program.
Whether you use a tool like this program, or some other method, it's critically important to understand the sequence of events. Once you realize that one user action can lead to several events, you can use that information to plan which events you want to write code for. For example, you can use a text box's KeyDown event, which is the first event that occurs when the user presses a key while the text box has the focus, to determine if the user has pressed Ctrl. That may be an indication within your program of some special action, such as opening a new record or saving initialization information. Whatever the case, you should test your applications thoroughly, using a variety of sequences of actions to your objects, to make sure that unexpected event sequences won't cause undesired results.
This chapter focused on how Visual Basic handles events. But events are useful only in the context of forms and controls because these are the objects capable of receiving events. Also, events can cause an action in your program only if some code has been written for the event. You can learn more about these related topics in these chapters:
© Copyright, Macmillan Computer Publishing. All rights reserved.