Win32 API Programming with Visual Basic

Page 305
  17. Window Classes and the Window Creation Process  
   
  We have seen how the operating system communicates with a window using messages. In this chapter, we take a closer look at the nature of a window itself. We will discuss window classes and how a window is actually created. This is the process that every VC++ programmer must understand. Visual Basic takes care of all of this for the VB programmer. Nevertheless, advanced VB programmers should have some understanding of these issues in order to program the Windows API.  
   
  To get a deeper understanding of what a window is and how it works, it is instructive to follow a brief (that is, as brief as possible) Visual C++ program that creates a window class and a window based on this class. Don't worry about understanding every line of code we are after a general understanding here.  
   
  The basic steps in window creation under VC++ are as follows (although we will change the order a bit):  
   
  Define a window class.  
   
  Register the class.  
   
  Set up the window procedure for the class.  
   
  Create a window based on this class.  
   
  Set up the message loop for the window.  
 
  Window Classes  
   
  Every window is an instance of a window class. In this sense, windows are object-oriented. Creating a window class is not hard. We begin by declaring a variable of type WNDCLASS. This is a structure with the following definition.  

typedef struct WNDCLASS { UNIT style; // window style WNDPROC lpfnWndProc; // window procedure int cbClsExtra; int cbWndExtra; HANDLE hInstance; // instance handle of process HICON hIcon; // icon for window HCURSOR hCursor; // mouse cursor for window HBRUSH hbrBackground; // background color of window LPCTSTR lpszMenuName; // menu for window class LPCTSTR lpszClassName; // class name };

Page 306
   
  This structure defines the properties of the window class, and thus of each window that is based on this class. In particular, the structure has members to set the icon, mouse cursor background color, and menu for the class. The last member is used to give the class a name. In fact, the most important members of this structure are the class name and the window procedure.  
   
  Note that we can change or augment the properties of any given window once it is created. The window class properties act only as a starting point.  
   
  Here is some actual VC++ code:  

WNDCLASS wndclass; // Define window class wndclass.style = CS_HREDRAW CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = NULL; wndclass.cbWndExtra = Null; wndclass.hInstance = hInstance; wndclass.hIcon = NULL; wndclass.hCursor = NULL; wndclass.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = TEXT( rpiClass1 );

   
  The style member is defined as the conjunction (ORing) of two values. (A horizontal bar is the OR operator in VC++.) The CS_HREDRAW style causes Windows to redraw the window if its width is changed, and similarly for the CS_VREDRAW style. Thus, all windows based on this class will have these redrawing properties.  
   
  The next step is to register the class with Windows. This is done using the RegisterClass function, which takes a pointer to the WNDCLASS structure for the class (the & means "address of"):  
 
  // Register class

RegisterClass (&wndclass)

 
Page 307
 
  Predefined Window Classes  
   
  Fortunately, Windows defines several window classes that we can use for creating windows. There are:  
   
  Button  
   
  Combo box  
   
  Edit (Text box)  
   
  Listbox  
   
  MDIclient (an MDI client window)  
   
  RichEdit (Version 1.0)  
   
  RichEdit_Class (Version 2.0)  
   
  Scrollbar  
   
  Static (A label, rectangle, or line used to label, box, or separate other controls. Static controls receive no input and produce no output, which is why they are called static.)  
 
  The Window Procedure of a Window Class  
   
  We have already discussed the fact that messages are processed in the window procedure of a window class. It is worth emphasizing that it is the window class that has a window procedure and not individual windows. Thus, all windows based on a certain class use the same window procedure.  
   
  Here is an example of a window procedure. This example watches for only two types of messages: a WM_DESTROY message is sent to a window when it is being destroyed, and a WM_SIZE message is sent to a window after it has been resized by Windows (probably in response to user action with the mouse).  
 
  // Window procedure for this class

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

   // a temporary string

   char temp[50];

   switch(iMsg)    // like the VB Select Case structure

   {

      // process any resize messages

      case WM_SIZE:

         _itoa( (lParam & 0x0000FFFF) , temp, 10 );

         SetWindowText (hwnd, temp);

         return 0;

 

 

Page 308
 
        // process the destroy message

      case WM_DESTROY:

         // Generate a quit message

         PostQuitMessage(0);

         return 0;

   }

   // Call Windows default window procedure

   return DefWindowProc(hwnd, iMsg, wParam, lParam);

}

 
   
  If a WM_DESTROY message arrives, our window procedure sends a WM_QUIT message to terminate the thread that created the window (and thus the application if it is single-threaded). If a WM_SIZE message arrives, our window procedure grabs the width of the client area of the window (this is the portion of the window that does not include the caption or borders), which is returned in the lower 16 bits of the lParam parameter, and places it in the window's caption just for something to do.  
   
  We should emphasize that programmers seldom call a window procedure directly. The window procedure is a callback function, that is, it is called by Windows in order to pass the message information (window handle, message ID, wParam, and lParam) to the application for processing.  
   
  Finally, note that the final act of our window procedure is to call the default window procedure, which will supply default processing of all messages that are not processed by our window procedure (in this case, all messages except WM_SIZE and WM_DESTROY the VC++ return statement exits the window procedure). Of course, window procedures in most applications will be far more complicated than this one, possibly having code that depends upon the window that is the target of the message (as given by the hwnd parameter). In fact, for many Windows applications, the main action takes place in the window procedure!  
 
  Creating a Window  
   
  Once a window class is defined and registered, we can create a window based on this class. This is done using the CreateWindow function:  

