vbu07






Visual Basic 4 Unleashed vbu07.htm



















7 — Events





by Bill Hatfield





Introducing the Event-Driven Paradigm





An Example



Objects and Events



A Shift of Control



Looking at Event-Driven Gotchas



Application Startup



Application Shutdown



Digging Deeper Mapping Visual Basic Events to Windows Messages



Kicking Off Your Own Events





PostMessage



SendMessage





Setting Tab Stops



Performing Magic with Multiline Edits



Finding a String in a Listbox



Dropping Down a Listbox Automatically



Summary









7 — Events







by Bill Hatfield



Visual Basic is a Visual Development Environment (VDE). In fact, it was the first VDE for Windows. A VDE is more than a programming language. It is a complete environment tailored to the needs of a programmer writing applications for a Graphical User
Interface (GUI) environment.



If you have never programmed in a VDE and you are new to Visual Basic, then this new way of doing things will take some getting used to. Programming in Visual Basic is a very different experience from programming in a traditional language like C,
Pascal, or COBOL. Probably the biggest difference between the two is the event-driven paradigm. Let me explain.







Introducing the Event-Driven Paradigm



In traditional development environments, you, as the developer, were in complete control. You determined what happened first and then what happened next. That's not to say that the users didn't have input. They did, of course. But for the most part, if

the application was at step three, for example, you knew for sure that steps one and two already had been completed. You could depend on it because there was no way the user could have gotten to step three without going through the first two steps. This
sort of certainty helped to simplify coding and error checking.



An application that makes use of the event-driven model works very differently. It starts up by doing some initialization code, just as the traditional system did, but at some point the application just stops. It doesn't do anything. The user stares at

the application and the application stares back. And nothing happens until the user makes a move. What kind of move? Well, nearly anything qualifies. She could simply move her mouse. Or she could click a menu or a button. When she does, she sets into
motion a series of events that cause the application to respond.







An Example



Let's say the user clicks a button. The operating system is monitoring very closely every move the user makes. So it doesn't fail to notice the click. The operating system also notices that the clicked button is a part of the Visual Basic application
you wrote, so it sends a message to this application. Message here is a technical term, but it means pretty much what you might think. It is as if Windows takes out its quill pen and writes a brief note that says,



Dear VB Application,

The user has just clicked on the OK button that is on your main window.

Love,

Windows

It then drops it in the mailbox to be delivered to the application. A couple of milliseconds later when your application reads the message, it responds by generating an event on the appropriate object. The object, in this case, is the button on the main

window. And the event is Click.



Now it just so happens that when the application was written, the programmer had an inkling that the user might click on that button. So he wrote a routine that was associated with the Click event of the OK button on the main window. In fact, that is
how you will do almost all your programming in an event-driven application. You first choose an object. Then you choose an event that can happen to that object. And finally, you write a routine that will be executed at runtime when that event happens to
that object.







Objects and Events



What are some examples of objects? The button in the earlier example was an object. But if there are text boxes, labels, drop-down listboxes, and other controls on the window, all of those are objects as well. Even the window itself is an object.



Events are simply things that can happen to an object. A given object can have many events. A text box can receive the focus (the GotFocus event). And it can lose the focus (LostFocus). Its text can be changed (Changed). It even can be clicked on like
the button was (Click). All these are events associated with a text box control. A button shares some of these events (like Click, GotFocus, and LostFocus) but not all of them. And the button may even have a few of its own events that the text box doesn't

have. Table 7.1 summarizes some often-used events common to many objects.





Table 7.1. Visual Basic common object events.









Event





Event Description







Change



Occurs again and again as changes are made in a text control.





Click



Occurs when the user presses and then releases a mouse button over an object.





DblClick



Occurs when the user rapidly presses the mouse button twice.





DragDrop



Occurs when the user presses and holds the mouse button down over a dragable object, moves the mouse to a new position, and then releases the mouse button (drops the dragged object). The DragDrop event is an event of the object that was dropped on, not

the one being dragged.





DragOver



Occurs in an object when another object is being dragged across it.





GotFocus



Occurs when an object receives the focus. Happens when the user tabs to or clicks to move focus to the object.





KeyDown, KeyUp



Occurs when the user presses (KeyDown) or releases (KeyUp) a key while an object has the focus.





KeyPress



Occurs when the user presses and releases a key.





LostFocus



Occurs when an object loses the focus. Happens when a user tabs to or clicks on another object.





MouseDown, MouseUp



Occurs when the user presses (MouseDown) or releases (MouseUp) a mouse button.





MouseMove



Occurs when the user moves the mouse across the screen. MouseMove is generated again and again dozens and dozens of times as the mouse moves from one place to another.

Will you, as a programmer, write code to respond to all these different events? No. In fact, you probably will write code only for a small portion of them. You will simply write for the events you think should be responded to in your application.



