17. Window Classes and the Window Creation Process

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.)  
   
  0313-01.gif  
   
  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.  



WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

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