'; zhtm += ''; zhtm += '

' + pPage + ''; zhtm += ''; window.popUpWin.document.write(zhtm); window.popUpWin.document.close(); // Johnny Jackson 4/28/98 } //--> Using Visual Basic 6 -- Ch 23 -- Programming Beyond Visual Basic Using the Windows API


Using Visual Basic 6

Previous chapterNext chapterContents


- 23 -
Programming Beyond Visual Basic Using the Windows API


Understanding the Windows API


Understanding DLLs

A DLL (dynamic link library) is compiled code that can be accessed only by other programs at the programming level. You put functions and subroutines into DLLs so that you can share them among many programs running at the same time. DLLs are made in languages such as C(++) and Object Pascal (Delphi). You also can make DLLs in VB, except that they're called ActiveX DLLs. For more information about making ActiveX DLLs in VB, read Chapter 26, "Making Object-Oriented Programs with Visual Basic."


The Windows Application Programming Interface (API) is a set of hundreds of predefined functions built into the DLLs that make up the Windows operating system. End users can't access these functions. However, programmers can access the code written in the DLLs through the API and use this code in their programs (see Figure 23.1). This allows you to use existent code in the DLLs and save you time in the programming development cycle.

FIGURE 23.1 The most often used DLLs in the API for 32-bit Windows operating systems are Kernel32.DLL, User32.DLL, and GDI32.DLL.

You can program any type of Visual Basic project to access the Windows API by using the Declare statement in the General Declarations section of a module. For example, one function that you can access is GetCursorPos, which locates the onscreen cursor position. You define it for use within Visual Basic as follows:

Declare Function GetCursorPos Lib "user32" _    Alias "GetCursorPos" (lpPoint As POINTAPI) As Long

In this syntax,


Windows-defined types

A type is also known in C(++) as a structure. A structure is a group of variables organized under one name. When they're grouped, you can create instances of the type, as you would create a variable. (See Chapter 26, "Making Object-Oriented Programs with Visual Basic.") When you program directly in Windows by using the API, sometimes you have to use Windows-defined types, such as POINTAPI. You can find the definition of these Windows-defined types within the API Viewer tool provided with your copy of VB or within the documentation of the Windows Software Development Kit (SDK), which comes on the Developers Network CD-ROM included in your copy of VB.


As you can see, GetCursorPos resides in the DLL User32.DLL. The function takes one argument, lpPoint, which is a Windows-defined type, POINTAPI. The function returns a Long. (Listing 23.1 shows you how to define the POINTAPI Windows-defined type.)

LISTING 23.1  23LIST01.TXT--The API and Subsequent Windows-Type
Declaration for GetCursorPos

01 Declare Function GetCursorPos Lib "user32" _
(lpPoint As POINTAPI) As Long
02
03 Type POINTAPI
04 x As Long
05 y As Long
06 End Type

You don't need any tools other than VB itself to use the Windows API. Working with the API requires a lot of extra knowledge, however. If you plan to do any coding with the Windows API, you need a copy of The Microsoft Developers Network CD-ROM, which ships with the Professional and Enterprise versions of Visual Basic and documents all the available Windows API functions. It's an extensive work that provides a lot of sample code. Sadly, most of the code is in C, so you might need a good third-party Visual Basic book that deals with the Windows API. One such resource is Macmillan's Web site at http://www.mcp.com, where you can view many books about Windows programming in the library and even purchase a book from the online catalog.

Working with the API Viewer

Although you don't need any additional tools to work with the Windows API (Application Programming Interface), Visual Basic ships with a tool, the API Viewer, that enables you to cut and paste API functions, constants, and Windows-defined types right into your code. You access the API Viewer from the Visual Basic Start menu. Also, you can add the API Viewer to your VB IDE in the form of an Add-In, thus having it readily available to you while you work.

Simply put, the API lets you access the DLLs in the Windows environment for programming purposes. Microsoft wisely put all reusable code into DLLs (dynamic link libraries) so they can be used over and over. This not only saves valuable programming time but also standardizes the programming community, as it allows the same code to be used repeatedly. This may not seem very important in some instances, but with the same code running in many similar applications, they become more compatible.

Add the API Viewer to the Add-In menu

1. Choose Add-In Manager from Visual Basic's Add-Ins menu.

2. Select VB 6 API Viewer from the Available Add-Ins list.