Do all events happen because the user did something? No. Although most events ultimately happen because of user intervention, Windows itself also can generate events based on a timer, the system clock, or the changing status of a connected peripheral.







A Shift of Control



But wait. If all your code is in these little event routines and the routines are executed only when the associated event happens, then how do you know which events will happen first? The answer is simple: You don't. And that's why event-driven
programming can be more challenging than traditional development. If you are in step three, you have to check to see whether steps one and two have been done. If not, you have to decide whether they really must be done before step three. If they do, you
will have to inform your users. If not, let your users do it however they like.



This points to a subtle but very important shift in priorities. You, the developer, are no longer the one in control. The user is. In fact, the user should be the center of the universe you create. This concept, called user-centric design, is the
cornerstone to developing truly great user interfaces.







Looking at Event-Driven Gotchas



In the event-driven world, there are times when you can be writing code that looks completely harmless but causes problems when run. Look at the window in Figure 7.1.





FIGURE 7.1. A simple Visual Basic form.



The first text box is txtName and the second is txtPhone. Now suppose that the code in the LostFocus event of the first text box looks like Listing 7.1.





Listing 7.1. The LostFocus event of txtName.

Private Sub txtName_LostFocus()

If txtName.Text = "" Then

Beep

txtName.SetFocus

End If

End Sub

Now suppose that the LostFocus event of the second text box looks like Listing 7.2.





Listing 7.2. The LostFocus event of txtPhone.

Private Sub txtPhone_LostFocus()

If txtPhone.Text = "" Then

Beep

txtPhone.SetFocus

End If

End Sub

Looks harmless enough. If either of these is empty, you will want your application to beep when users try to tab away and force them to enter something in the text box.



But if you run this code and simply press Tab right away, what will happen? First, the focus moves to txtPhone. Then the LostFocus event of txtName is executed (because it just lost the focus to txtPhone). txtName's LostFocus checks to see whether
txtName is empty. Since txtName is empty, it beeps and sets the focus back on txtName. So far so good. But setting the focus back on txtName causes txtPhone to trigger its LostFocus event. It goes through the same process and tries to set the focus back to

it. This, in turn, triggers the txtName LostFocus event, creating an endless loop.



This example shows how easy it can be to unintentionally create bad situations in event-driven code. Remember that everything you do in code has the potential for triggering other events, and if those events trigger events that eventually trigger your
code again, you easily can create an endless loop.



The moral of this particular story is this: Be careful when using a SetFocus command in a LostFocus or GotFocus event. What are some other situations to watch out for?



Table 7.2 lists some actions and corresponding events likely to be triggered.





Table 7.2. Actions and events triggered.









Action





Event(s) Triggered







Form Refresh



form's_Paint





Changing the Text property of a text box



text box's Change





Changing the Height or Width properties of a form



form's_Paint and form's_Resize

If you perform any of these actions in the event script of an event they could trigger, you risk an endless loop. If you change the height of the form in the form's Resize event, for example, you almost certainly will cause an endless loop that will
generate an Out of Stack Space error. Again, these are just a few common examples. If you get an Out of Stack Space error or some other form of endless loop, be sure to check for this kind of situation.







Application Startup



So if our application is event-driven and code is only written in response to events, how do things ever get started? How your application starts depends on the setting in the Options dialog box. To access the Options dialog box, choose Options from the

Tools menu. The Options tabbed dialog box appears. Now choose the Project tab. The first control is Startup Form.



Visual Basic assumes that the first thing you want to do is display a form. And, of course, this usually is correct. It further assumes that the first form you create is the one you will want to display when your application starts. This is much more
likely to be a wrong assumption. After you have created several forms in your application and then want to test it, be sure to come here to the Options dialog and verify that the correct form is specified.



But what if you don't want a window to display first thing? Suppose that you want to do some initialization or checking of the system before you actually display a form? If you click on the Startup Form drop-down listbox, you will see that one of the
options is Sub Main. This option enables you to create a subroutine named Main and to have it be the first thing executed. After it finishes its processing, it probably will show a form or two, but executing the subroutine first gives you a chance to
verify that everything is okay before you get started.



You also would use Sub Main if you wanted to create a batch application that does all its processing behind the scenes without any user interaction. This kind of application is more unusual, but it is always good to have the option.







Application Shutdown



The next logical question is how does this whole thing end? The simplest answer is that it ends when the last form of your application is unloaded. That's the way it looks from the user's perspective. But the fact is that you still can have code
executing behind the scenes long after the user thinks your application has died. So it is better to say that the application ends when the last of its components has left memory.



But that isn't very helpful from a programming perspective. How do you wrap up processing for an application in your code? The Unload command removes a form from memory. Assuming that nothing else is running, an Unload on the last form that is up shuts