HWND CreateWindow( LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent to owner window HMENU hMenu, // handle to menu of child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data );

Page 309
   
  Notice that the parameters to this function are quite similar to the parameters of the WNDCLASS structure.  
   
  The CreateWindow function requires the class name for the window and sets various properties of the window, such as its initial placement. The window name is used as a caption for those windows that have captions (application windows and command buttons, for instance).  
   
  The CreateWindow function returns a handle to the newly created window. Here is an example:  

HWND CreateWindow( LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent to owner window HMENU hMenu, // handle to menu of child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data );

 
  Window Styles  
   
  As we have seen, every window has a style. This is a combination of the style defined for the window class as well as the setting of the dwStyle parameter in the CreateWindow function. The dwStyle parameter can be a combination of several style constants, both general constants and those that apply specifically to a particular type of window, such as a command button. Here are some examples of general style constants:  
 
  WS_BORDER

Creates a window that has a thin-line border

 
 
  WS_CAPTION

Creates a window that has a titlebar (includes the WS_BORDER style)

 
 
  WS_CHILD

Creates a child window

 
 
  WS_DLGFRAME

Creates a window that has a border of a style typically used with dialog boxes

 
 
  WS_HSCROLL

Creates a window that has a horizontal scrollbar

 
 
  WS_MAXIMIZE

Creates a window that is initially maximized

 
Page 310
 
  WS_MAXIMIZEBOX

Creates a window that has a Maximize button

 
 
  WS_SYSMENU

Creates a window that has a window menu on its titlebar

 
   
  In addition, each type of predefined class has styles. For instance, the majority of button styles are shown here.  
   
  To create a checkbox  
 
  BS_AUTO3STATE

A three-state checkbox that changes its state when the user selects it

 
 
  BS_CHECKBOX

A checkbox with text

 
   
  To create a radio button (option button)  
 
  BS_RADIOBUTTON

A small circle with text

 
 
  BS_AUTORADIOBUTTON

A radio button for which Windows automatically sets the button's state to checked and automatically sets the state for all other buttons in the same group to unchecked

 
   
  To create a command button (push button)  
 
  BS_DEFPUSHBUTTON

A push button that behaves like a BS_PUSHBUTTON-style button, but is also the default button (can be selected by hitting the ENTER key)

 
 
  BS_PUSHBUTTON

A push button

 
   
  To place text  
 
  BS_LEFTTEXT

Places text on the left side of the radio button or checkbox

 
 
  BS_BOTTOM

Places text at the bottom of the button rectangle

 
 
  BS_CENTER

Centers text horizontally in the button rectangle

 
 
  BS_MULTILINE

Wraps button text to multiple lines, if required

 
 
  BS_VCENTER

Places text in the middle (vertically) of the button rectangle

 
Page 311
   
  Other  
 
  BS_GROUPBOX

A rectangle in which other controls can be grouped

 
 
  BS_OWNERDRAW

An owner-drawn button (The programmer is responsible for the control's appearance.)

 
 
  BS_BITMAP

Specifies that the button displays a bitmap

 
 
  BS_ICON

Specifies that the button displays an icon

 
 
  BS_TEXT

Specifies that the button displays text

 
   
  The most important thing to notice about the button styles is that what VB programmers think of as different controls command buttons, checkboxes, and option buttons are in reality just buttons with different styles.  
   
  Changing a Window's Style  
   
  The SetWindowLong function can be used to set the style of a window after the window has been created. As it happens, some styles can be effectively changed after the window has been created and others cannot (the result is either nothing or disaster).  
   
  The only way to see whether changing a window's style will work is to try it. Here are a couple of examples that you might want to try. Just place a command button and two text boxes on a form:  
 
  Dim IStyle As Long

' Command button caption aligned at bottom

lStyle = GetWindowLong (Command1.hwnd, GWL_STYLE)

SetWindowLong Command1.hwnd, GWL_STYLE, lStyle Or BS_BOTTOM

' Textbox converts all input to lower case

lStyle = GetWindowLong(Text1.hwnd, GWL_STYLE)

SetWindowLong Text1.hwnd, GWL_STYLE, lStyle Or ES_LOWERCASE

' Textbox accepts only digits

lStyle = GetWindowLong(Text2.hwnd, GWL_STYLE)

SetWindowLong Text2.hwnd, GWL_STYLE, lStyle Or ES_NUMBER

 
   
  The main point to note about this code is that we must first get the current style so that we can make the necessary changes. It would be a mistake to simply set a window's style to, say, BS_BOTTOM, because this would clear all other style settings.  
Page 312
 
  Windows and VB Controls  
   
  Visual Basic controls are windows. The older controls have class names that begin with the word Thunder, because this was Microsoft's internal code name for Visual Basic 1.0. Note that some design-time controls are different than the corresponding runtime controls and thus may have different class names. Runtime control class names are based on the design-time name, but also include reference to the version of VB. For instance, a design-time listbox has class name ThunderListBox, Whereas a VB5 runtime listbox has class name ThunderRT5ListBox and a VB6 runtime listbox has class name ThunderRT6ListBox. Table 17-1 shows the design-time class names for some common VB controls.  
Table 17-1. Controls and Their Design-time Class Names
Control Class Name
Check ThunderCheckBox
Combo ThunderComboBox
Command ThunderCommandButton
Dir ThunderDirListBox
Drive ThunderDriveListBox
File ThunderFileListBox
Form ThunderForm
Frame ThunderFrame
Label ThunderLabel
List ThunderListBox
MDIForm ThunderMDIForm
Option ThunderOptionButton
Picture ThunderPictureBox
Scroll (Horiz) ThunderHScrollBar
Scroll (Vert) ThunderVScrollBar
Text ThunderTextBox
Timer ThunderTimer
TabStrip TabStript20WndClass
Toolbar msvb_lib_toolbar
ProgressBar ProgressBar20WndClass
StatusBar StatusBat20WndClass
TreeView TreeView20WndClass
ListView ListView20WndClass
ImageList ImageList20WndClass
Slider Slider20WndClass

Page 313
 
  Example: Spying on Windows  
   
  The rpiSpyWin application, whose complete source code is on the CD, is a little utility for getting information about a specific window. Figure 17-1 shows the main (and only) window for this program. In particular, the utility retrieves the window handle, class name, caption, styles, location, window ID, and process and thread IDs of any visible window. (The window identifier, or window ID, is a number that is passed to the CreateWindow function and identifies a child window from among its siblings. This value is occasionally useful in calling API functions, so I have included it here in case you run across it.)  
   
   
   
  Figure 17-1.

The Windows spying utility

 
   
  The utility takes advantage of the system-wide mouse capture that Windows creates when we drag a window with the mouse. So to use rpiSpyWin, just hold down the left mouse button over the red box and move the mouse. When you have found the window of interest, just release the mouse button. Incidentally, if rpiWinSpy leaves some residual lines on the screen, just move the mouse pointer over the red box again.  
   
  Almost all of the program's action takes place in the MouseMove event for the red picture box:  
 
  Private Sub picSpy_MouseMove(Button As Integer, Shift As Integer, _

x As Single, y As Single)

Dim xValue As Long, yValue As Long

Dim pt As POINTAPI

' Convert X and Y to screen coordinates (pixels)

' X and Y are twips with respect to the upper left corner of drag window

' Top and Left are in twips with respect to client area of form

xValue = (x + picSpy.Left) \ Screen.TwipsPerPixelX

yValue = (y + picSpy.Top) \ Screen.TwipsPerPixelY

pt.x = xValue

pt.y = yValue

ClientToScreen Me.hwnd, pt

 

 

Page 314
 
  txtX = "X = " & pt.x & " Y = " & pt.y

'txtY = pt.y

' Get window handle from mouse location

hCurrent = WindowFromPoint (pt.x, pt.y)

If hCurrent <> hPrevious Then

   ' Change of window

   txthWnd = "&H" & Hex$ (hCurrent) & " (" & hCurrent & ")"

   ' Get class name

   txtClass = GetClass(hCurrent)

   ' Get caption

   txtCaption = GetCaption(hCurrent)

   ' Get style

   txtStyle = "&H" & Hex$ (GetWindowLong(hCurrent, GWL_STYLE))

   ' Get extended style

   txtEXStyle = "&H" Hex$ (GetWindowLong(hCurrent, GWL_EXSTYLE))

   ' Get window ID

   txtID = GetWindowLong(hCurrent, GWL_ID)

   ' Get rectangle

   lretSpy = GetWindowRect(hCurrent, rectCurrent)

   ' Invert the borders of previous rectangle

   ' Top line

   rectTemp = rectPrev

   rectTemp.Bottom = rectPrev.Top + PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Bottom line

   rectTemp = rectPrev

   rectTemp.Top = rectPrev.Bottom - PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Left side

   rectTemp = rectPrev

   rectTemp.Right = rectPrev.Left + PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   '  Right side

   rectTemp = rectPrev

   rectTemp.Left = rectPrev.Right - PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Invert the borders of new rectangle

   ' Top line

   rectTemp = rectCurrent

   rectTemp.Bottom = rectCurrent.Top + PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Bottom line

   rectTemp = rectCurrent

   rectTemp.Top = rectCurrent.Bottom - PEN_WIDTH

   InvertRect hDCScreen, rectTemp

 

 

Page 315
 
     ' Left side

   rectTemp = rectCurrent

   rectTemp.Right = rectCurrent.Left + PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Right side

   rectTemp = rectCurrent

   rectTemp.Left = rectCurrent.Right - PEN_WIDTH

   InvertRect hDCScreen, rectTemp

   ' Update previous

   hPrevious = hCurrent

   rectPrev = rectCurrent

End If

End Sub

 
   
  The first thing that we must do is translate the incoming mouse coordinates, which are relative to the upper-left corner of the picture box, to values that are relative to the upper-left corner of the main form's client area. Note that twips must also be translated to pixels. (Most API functions use pixels.) Then we must translate from client coordinates to screen coordinates.  
   
  The screen coordinates in pixels can be used to get the handle of the window that is under the mouse pointer, using the WindowFromPoint function. If this handle has changed since the last mouse move, the window's class name, caption, and other data are obtained and placed in the text boxes. The GetClass function is basically just a wrapper for the API function GetClassName:  
 
  Function GetClass (lhwnd As Long) As String

' Return class name of lhwnd window

Dim lret As Long

Dim sClassName As String

GetClass = "Cannot get class name."

sClassName = String$ (256, 0)

lret = GetClassName(lhwnd, sClassName, 257)

If lret = 0 Then

   Exit Function

Else

   GetClass = Left$ (sClassName, lret)

End If

End Function

 
   
  The GetCaption function uses the API functions GetWindowTextLength and GetWindowText:  
 
  Function GetCaption(lhwnd As Long) As String

Dim sText As String

 

 

Page 316
 
  Dim lCaption As Long

Dim hnd As Long

lCaption = GetWindowTextLength(lhwnd)

' Allocate string buffer

sText = String$ (lCaption + 2, 0)

lCaption = GetWindowText(lhwnd, sText, lCaption + 1)  ' include NULL

GetCaption = Left$ (sText, lCaption)

End Function

 
   
  To get the styles, we just use the GetWindowLong function.  
   
  The GetWindowRect function retrieves the dimensions of a window. In particular, it fills a rect structure:  
 
  Type rect

    Left As Long

    Top As Long

    Right As Long

    Bottom As Long

End Type

 
   
  The most interesting part of the application is the use of the API function InvertRect which inverts the pixels in a given rectangle by performing a logical negation on each pixel. Thus, a second inversion returns the pixels to their original value. When the window handle changes, the procedure returns the previous rectangle to normal and inverts a new rectangle that surrounds the new window. Note that InvertRect inverts all of the pixels in the interior of the rectangle, so we had to make up four small rectangles to represent the sides of the window under the mouse.  
   
  Incidentally, I got the idea for using InvertRect by using the rpiPEInfo utility to spy on the imported functions for a commercial screen-capturing utility that does essentially the same thing. This is a good way to get ideas.  
   
  One more item is worth mentioning. The rpiWinSpy window is given the topmost property using the SetWindowPos API function. This means that the window will remain above all other windows (except other topmost windows), even when the window loses its focus.  

Категории