3. In the Load Behavior section in the lower right corner of the Add-In Manager dialog, select Load on Startup. (If this option is already checked, the API Viewer should already be available in the Add-Ins menu.)

4. Click OK.

5. Close Visual Basic and restart it.

6. Choose API Viewer from the Add-Ins menu to open the API Viewer window.

In the following sections, you'll build a small program that shows you how to use some API functions to do things you normally can't do in VB. You'll use the API Viewer to retrieve functions, constants, and Windows-defined types to help you accomplish this. You'll also build a function that reports back the position of the cursor anywhere onscreen. To do this, you need to use the API function GetCursorPos.

To use the API Viewer to retrieve GetCursorPos, you first need to configure the viewer for first-time use.

Configure the API Viewer

1. Start the API Viewer.

2. Choose Load Text File from the File menu (see Figure 23.2).

FIGURE 23.2 You load API definition data into the API Viewer from either a text file or a database file.

3. Select the file Win32API.txt from the Open File dialog.

4. When the text file is loaded into the API Viewer (this takes about 15 seconds), choose Convert Text to Database from the File menu.

5. Save the database file with the filename suggested by the API viewer, Win32API.mdb.

The API Viewer doesn't have API data definition built into it; you must load the data at the beginning of every session. After you configure the API Viewer to read its API definition data from a database file, you select the database file whenever you need to use the API Viewer. Although you can use the provided text file, Win32API.txt, using the database file makes the API Viewer perform faster.

After you configure the API Viewer, you can use it to select functions, constants, and Windows-defined types associated with the Windows API.

Select the API function GetCursorPos

1. Select Declares from the API Type drop-down list (see Figure 23.3).

2. Type GetCursorPos in the text box below the API Type drop-down list (See Figure 23.4).


API Viewer auto-searching

The API Viewer auto-searches input made in the search TextBox against items listed in the Available Items list. However, it takes time for the API Viewer to do a search. Therefore, if you type slowly, the API Viewer performs more accurately.


3. When the GetCursorPos function is located in the Available Items list, click the Add button to add the function to the Selected Items list.

4. From the API Type drop-down list, select Types.

5. Type the word POINTAPI in the search text box.

6. Click the Copy button to send the entire function and type definitions to the Clipboard (see Figure 23.5).

7. Exit and start a new session of Visual Basic.

8. Choose Add Module from Visual Basic's Project menu.

9. Paste the function definition for GetCursorPos and the type definition for POINTAPI from the Clipboard to the General Declaration section of the module (see Figure 23.6).

FIGURE 23.3 You can locate constants, declares (API functions), and types in the API Viewer.

FIGURE 23.4 When you enter a word in the search text box, the API Viewer looks through the available items on a letter-by-letter basis.

FIGURE 23.5 The Selected Items list holds selected constants, declares, and types that you can copy to the Clipboard in one process.

Notice that GetCursorPos takes a Windows-defined type, POINTAPI, as an argument. You need to include the type definition in your VB project for the code to work.

FIGURE 23.6 You must copy API declarations into the General Declarations section of a module.

Monitoring Mouse Movement with GetCursorPos

Now that you've seen how to use the API Viewer to work more easily with the Windows API, let's put the function GetCursorPos to work in a real-world example.

GetCursorPos reports the position of the mouse pointer anywhere onscreen. Normally, a Visual Basic application is limited to reporting the position of the mouse pointer with the boundaries of itself, so when the mouse pointer is moved beyond a Visual Basic application's form, the application has no idea where the cursor is. Windows always knows where the mouse pointer is, however, and reports the location through the GetCursorPos API function.


Wrapper function

A wrapper function is one that primarily uses the functionality of another function, as if the function were wrapping itself around another.


This use of GetCursorPos is illustrated in the project prjAPIStuff.vbp (see Figure 23.7), which you can download from http://www.mcp.com/info. At the prompt for the book's ISBN, enter 078971633x and then click the Search button to access the book Info page for Using VB6. You can then download the code. Within the project, in the module modAPIStuff is a user-defined function, ReportMousePos(), which returns a string that reports back the location of the mouse pointer in friendly language. The function is a wrapper function for GetCursorPos. Listing 23.2 shows the structure of ReportMousePos().

FIGURE 23.7 The APIStuff project demonstrates various API functions.

LISTING 23.2  23LIST02.TXT--ReportMousePos() Wraps GetCursorPos