down the application. So generally, you don't have to worry about anything special when you are creating a simple application. Just have each form do its own cleanup in the form's Unload event.



What about larger MDI applications? Here is the process that takes place when the user indicates that he wants to close a MDI frame:





All the MDI child windows receive a QueryUnload event, each in turn, followed by the MDI frame's QueryUnload. In this event, each of the windows can decide whether it is OK for the application to close. This event often is used to query the user to see

whether she wants to save data or cancel the close. Any of the windows can choose to cancel the close. And, in doing so, all the sheets and the frame stay open.





If none of the child windows cancel to the close, the Unload event occurs for each child window, and then it closes.





After all the child windows are closed, the frame receives an Unload event and subsequently closes. Because the MDI frame is usually the last window up at this stage, the application ends.









NOTE



Can't you cancel the Unload in the Unload event? Actually, yes, you can. But if you do it there, you won't know the current state of the application. Some or all of the other child windows already may have been unloaded by that time. Using the
QueryUnload for canceling ensures that everything is left as it was.





What if you don't want to go through all this checking and verifying? You can stop your application cold in its tracks by executing the End command.







WARNING



Be careful with End. It kills the application right away. No QueryUnload—not even an Unload event—occurs. So if you have housekeeping chores or any other code in the Unload of your forms, it will not be executed.





Another way to freeze your program is by using the Stop command. This is a break. It differs from End in that it leaves all your variable values intact so that you can use the Debug window to check them. You even can restart after a Stop. You will use
Stop a lot in debugging, but your final code probably will never have a need to use it.







Digging Deeper Mapping Visual Basic Events to Windows Messages



So Visual Basic creates a visual, easy-to-use way of creating Windows applications. It receives Windows messages and converts them into events for which you can write code. But sooner or later, you are going to want to go further with your applications.

Sometimes you will hear developers talk about "hitting walls" with environments like Visual Basic. Although Visual Basic makes it easy to access the most common capabilities of Windows, it doesn't really erect many walls that prevent you from
going deeper. You just have to know how it's done.



Suppose that you want to make use of a Windows message that is not captured in Visual Basic. The first step is to be absolutely sure that it isn't captured in some way. Although many messages are captured as form or control events, some are captured
through methods as well. Table 7.3 lists window messages and how they are captured within Visual Basic.





Table 7.3. Windows messages and how they are captured in Visual Basic.









Windows Message





VB Event/Method/Property





Description







WM_ACTIVATE



Form_Activate



Indicates a change in the activation state





WM_ACTIVATEAPP



None



Notifies applications when a new task is activated





WM_ASKCBFORMATNAME



GetFormat() Clipboard function



Retrieves the name of the Clipboard format





WM_CANCELMODE



None



Notifies a window to cancel internal modes





WM_CHANGECBCHAIN



None



Notifies Clipboard viewer of removal from chain





WM_CHAR



Form_KeyPress, Form_KeyDown



Passes keyboard events to focus window





WM_CHARTOITEM



None, managed by Visual Basic



Provides listbox keystrokes to owner window





WM_CHILDACTIVATE



MDIChildForm_ Activate



Notifies a child window of activation





WM_CHOOSEFONT_GETLOGFONT



None, managed by Visual Basic



Retrieves LOGFONT structure for Font dialog box





WM_CLEAR



Used as the control object.Clear method or Text1.Text = ""



Clears an edit or combo box





WM_CLOSE



Form_Unload,Form_QueryUnload



Signals a window or application to terminate





WM_COMMAND



object_Click (menu or command button)



Specifies a command message





WM_COMMNOTIFY



None



Notifies a window about the status of its queues





WM_COMPACTING



None



Indicates a low-memory con-dition





WM_COMPAREITEM



Used as the object.ListIndex method



Determines position of combo box or listbox item.





WM_COPY



Same as SetText() and SetData() Clipboard methods



Copies a selection to the Clipboard





WM_CREATE



Form_Load



Indicates that a window is being created





WM_CTLCOLOR



None



Indicates that a control is about to be drawn





WM_CUT



Used as SetText() or SetData() Clipboard methods



Deletes a selection and copies it to the Clipboard





WM_DDE_ACK







Acknowledges the receipt of a DDE transaction





WM_DDE_EXECUTE



object.LinkExecute()



Passes a command to a DDE server





WM_DDE_INITIATE



object.LinkMethod()



Initiates a DDE conversation





WM_DDE_POKE



object.LinkPoke()



Sends an unsolicited data item to a server





WM_DDE_REQUEST



object.LinkRequest()



Requests value of a data item from a DDE server





WM_DDE_TERMINATE



object.LinkClose()



Terminates a DDE conversation





WM_DEADCHAR



None



Indicates when a dead key is pressed





WM_DELETEITEM



None



Indicates that owner-drawn item or control is altered





WM_DESTROY



Form_QueryUnload



Indicates that window is about to be destroyed





WM_DESTROYCLIPBOARD



None



Notifies owner when Clipboard is emptied





WM_DEVMODECHANGE



None



Indicates when device-mode settings are changed





WM_DRAWCLIPBOARD



None



Indicates when Clipboard contents are changed





WM_DRAWITEM



None



Indicates when owner-drawn control or menu changes





WM_DROPFILES



object_DragDrop



Indicates when a file is dropped





WM_ENABLE



None



Indicates when enable state of window is changing





WM_ENDSESSION



None



Indicates whether the Windows session is ending





WM_ENTERIDLE



None



Indicates that a modal dialog box or menu is idle





WM_ERASEBKGND



None, closest item is the AutoRedraw property



Indicates when a window background needs erasing





WM_FONTCHANGE



None



Indicates a change in the font-resource pool





WM_GETDLGCODE



None



Allows processing of control input





WM_GETFONT



None



Retrieves the font that a control is using





WM_GETMINMAXINFO



None, however can be linked to Form_Resize



Retrieves minimum and maximum sizing information





WM_GETTEXT



Used as GetText() or GetData() Clipboard methods



Copies the text to a corresponding window





WM_GETTEXTLENGTH



Same as Len(object.Text)



Determines length of text associated with a window





WM_HSCROLL



object.Scroll where object is a horizontal scroll bar



Indicates a click in a horizontal scroll bar





WM_HSCROLLCLIPBOARD



None



Prompts owner to scroll Clipboard contents





WM_ICONERASEBKGND



None



Notifies minimized window to fill icon background





WM_INITDIALOG



None



Initializes a dialog box





WM_INITMENU



None



Indicates when a menu is about to become active





WM_INITMENUPOPUP



Used as object.PopUpMenu method



Indicates when a pop-up menu is being created





WM_KEYDOWN



object_KeyDown



Indicates when a nonsystem key is pressed





WM_KEYUP



object_KeyUp



Indicates when a nonsystem key is released





WM_KILLFOCUS



object_LostFocus



Indicates window is about to lose input focus





WM_LBUTTONDBLCLK



object_DblClick



Indicates double-click of left mouse button





WM_LBUTTONDOWN



object_MouseDown



Indicates when left mouse button is pressed





WM_LBUTTONUP



object_MouseUp



Indicates when left mouse button is released





WM_MBUTTONDBLCLK



object_DblClick



Indicates double-click of middle mouse button





WM_MBUTTONDOWN



object_MouseDown



Indicates when middle mouse button is pressed





WM_MBUTTONUP



object_MouseUp



Indicates when middle mouse button is released





WM_MDIACTIVATE



MDIChildForm Activate



Activates a new MDI child window





WM_MDICASCADE



Used as the MDIForm.Arrange() method



Arranges MDI child windows in a cascade format





WM_MDICREATE



None



Prompts a MDI client to create a child window





WM_MDIDESTROY



MDIChildForm QueryUnload, MDIChildForm_Unload



Closes a MDI child window





WM_MDIGETACTIVE



None



Retrieves data about the active MDI child window





WM_MDIICONARRANGE



MDIForm.Arrange()



Arranges minimized MDI child windows





WM_MDIMAXIMIZE



None



Maximizes a MDI child window





WM_MDINEXT



None



Activates the next MDI child window





WM_MDIRESTORE



None



Prompts a MDI client to restore a child window





WM_MDISETMENU



None



Replaces the menu of a MDI frame window





WM_MDITILE



MDIForm.Arrange()



Arranges MDI child windows in a tiled format





WM_MEASUREITEM



None



Requests dimensions of owner-drawn control





WM_MENUCHAR



None



Indicates when unknown menu mnemonic is pressed





WM_MENUSELECT



MenuObject_Click



Indicates when a menu item is selected





WM_MOUSEACTIVATE



Form_Activate



Indicates a mouse click in an inactive window





WM_MOUSEMOVE



object_MouseMove



Indicates mouse-cursor movement





WM_MOVE



None



Indicates the position of a window has changed





WM_NCACTIVATE



None. Visual Basic does not provide access to any non-client events



Changes the active state of a nonclient area





WM_NCCALCSIZE



Used as the ScaleWidth and ScaleHeight properties



Calculates the size of a window's client area





WM_NCCREATE



None



Indicates that a nonclient area is being created





WM_NCDESTROY



None



Indicates when nonclient area is being destroyed





WM_NCHITTEST



None



Indicates mouse-cursor movement





WM_NCLBUTTONDBLCLK



None



Indicates non-client left button double-click





WM_NCLBUTTONDOWN



None