01 Public Function ReportMousePos() As String
02
03 Dim MyPointAPI As POINTAPI
04 Dim l As Long
05 Dim strReport As String
06
07 `Call the API function
08 l = GetCursorPos(MyPointAPI)
09
10 `Make a report string
11 strReport = "X=" & CStr(MyPointAPI.X) & ", "
12 strReport = strReport & "Y= " & CStr(MyPointAPI.Y)
13
14 `Return the value of the report string
15 If l > 0 Then
16 ReportMousePos = strReport
17 Else
18 ReportMousePos = "Error"
19 End If
20
21 End Function

Notice that line 3 initializes POINTAPI. The actual declaration of the type and the GetCursorPos function were done in the General Declarations section of the module. Line 8 calls the GetCursorPos function, using the type instance MyPointAPI as an argument. Windows passes back the x and y location of the mouse pointer to the elements of MyPointAPI. If the value of l (the return buffer) is greater than 0, the call was successful. Lines 11 and 12 concatenate a string that reports back the pixel location of the mouse pointer with some "x:" and "y:" labeling. If the operation was successful (line 15), the reporting string is returned by the ReportMousePos() function. Otherwise, an error string is returned (line 18).

This function is called from within a Timer control's Timer() event procedure. The Timer() event procedure assigns the function's return value to the Caption property of a Label control. The event procedure is as follows:

Private Sub Timer1_Timer()
    lblMousePos.Caption = ReportMousePos()
End Sub

GetCursorPos doesn't report back the mouse pointer position unless called. Therefore, the Timer control is used to poll Windows every 1/10 of a second for the location of the mouse pointer.

For more information about timers, see Chapter 16, "Working with Time and Timers."

Keeping a Window Always on Top by Using SetWindowPos

Sometimes you might want to write an application that has an "always-on-top" feature. An always-on-top feature ensures that your application's window isn't covered by another active application's window. This sort of feature is typical among utilities such as clocks that need to be visible at all times, whether or not they're running in the background.


ByRef versus ByVal

By default, Visual Basic passes arguments to a function ByRef (by reference), which means that VB passes the address of the location in a computer's memory where the argument lives. When you're working within the usual confines of Visual Basic, this is no cause for concern; when you move into API programming, however, passing memory locations to an external procedure can cause a problem. Therefore, most of the time you pass only the value of the argument to a function, not the address of the memory location--hence, the use of the keyword ByVal (by value).


To make your application always-on-top, you use the Windows API function SetWindowPos. The definition for SetWindowPos, which is put in the General Declarations section of a module, is as follows:

Public Declare Function SetWindowPos Lib "user32" _         (ByVal hWnd As Long, _          ByVal hWndInsertAfter As Long, _          ByVal X As Long, _          ByVal Y As Long, _          ByVal cx As Long, _          ByVal cy As 
Long,_          ByVal wFlags As Long) As Long


What's an hWnd?

Part of the Windows operating system's job is to make windows. Therefore, whenever you start a program, Windows makes all the windows the program needs on invocation. On creation, every window that Windows makes is assigned a number, called the window handle and referred to as an hWnd. Some windows contain other windows. Most of the intrinsic controls are considered windows and have their own window handles. Windows assigns the value of a control's hWnd to the control's HWnd property.


The arguments for this function are as follows:

These constants can be retrieved from the API Viewer. For a full description of the constants and other associated values for SetWindowPos, see the documentation that comes with your copy of VB.

The way you use SetWindowPos to make a window always on top is straightforward. First, you have to use SetWindowPos only once a session. When you call the function, Windows will juggle or adjust the bits in memory to tell the window in question whether to be on top all the time. After you set the always-on-top bit, you don't have to do anything else, unless (of course) you want to reset the window to not be on top all the time.

The way you set the always-on-top bit is to use the constant HWND_TOPMOST for the argument hWndInsertAfter. When you pass this value, Windows automatically knows how to set the window to be on top all the time. To avoid resizing or repositioning issues, you need to combine the constants SWP_NOMOVE and SWP_NOSIZE and pass them as the wFlags argument. Combining the values effectively tells Windows to keep the size and position that the user determines.

To use this API function for your coding needs, you must retrieve the function's description as well as the values for the constants associated with the function, using the API Viewer. Figure 23.8 shows the retrieved functions and constants included in the Selected Items list.

FIGURE 23.8 You can add API and associate functions to the Selected Items list before you copy them to the Clipboard.

Listing 23.3 shows an abridged version of the user-defined function SetWinPos() from the module of the prjAPIStuff.vbp project. SetWinPos() is a wrapper function for the SetWindowPos API function.

LISTING 23.3  23LIST03.TXT--SetWinPos() Wraps SetWindowPos

01 Public Function SetWinPos (iPos As Integer, _
lHWnd As Long) As Boolean
02 Dim lWinPos As Long `A variable to hold the value of
03 `the API window position constant
04 Dim l As Long
05
06 `Use a SELECT CASE to set the value of the
07 `API Window constant
08
09 Select Case iPos
10 `The window is set to its regular position
11 Case 0
12 lWinPos = HWND_NOTOPMOST
13 `Set the window always on top
14 Case 1
15 lWinPos = HWND_TOPMOST
16 `You have a bad value; leave the function
17 Case Else
18 Exit Function
19 End Select
20
21 `Run the API SetWindowPos function
22 If SetWindowPos(lHWnd, lWinPos, 0, 0, 0, 0, SWP_NOMOVE _
+ SWP_NOSIZE) Then
23 `If the function is greater than 0 (FALSE), the operation
24 `was successful. Return a True to indicate such.
25 SetWinPos = True
26 End If
27 End Function

The user-defined SetWinPos function takes two arguments: iPos, an integer that determines whether the function will set a window always on top or not, and lHWnd, the handle of the window to process. Lines 9-19 are a Select Case statement that determines which value to pass to the hWndInsertAfter parameter of the SetWindowPos API function. If iPos is 0, the window to be processed isn't always on top (HWND_NOTOPMOST); if iPos is 1, the window is always on top (HWND_TOPMOST). Any value greater than 1 is considered an error, and the function is terminated, returning the default value of False.

Line 22 is the core of the user-defined function--this is where the API function is called. SetWindowPos takes it the value of its HWnd (window handle) argument from lHwnd, the value passed into the user-defined function SetWinPos (line 1). lWinPos is determined within the Select Case statement in lines 9-19. The size and position arguments x, y, cx, and cy are all set to 0 because the window being affected retains its original values. The combined values of the constants SWP_NOMOVE and SWP_NOSIZE are passed to the wFlags argument, effectively telling Windows to not change any of the original size and position settings of the window. If the API function is successful, a True value is passed out of SetWinPos (line 25). You can see a more fully commented version of this code on the Web at http://www.mcp.com.

The way end users select whether they want the window in project prjAPIStuff to be always on top at runtime is to check the Keep on Top check box (refer to Figure 23.7). When the user checks the check box, the Click() event procedure for the control is called, which in turn calls the user-defined wrapper function SetWinPos. Listing 23.4 shows the code for the event procedure.

LISTING 23.4  23LIST04.TXT--Using a Wrapper Function to Set a Window Always on Top

01 Private Sub chkOnTop_Cligck()
02 Dim b As Boolean
03 If chkOnTop.Value = 1 Then
04 b = SetWinPos(1, Me.hWnd)
05 Me.Caption = "Always on Top"
06 Else
07 b = SetWinPos(0, Me.hWnd)
08 Me.Caption = "Not Always on Top"
09 End If
10
11 `If b returns false, the API function was unsuccessful
12 If Not b Then
13 MsgBox "Positioning Error", vbCritical, "Program Error"
14 End If
15 End Sub