Indicates left button pressed in nonclient area





WM_NCLBUTTONUP



None



Indicates left button released in nonclient area





WM_NCMBUTTONDBLCLK



None



Indicates middle button nonclient double-click





WM_NCMBUTTONDOWN



None



Indicates middle button pressed in nonclient area





WM_NCMBUTTONUP



None



Indicates middle button released in nonclient area





WM_NCMOUSEMOVE



None



Indicates mouse-cursor movement in nonclient area





WM_NCPAINT



None



Indicates that a window's frame needs painting





WM_NCRBUTTONDBLCLK



None



Indicates right button nonclient double-click





WM_NCRBUTTONDOWN



None



Indicates right button pressed in nonclient area





WM_NCRBUTTONUP



None



Indicates right button released in nonclient area





WM_NEXTDLGCTL



Used as the SetFocus() method



Sets focus to a different dialog box control





WM_PAINT



Form_Paint



Indicates that a window frame needs painting





WM_PAINTCLIPBOARD



None



Paints the specified portion of the window





WM_PALETTECHANGED



None



Indicates that focus window has realized its palette





WM_PALETTEISCHANGING



None



Informs windows about change to palette





WM_PARENTNOTIFY



None



Notifies parent of child-window activity





WM_PASTE



Used as Text1.Text =Clipboard.GetText (vbCFText)



Inserts Clipboard data into an edit control (text box)





WM_POWER



None



Indicates that the system is entering suspended mode







WM_QUERYDRAGICON



None



Requests a cursor handle for a minimized window





WM_QUERYENDSESSION



None



Requests that the Windows session be ended





WM_QUERYNEWPALETTE



None



Enables a window to realize its logical palette





WM_QUERYOPEN



None



Requests that a minimized window be restored





WM_QUEUESYNC



None



Delimits CBT messages





WM_QUIT



Form_QueryUnload, Form_Unload



Requests that an application be terminated





WM_RBUTTONDBLCLK



object_DblClick



Indicates a double-click of right mouse button





WM_RBUTTONDOWN



object_MouseDown



Indicates when the right mouse button is pressed





WM_RBUTTONUP



object_MouseUp



Indicates when the right mouse button is released





WM_RENDERALLFORMATS



None



Notifies owner to render all Clipboard formats





WM_RENDERFORMAT



None



Notifies owner to render particular Clipboard data





WM_SETCURSOR



object.ScreenMouse Pointer property



Displays the appropriate mouse cursor shape





WM_SETFOCUS



object_GotFocus



Indicates when a window has gained input focus





WM_SETFONT



Used as the object.FontName property



Sets the font for a control





WM_SETREDRAW



None



Allows or prevents redrawing in a window





WM_SETTEXT



Used as the .Text or .Caption



Sets the text of a window properties





WM_SHOWWINDOW



None



Indicates that a window is about to be hidden or shown





WM_SIZE



Form_ReSize



Indicates a change in window size





WM_SIZECLIPBOARD



None



Indicates a change in Clipboard size





WM_SPOOLERSTATUS



None



Indicates when a print job is added or removed





WM_SYSCHAR



None



Indicates when a system-menu key is pressed





WM_SYSCOLORCHANGE



None



Indicates when a system color setting is changed





WM_SYSCOMMAND



None



Indicates when a system command is requested





WM_SYSDEADCHAR



None



Indicates when a system dead key is pressed





WM_SYSKEYDOWN



object_KeyDown



Indicates that Alt plus another key was pressed





WM_SYSKEYUP



object_KeyUp



Indicates that Alt plus another key was released





WM_SYSTEMERROR



None



Indicates that a system error has occurred





WM_TIMECHANGE



None



Indicates that the system time has been set





WM_TIMER



TimerObject_Timer



Indicates that the time-out interval for a timer has elapsed





WM_UNDO



None



Undoes the last operation in an edit control





WM_USER



None



Indicates a range of message values





WM_VKEYTOITEM



None



Provides listbox keystrokes to owner window





WM_VSCROLL



object_Scroll



Indicates a click where object is a vertical scroll bar





WM_VSCROLLCLIPBOARD



None



Prompts the owner to scroll Clipboard contents





WM_WINDOWPOSCHANGED



Form_Resize



Notifies a window of a size or position change





WM_WINDOWPOSCHANGING



Form_Resize



Notifies a window of a new size or position





WM_WININICHANGE



None



Notifies applications of change to WIN.INI

As you can see, some of the Windows messages map directly to events in Visual Basic. Others map directly to methods or properties. Some loosely map to an event, method, or property that Visual Basic has chosen to handle in a more generalized way. And
finally, there are some that simply are unavailable. These same things could be said of the messages associated with controls in Table 7.4.





Table 7.4. Windows messages associated with Visual Basic controls.









Windows Message





VB Event/Method/Property





Indicates That







BN_CLICKED



CommandButton _Click



The user clicked a button





BN_DISABLE



CommandButton.Enabled



A button is disabled





BN_DOUBLECLICKED



CommandButton _DblClick



The user double-clicked a button





BN_HILITE



CommandButton _GetFocus



The user highlighted a button





BN_PAINT



None



The button should be painted





BN_UNHILITE



CommandButton_LostFocus



The highlight should be removed





CBN_CLOSEUP



None



The listbox of a combo box has closed





CBN_DBLCLK



None



The user double-clicked a string





CBN_DROPDOWN



None



The listbox of a combo box is dropping down





CBN_EDITCHANGE



ComboBox_Change



The user has changed text in the edit control





CBN_EDITUPDATE



None



Altered text is about to be dis- played





CBN_ERRSPACE



None



The combo box is out of memory





CBN_KILLFOCUS



ComboBox_LostFocus



The combo box is losing the input focus





CBN_SELCHANGE



ComboBox.ListFocus



A new combo box list item is selected





CBN_SELENDCANCEL



None



The user's selection should be canceled





CBN_SELENDOK



None



The user's selection is valid





CBN_SETFOCUS



ComboBox_GotFocus



The combo box is receiving the input focus





EN_CHANGE



TextBox_Change



The display is updated after text changes





EN_ERRSPACE



None



The edit control is out of memory





EN_HSCROLL



None



The user clicked the scroll bar





EN_KILLFOCUS



TextBox_LostFocus



The edit control is losing the input focus





EN_MAXTEXT



Text .MaxLength



The insertion is truncated





EN_SETFOCUS



TextBox_GotFocus



The edit control is receiving the input focus





EN_UPDATE



None



The edit control is about to display altered text





EN_VSCROLL



Vscroll_Scroll



The user clicked the vertical scroll bar





LBN_DBLCLK



ListBox_DblClick



The user double-clicked a string





LBN_ERRSPACE



None



The list box is out of memory





LBN_KILLFOCUS



ListBox_LostFocus



The list box is losing the input focus





LBN_SELCANCEL



None



The selection is canceled





LBN_SELCHANGE



ListBox_Change



The selection is about to change





LBN_SETFOCUS



ListBox_GetFocus



The list box is receiving the input focus





Kicking Off Your Own Events



Windows sends messages to applications indicating what the user is doing, and the application responds. I also mentioned that events can be triggered by timers or the system clock. So can you, as a developer, trigger events? Can you send a message to
other windows in your own application or even other applications? Well, if you just want to execute the code associated with the clicked event of your Update button, for example, you can simply code this:



Call cmdUpdate_Click()

This doesn't actually click the button (it doesn't cause the graphic to appear depressed), but it does execute the code in the Click event.



But what if you want to go further? You actually want to send a real Windows message to a window or control—can you do it? The answer is yes, but not in native Visual Basic. You'll have to resort to Win32.



The two primary Win32 functions you will use are SendMessage() and PostMessage(). These functions both do the same thing: They ship off a message to the window or control indicated (either in this application or in another one). The difference is that
SendMessage() is synchronous and PostMessage() is asynchronous. That is, SendMessage() sends the message and waits until it has arrived there and has taken effect before it returns control to your application. PostMessage(), on the other hand, simply fires

off the message and immediately returns control to your application. SendMessage(), then, works more like a function call. You know it is completely finished by the time the next line executes.







PostMessage



Suppose that your application wants to shut down another application. Using PostMessage, it is easy. First you have to declare the Win32 function, as shown in Listing 7.3.





Listing 7.3. The PostMessage declare statement.

Declare Function PostMessage Lib "user32" Alias "PostMessageA"

(ByVal hWnd As Long, _

ByVal wMsg As Long, _

ByVal wParam As Integer, _

lParam As Any) As Long

hWnd identifies the window to which the message is posted. If this parameter is HWND_BROADCAST, the message is posted to all top-level windows, including disabled or invisible windows.



wMsg specifies the message to be posted.



wParam specifies 16 bits of additional message-dependent information. lParam specifies 32 bits of additional message-dependent information.



The return value is nonzero if the function is successful. The only way it would return zero is if the receiving application's message queue is full. That can happen when you send the same application many messages without giving it a chance to respond.

If you run into this problem, try sprinkling a few Yield() statements around. You will use only PostMessage() with Windows. You'll see how the more flexible SendMessage() can be used with controls later.



After you declare PostMessage(), you need to declare a few other Win32 functions you'll use along the way, as shown in Listing 7.4.





Listing 7.4. Additional declarations.

Declare Function IsWindow Lib "User" (ByVal Hwnd As Integer) As Integer