The keyword Me

Me is a Visual Basic keyword that refers to the current form or class object in which the word is used.


If the value of the check box is set to 1 (checked), the wrapper function sends the API function instructions to set the window (as determined by the argument Me.hWnd) to always on top (line 4). Also, end users are sent a notification in the form's title bar that the window is set to always on top (line 5). If it's not checked, the window is set to not always on top (line 7) and an associated notification is sent to the end user (line 8).

Dragging a Window by Using SendMessage

Usually, you drag a window around the computer screen by clicking while the mouse pointer is over a window's title bar and then dragging. To move a window without using the title bar, you trick Windows into thinking that your mouse is over the title bar--by using the API function SendMessage.

Windows is an operating system that uses messaging extensively. Every time something happens in Windows--for example, a mouse movement, a key press, or a window creation--a message is sent. Every second, thousands of numeric messages are sent to all the windows through the Windows environment, sort of like a very fast, internal mail delivery system. Not only are messages sent by the internals of Windows, but you, the programmer, also can send messages to Windows by using the SendMessage API function. The structure of SendMessage is as follows:

Public Declare Function SendMessage Lib "user32" _        Alias "SendMessageA" (ByVal hWnd As Long, _        ByVal wMsg As Long, _        ByVal wParam As Long, _        lParam As Any) As Long