Declare Function GetWindow Lib "User" (ByVal Hwnd As Integer, ByVal wCmd As

_Integer) As Integer

Declare Function GetWindowLong Lib "User" (ByVal Hwnd As Integer, ByVal nIndex As _Integer) As Long

Declare Function PostMessage Lib "User" (ByVal Hwnd As Integer, ByVal wMsg As _Integer, ByVal wParam As Integer, ByVal lParam As Long) As Integer

Declare Function FindWindow Lib "User" (ByVal lpClassName As Any, ByVal _lpWindowName As String) As Integer

Const GW_OWNER = 4

Const GWL_STYLE = -16

Const WS_DISABLED = &H8000000

Const WS_CANCELMODE = &H1F

Const WM_CLOSE = &H10

Listing 7.5 is the EndTask function. It receives a window handle. It verifies that the window handle is valid and that it is not disabled. Then the WM_CANCELMODE and WM_CLOSE messages are passed with no parameters (0 and 0& in the word and long
params).





Listing 7.5. The EndTask function.

Function EndTask(TargetHwnd As Integer) As Integer

Dim X As Integer

Dim ReturnVal As Integer

ReturnVal = True

If TargetHwnd = hWndMe% Or GetWindow(TargetHwnd, GW_OWNER) = hWndMe% Then

End

End If

If IsWindow(TargetHwnd) = False Then

ReturnVal = False

Else

If Not (GetWindowLong(TargetHwnd, GWL_STYLE) And WS_DISABLED) Then

X = PostMessage(TargetHwnd, WM_CANCELMODE, 0, 0&)

X = PostMessage(TargetHwnd, WM_CLOSE, 0, 0&)

DoEvents

End If

End If

EndTask% = ReturnVal

End Function

Finally, Listing 7.6 is the Form Load event procedure, which uses EndTask to take down Notepad if it happens to be running when this window opens. You can use similar code wherever you need this functionality.





Listing 7.6. The Form Load event.

Sub Form_Load()

Dim Hwnd As Integer

Dim Y As Integer

Hwnd = FindWindow(0&, "Notepad")

If Hwnd = 0 Then

MsgBox "NotePad is not running", vbInformation

Exit Sub

Else

MsgBox "NotePad will now be closed", vbInformation

End If

Y = EndTask(Hwnd)

If Y <> 0 Then

MsgBox "NotePad terminated", vbInformation

Else

MsgBox "Error - Cannot terminate NotePad", vbInformation

End If

End Sub





SendMessage



SendMessage() is the synchronous sibling of PostMessage(). Unlike PostMessage(), SendMessage() tends to get used frequently to send messages to windows and controls within the same application. Why would you want to send a message to a control on your
own form? Well, it turns out that this is a good way to retrieve some of the functionality that Visual Basic steals away from you and to get around some of those "walls."



Here are some examples of things you can do with the appropriate SendMessage() that Visual Basic doesn't normally let you do:





Setting tab stops in listboxes





Getting a single line of text from a multiline text box





Finding a string in a listbox





Although you can implement your own code to find a string in a listbox, you don't have to. Windows already has that functionality built right in. All you have to do is access it through SendMessage()!



The declaration and parameters are very similar to PostMessage(), as shown in Listing 7.7.





Listing 7.7. The SendMessage() declare statement.

Declare Function SendMessage Lib "user32" Alias "SendMessageA"

(ByVal hWnd As Long, _

ByVal wMsg As Long, _

ByVal wParam As Integer, _

lParam As Any) As Long

hWnd identifies the window to which the message will be sent. If this parameter is HWND_BROADCAST, the message is sent to all top-level windows, including disabled or invisible unowned windows.



uMsg specifies the message to be sent.



wParam specifies 16 bits of additional message-dependent information. lParam specifies 32 bits of additional message-dependent information.







Setting Tab Stops



So how do you put it to use? Well, normally, if you include a Chr$(9) (a tab character) in a string that you put in a listbox or a multiline edit, the control simply moves the following text to its next tab stop. Visual Basic gives you no way to set
these tab stops to be where you want them, however. But you can with SendMessage(). Listing 7.8 shows you how.





Listing 7.8. Setting the tab stops in a listbox.

Dim nTabPos(3) As Integer

Dim lResult As Long

Rem Define the positions

nTabPos(0) = 10

nTabPos(1) = 50

nTabPos(2) = 90

Rem Set the focus to the target listbox and fire away

lstExample.SetFocus

lResult = SendMessage(GetFocus(), LB_SETTABSTOPS, 0, ByVal 0&)

lResult = SendMessage(GetFocus(), LB_SETTABSTOPS, 3, nTabPos(0))

First, an array is created and then filled in with the desired tab positions. Then an LB_SETTABSTOPS with a 0 and ByVal 0& is used to clear any existing tab stops. Finally, LB_SETTABSTOPS with the number of array elements (3) and the first array
element (nTabPos(0)) actually sets the tab stops.



You can set the tab stops in a multiline text box in the same way. Just send the message EM_SETTABSTOPS instead of LB_SETTABSTOPS.







Performing Magic with Multiline Edits



Speaking of multiline text boxes, there are several useful messages you can send to a multiline text box to get information that Visual Basic does not provide. You can send the EM_GETLINECOUNT message, for example, to get the number of lines of text in

a multiline text box:



lLineCount = SendMessage(GetFocus(), EM_GETLINECOUNT, 0, ByVal 0&)

In addition, you can get the text of any individual line in a multiline text box with the message EM_GETLINE. But this message is a little tricky because it requires you to pass three parameters:





The number of the line you want to retrieve





The string to be filled with the contents of the line





The length of that string





Because there are only two parameters, you will have to do a little trickery. You'll put the number of the line you want in the third parameter and the string in the fourth. But in order to tell it how long the string is, you'll have to embed the number

in the first two bytes of the string, as shown in Listing 7.9.





Listing 7.9. Placing the length of the string in the first two bytes.

Dim nLineNum As Integer

Dim nLineLen As Integer

Dim sTemp As String

Rem Set the max line length to 200

nLineLen = 200

Rem Fill The string with nulls and then paste the length to the front

sTemp = String$(nLinLen, vbNullChar)

sTemp = Chr$(nLinLen Mod 256) + Chr$(nLineLen \ 256) + sTemp

sTemp = Left$(sTemp, SendMessage(GetFocus(), EM_GETLINE, nLinNum, ByVal sTemp))

Another handy message for multiline text boxes is EM_LINESCROLL, which enables you to scroll your text boxes horizontally and vertically. Just specify the number of characters to scroll in the fourth argument of the SendMessage() function. Place the
number of characters to scroll horizontally in the high order word (by multiplying by 65,536) and the number of lines to scroll vertically in the low order word. See Listing 7.10.





Listing 7.10. The ScrollText subroutine.

Sub ScrollText (txtSubject As TextBox, nHorizontal As Integer, nVertical As _Integer)

Const EM_LINESCROLL = &HB6

Dim lResult As Long

Dim lScroll As Long

lScroll = (nHorizontal * 65536) + nVertical

txtSubject.SetFocus

lResult = SendMessage(GetFocus(), EM_LINESCROLL, 0, ByVal lScroll)

End Sub

Keep in mind that this is a relative scroll. So if you use the value 3, the text box scrolls down three lines. If you use the value —65,536 (—1 times 65,536), the text box scrolls left one char-acter.







Finding a String in a Listbox



A searching capability already is built into the listbox. But Visual Basic doesn't let you get to it. SendMessage() does. The code for FindStringInList is in Listing 7.11. It receives a string and a listbox or combo box. It returns True or False,
indicating whether it succeeded in finding the string in the listbox. If it did find it, the ListIndex for the listbox or combo box is set to the place it was found.





Listing 7.11. Using SendMessage() to find a string in a listbox.

Function FindStringInList(sString As String, lstBox As ListBox) As Boolean

Rem This subroutine finds the string that was passed to it and then

Rem sets the passed combo box to that value

Const LB_ERR = —1

Const LB_FINDSTRING = (&H400 + 16)

Dim lResult As Long

Dim nZero As Integer

lResult = SendMessage(lstBox.hWnd, LB_FINDSTRING, 0, ByVal sString)

If lResult = LB_ERR Then

FindStringInList = False

Else

FindStringInList = True

lstBox.ListIndex = lResult

End If

End Function





Dropping Down a Listbox Automatically



Here's a neat trick for a drop-down list combo box (a combo box with the Style property set to 2). This code drops the list automatically when the combo box gets the focus (see Listing 7.12).





Listing 7.12. GotFocus drops down the listbox automatically.

Sub Combo1_GotFocus ()

Rem When this control receives focus it will

Rem automatically drop down

Dim lResult As Long

Const CB_SHOWDROPDOWN = WM_USER + 15

lResult = SendMessage(GetFocus(), CB_SHOWDROPDOWN, 1, ByVal 0&)

End Sub

Sending messages in the GotFocus event for a control is a good technique because it enables you to avoid explicitly setting the focus to a control to get its hWnd.







Summary



This chapter was a whirlwind tour of event-driven programming and the use of events in

Visual Basic. I began with a description of what it means to be event-driven in comparison to using traditional techniques. After some warnings about potential pitfalls, you explored

application startup and shutdown. Then you saw the window and control events and how they applied to Visual Basic. Finally, you learned about some key Win32 functions that enable you to make use of Windows messages to get at some of the built-in
Windows functionality that Visual Basic hides.













Wyszukiwarka