The handle of the window to which the message is being sent

The numeric message

A packet of numeric data specific to the message being sent

The return value of SendMessage also is specific to the message being sent.

To manipulate Windows into thinking that when you do a mouse move on a form, you're doing a mouse move on the form's title bar, you must send Windows a message by means of WM_NCLBUTTONDOWN in the form's MouseMove event procedure. WM_NCLBUTTONDOWN is a numeric message that Windows translates to mean that the left mouse button is down in the nonclient area of a window. Before you can send Windows the new message, however, you must nullify the message that Windows receives when you initially move your mouse over the form. You do so by using the ReleaseCapture() API call.


Client area

The client area of a window is where end users enter data. Formally, the client area is the region of a window that doesn't include the title bar, menus, toolbars, status bars, or window borders. In Visual Basic, the width and height to the client area can be determined by examining the value of a form's ScaleWidth and ScaleHeight properties, respectively.


Make your form movable by dragging from anywhere on the form

1. Copy the ReleaseCapture and SendMessage API functions from the API Viewer to a module's Declarations section (see Figure 23.9).

FIGURE 23.9 Use the Windows API to copy the ReleaseCapture and SendMessage functions from the API Viewer to a module's declarations section.

2. Copy the constants WM_NCLBUTTONDOWN and HTCAPTION for the API Viewer to the same module's Declarations section.

3. Copy the code in Listing 23.5 to the MouseMove event procedure of a project's form. Omit the line numbers.

LISTING 23.5  23LIST05.TXT--The Code for Dragging a Window from a Form

01 Dim l As Long `Buffer for function return
02 `If the left button has been pressed
03 `and the Drag checkbox is checked
04 If Button = 1 And chkMove.Value = 1 Then
05 `terminate the mouse move message to the form
06 ReleaseCapture
07 `Send a new message to trick Windows into thinking
08 `that the user is dragging around the form's title
09 `bar
10 l = SendMessage(Me.hwnd, WM_NCLBUTTONDOWN, _ HTCAPTION, 0)
11 End If


Using the logical AND

You can combine two conditions into one If...Then statement by using the logical AND operator, which compares the two conditions. If both evaluate to True, the entire If...Then statement is True. If either condition evaluates to False or both are False, the entire statement is False.


When you move a mouse over a form, the form's MouseMove event is raised, thus allowing you to program the form's MouseMove event procedure. The code in Listing 23.5 checks whether the left mouse button is pressed during movement. The code also checks whether the Drag from Form check box (refer to Figure 23.7) has a check mark (chkMove in line 4). If these conditions are True, the program invokes the API function ReleaseCapture to nullify the default mouse action (line 6). Then, the program sends a new message to Windows, making Windows think that the mouse is being dragged over the form's title bar (lines 10-11). The constant HTCAPTION is a parameter associated with the WM_NCLBUTTONDOWN message that tells Windows that the mouse button is down over the menu bar. By default, when the mouse is dragged on a window's title bar, the window moves. For this reason, Windows moves the form because it believes the mouse is being dragged from the title bar.

Enhancing a List Box with SendMessage


Extended study of the Windows messaging system

Going into the details of each Windows message is well beyond the scope of this book. If you're interested in pursuing extended study of Windows messages, you should read the documentation in the Software Development Kit on the Microsoft Developers Network Library CD-ROM that accompanies your copy of VB.


As mentioned earlier, the API function SendMessage is very versatile. Windows has hundreds of different messages that you can master to alter your program's or another program's behavior within the Windows environment. To demonstrate the versatility of the SendMessage function, however, another use of the function is provided in the prjAPIStuff project created for this chapter.

When you run the code for the project, notice that a ToolTip window appears as you move your mouse over items in the form's list box (see Figure 23.10).

FIGURE 23.10 You can use the Windows API to help display the contents of a ListBox item in a ToolTip window.

The code uses the LB_ITEMFROMPOINT message to determine which item a mouse pointer is moving over within a list box. After the ListIndex is determined, the text of that item is assigned to the list box's ToolTipText property. For a detailed account of the process, look at the MouseMove event procedure of the list box lstFiles in the project prjAPIStuff.vbp, which you can download from the Web at http://www.mcp.com/info.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.