Windows Programming/Window Creation

On the Windows operating system, most user-interfacable objects are known as "windows". Each window is associated with a particular class, and once the class is registered with the system, windows of that class can be created.

WNDCLASS

edit

To register a windows class, you need to fill out the data fields in a WNDCLASS structure, and you need to pass this structure to the system. First, however, you need to provide your class with a name, so that Windows (the system) can identify it. It is customary to define the window class name as a global variable:

 LPTSTR szClassName = TEXT("My Class");

You can name it anything you want to name it, this is just an example.

After you have the class name, you can start filling out the WNDCLASS structure. WNDCLASS is defined as such:

typedef struct {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;

For more information on this structure, see this Microsoft Developer's Network article.

Notice the last data field is a pointer to a string named "lpszClassName"? This is where you point to your class name that you've just defined. The field named "hInstance" is where you supply the instance handle for your program. We will break the rest of the fields up into a few different categories.

The HANDLEs

edit

There are a number of different data types in the WNDCLASS structure that begin with the letter "h". As we remember from our discussion of Hungarian notation, if a variable starts with an "h", the variable itself holds a HANDLE object.

HICON hIcon
This is a handle to the icon that your program will use, as located in the top left, and in the taskbar. We will discuss icons more later. However, in our example below, we will use a default value for this item.
HCURSOR hCursor
This is a handle to the standard mouse pointer that your window will use. In our example, we will use a default value for this also.
HBRUSH hbrBackground
This is a handle to a brush (a brush is essentially a color) for the background of your window. Here is a list of the default colors supplied by Windows (these colors will change depending on what 'theme' is active on your computer):
COLOR_ACTIVEBORDER
COLOR_ACTIVECAPTION
COLOR_APPWORKSPACE
COLOR_BACKGROUND
COLOR_BTNFACE
COLOR_BTNSHADOW
COLOR_BTNTEXT
COLOR_CAPTIONTEXT
COLOR_GRAYTEXT
COLOR_HIGHLIGHT
COLOR_HIGHLIGHTTEXT
COLOR_INACTIVEBORDER
COLOR_INACTIVECAPTION
COLOR_MENU
COLOR_MENUTEXT
COLOR_SCROLLBAR
COLOR_WINDOW
COLOR_WINDOWFRAME
COLOR_WINDOWTEXT 

Because of a software issue, a value of 1 must be added to any of these values to make them a valid brush.

Another value that is worth mentioning in here is the "lpszMenuName" variable. lpszMenuName points to a string that holds the name of the program menu bar. If your program does not have a menu, you may set this to NULL.

Extra Fields

edit

There are 2 "extra" data members in the WNDCLASS structure that allow the programmer to specify how much additional space (in bytes) to allocate to the class (cbClsExtra) and to allocate to each specific window instance (cbWndExtra). In case you are wondering, the prefix "cb" stands for "count of bytes".

int cbClsExtra;
int cbWndExtra;

If you don't know how to use these members, or if you don't want to use them, you may leave both of these as 0. We will discuss these members in more detail later.

Window Fields

edit

There are 2 fields in the WNDCLASS that deal specifically with how the window will operate. The first is the "style" field, which is essentially a set of bitflags that will determine some actions that the system can take on the class. These flags can be bit-wise OR'd (using the | operator) to combine more then one into the style field. The MSDN WNDCLASS documentation has more information.

The next (and arguably most important) member of the WNDCLASS is the lpfnWndProc member. This member points to a WNDPROC function that will control the window, and will handle all of the window's messages.

Registering the WNDCLASS

edit

After the fields of the WNDCLASS structure have been initialized, you need to register your class with the system. This can be done by passing a pointer to the WNDCLASS structure to the RegisterClass function. If the RegisterClass function returns a zero value, the registration has failed, and your system has failed to register a new window class.

Creating Windows

edit

Windows are generally created using the "CreateWindow" function, although there are a few other functions that are useful as well. Once a WNDCLASS has been registered, you can tell the system to make a window from that class by passing the class name (remember that global string we defined?) to the CreateWindow function.

HWND CreateWindow(          
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam 
);

(See this MSDN article for more information.)

The first parameter, "lpClassName" is the string associated with our window class. The "lpWindowName" parameter is the title that will be displayed in the titlebar of our window (if the window has a titlebar).

"dwStyle" is a field that contains a number of bit-wise OR'd flags, that will control window creation.

Window Dimensions

edit

The "x" and "y" parameters specify the coordinates of the upper-left corner of your window, on the screen. If x and y are both zero, the window will appear in the upper-left corner of your screen. "nWidth" and "nHeight" specify the width and height of your window, in pixels, respectively.

The HANDLEs

edit

There are 3 HANDLE values that need to be passed to CreateWindow: hWndParent, hMenu, and hInstance. hwndParent is a handle to the parent window. If your window doesn't have a parent, or if you don't want your windows to be related to each other, you can set this to NULL. hMenu is a handle to a menu, and hInstance is a handle to your programs instance value.

Passing Values to the New Window

edit

To pass a value to the new window, you may pass a generic, LPVOID pointer (a 32-bit value) in the lpParam value of CreateWindow. Generally, it is a better idea to pass parameters via this method than to make all your variables global. If you have more than 1 parameter to pass to the new window, you should put all of your values into a struct, and pass a pointer to that struct to the window. We will discuss this in more detail later.

An Example

edit

Finally, we are going to display a simple example of this process. This program will display a simple window on the screen, but the window won't do anything. This program is a bare-bones program, and it encompasses most of the framework necessary to make any Windows program do anything. Beyond this, it is easy to add more functionality to a program.

#include <windows.h>
 
LPSTR szClassName = "MyClass";
HINSTANCE hInstance;
LRESULT CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
 
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow)
{
    WNDCLASS wnd;
    MSG msg;
    HWND hwnd;
 
    hInstance = hInst;
         
    wnd.style = CS_HREDRAW | CS_VREDRAW; //we will explain this later
    wnd.lpfnWndProc = MyWndProc;
    wnd.cbClsExtra = 0;
    wnd.cbWndExtra = 0;
    wnd.hInstance = hInstance;
    wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); //default icon
    wnd.hCursor = LoadCursor(NULL, IDC_ARROW);   //default arrow mouse cursor
    wnd.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1);
    wnd.lpszMenuName = NULL;                     //no menu
    wnd.lpszClassName = szClassName;
 
    if(!RegisterClass(&wnd))                     //register the WNDCLASS
    {
        MessageBox(NULL, "This Program Requires Windows NT", 
                         "Error", MB_OK);
        return 0;
    }
 
    hwnd = CreateWindow(szClassName,
                        "Window Title",
                        WS_OVERLAPPEDWINDOW, //basic window style
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,       //set starting point to default value
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,       //set all the dimensions to default value
                        NULL,                //no parent window
                        NULL,                //no menu
                        hInstance,
                        NULL);               //no parameters to pass
    ShowWindow(hwnd, iCmdShow);              //display the window on the screen
    UpdateWindow(hwnd);             //make sure the window is updated correctly
 
    while(GetMessage(&msg, NULL, 0, 0))      //message loop
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
 
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

-EX members

edit

The Win32 API gains more functionality with each generation, although Microsoft faithfully maintains the API to be almost completely backwards-compatible with older versions of windows. To add more functionally, therefore, Microsoft needed to add new functions and new structures, to make use of new features. An extended version of the WNDCLASS structure is known as the "WNDCLASSEX" structure, which has more fields, and allows for more options. To register a WNDCLASSEX structure, you must use the RegisterClassEx function instead.

Also, there is a version of the CreateWindow function with extended functionality: CreateWindowEx. To learn more about these extensions, you can do a search on MSDN.

Dialog Boxes

edit

Dialog Boxes are special types of windows that get created and managed differently from other windows. To create a dialog box, we will use the CreateDialog, DialogBox, or DialogBoxParam functions. We will discuss these all later. It is possible to create a dialog box by defining a WNDCLASS and calling CreateWindow, but Windows already has all the definitions stored internally, and provides a number of easy tools to work with. For the full discussion, see: Dialog Boxes.

Default Window Classes

edit

There are a number of window classes that are already defined and stored in the Windows system. These classes include things like buttons and edit boxes, that would take far too much work to define manually. Here is a list of some of the pre-made window types:

BUTTON
A BUTTON window can encompass everything from a push button to a check box and a radio button. The "title" of a button window is the text that is displayed on a button.
SCROLLBAR
SCROLLBAR windows are slider controls that are frequently used on the edge of a larger window to control scrolling. SCROLLBAR types can also be used as slider controls.
MDICLIENT
This client type enables Multiple Document Interface (MDI) applications. We will discuss MDI applications in a later chapter.
STATIC
STATIC windows are simple text displays. STATIC windows rarely accept user input. However, a STATIC window can be modified to look like a hyperlink, if necessary.
LISTBOX, COMBOBOX
LISTBOX windows are drop-down list boxes, that can be populated with a number of different choices that the user can select. A COMBOBOX window is like a LISTBOX, but it can contain complex items.
EDIT, RichEdit
EDIT windows allow text input with a cursor. Basic EDIT windows also allow for copy+paste operations, although you need to supply the code to handle those options yourself. RichEdit controls allow for text editing and formatting. Consider an EDIT control being in Notepad.exe, and a RichEdit control being in WordPad.exe.
edit

There are a number of different menus that can be included in a window or a dialog box. One of the most common (and most important) is the drop-down menu bar that is displayed across the top of a window of a dialog box. Also, many programs offer menus that appear when the mouse is right-clicked on the window. The bar across the top of the window is known as the "Menu Bar", and we will discuss that first. For some information about creating a menu in a resource script, see The Resource Script Reference Page, in the appendix to this book.

edit

The easiest and most straightforward method to create a menu is in a resource script. Let's say that we want to make a menu with some common headings in it: "File", "Edit", "View", and "Help". These are common menu items that most programs have, and that most users are familiar with.

We create an item in our resource script to define these menu items. We will denote our resource through a numerical identifier, "IDM_MY_MENU":


IDM_MY_MENU MENU DISCARDABLE
BEGIN
   POPUP "File"
   POPUP "Edit"
   POPUP "View"
   POPUP "Help"
 END

The keyword POPUP denotes a menu that opens when you click on it. However, let's say that we don't want the "Help" menu item to pop up, but instead we want to click on the word "Help", and immediately open the help window. We can change it as such:

IDM_MY_MENU MENU DISCARDABLE
BEGIN
   POPUP "File"
   POPUP "Edit"
   POPUP "View"
   MENUITEM "Help"
 END

The MENUITEM designator shows that when we click on "Help", another menu won't open, and a command will be sent to the program.

Now, we don't want to have empty menus, so we will fill in some common commands in the "File" and "Edit" menus, using the same MENUITEM keyword as we used above:

IDM_MY_MENU MENU DISCARDABLE
BEGIN
   POPUP "File"
   BEGIN
       MENUITEM "Open"
       MENUITEM "Save"
       MENUITEM "Close"
   END
   POPUP "Edit"
   BEGIN
       MENUITEM "Cut"
       MENUITEM "Copy"
       MENUITEM "Paste"
   END
   POPUP "View"
   MENUITEM "Help"
 END

Now, in the "View" category, we want to have yet another popup menu, that says "Toolbars". When we put the mouse on the "Toolbars" command, a submenu will open to the right, with all our selections on it:

IDM_MY_MENU MENU DISCARDABLE
BEGIN
   POPUP "File"
   BEGIN
       MENUITEM "Open"
       MENUITEM "Save"
       MENUITEM "Close"
   END
   POPUP "Edit"
   BEGIN
       MENUITEM "Cut"
       MENUITEM "Copy"
       MENUITEM "Paste"
   END
   POPUP "View"
   BEGIN
       POPUP "Toolbars"
       BEGIN
           MENUITEM "Standard"
           MENUITEM "Custom"
       END
   END
   MENUITEM "Help"
 END

This is reasonably easy, to start with, except that now we need to provide a method for interfacing our menu with our program. To do this, we must assign every MENUITEM with a command identifier, that we can define in a headerfile. It is customary to name these command resources with an "IDC_" prefix, followed by a short text saying what it is. For instance, for the "File > Open" command, we will use an id called "IDC_FILE_OPEN". We will define all these ID tags in a resource header script later. Here is our menu with all the ID's in place:

IDM_MY_MENU MENU DISCARDABLE
BEGIN
   POPUP "File"
   BEGIN
       MENUITEM "Open", IDC_FILE_OPEN
       MENUITEM "Save", IDC_FILE_SAVE
       MENUITEM "Close", IDC_FILE_CLOSE
   END
   POPUP "Edit"
   BEGIN
       MENUITEM "Cut", IDC_EDIT_CUT
       MENUITEM "Copy", IDC_EDIT_COPY
       MENUITEM "Paste", IDC_EDIT_PASTE
   END
   POPUP "View"
   BEGIN
       POPUP "Toolbars"
       BEGIN
           MENUITEM "Standard", IDC_VIEW_STANDARD
           MENUITEM "Custom", IDC_VIEW_CUSTOM
       END
   END
   MENUITEM "Help", IDC_HELP
 END

When we click on one of these entries in our window, the message loop will receive a WM_COMMAND message, with the identifier in the WPARAM parameter.

We will define all our identifiers in a header file to be numerical values in an arbitrary range that does not overlap with the command identifiers of our other input sources (accelerator tables, push-buttons, etc):

 //resource.h
 #define IDC_FILE_OPEN       200
 #define IDC_FILE_SAVE       201
 #define IDC_FILE_CLOSE      202
 #define IDC_EDIT_COPY       203
 #define IDC_EDIT_CUT        204
 #define IDC_EDIT_PASTE      205
 #define IDC_VIEW_STANDARD   206
 #define IDC_VIEW_CUSTOM     207
 #define IDC_HELP            208

And we will then include this resource header both into our main program code file, and our resource script. When we want to load a menu into our program, we need to create a handle to a menu, or an HMENU. HMENU data items are identical in size and shape to other handle types, except they are used specifically for pointing to menus.

When we start our program, usually in the WinMain function, we will obtain a handle to this menu using an HMENU data item, with the LoadMenu function:

 HMENU hmenu;
 hmenu = LoadMenu(hInst, MAKEINTRESOURCE(IDM_MY_MENU));

We will discuss how to use this handle to make the menu appear in another section, below.

edit
edit

To associate a menu with a window class, we need to include the name of the menu into the WNDCLASS structure. Remember the WNDCLASS structure:

 typedef struct {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
 } WNDCLASS, *PWNDCLASS;

It has a data field called "lpszMenuName". This is where we will include the ID of our menu:

 WNDCLASS wnd;
 wnd.lpszMenuName = MAKEINTRESOURCE(IDM_MY_MENU);

Remember, we need to use the MAKEINTRESOURCE keyword to convert the numerical identifier (IDM_MY_MENU) into an appropriate string pointer.

Next, after we have associated the menu with the window class, we need to obtain our handle to the menu:

 HMENU hmenu;
 hmenu = LoadMenu(hInst, MAKEINTRESOURCE(IDM_MY_MENU));

And once we have the HMENU handle to the menu, we can supply it to our CreateWindow function, so that the menu is created when the window is created:

 HWND CreateWindow(          
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hmenu,
    HINSTANCE hInstance,
    LPVOID lpParam 
 );

We pass our HMENU handle to the hMenu parameter of the CreateWindow function call. Here is a simple example:

 HWND hwnd;
 hwnd = CreateWindow(szClassName, "Menu Test Window!",
                     WS_OVERLAPPEDWINDOW,
                     CW_USEDEFAULT, CW_USEDEFAULT, 
                     CW_USEDEFAULT, CW_USEDEFAULT,
                     NULL,
                     hmenu, 
                     hInstance,
                     0);

As a quick refresher, notice that we are using default values for all the position and size attributes. We are defining the new window to be a WS_OVERLAPPEDWINDOW, which is a common, ordinary window type. Also the title bar of the window will say "Menu Test Window!". We also need to pass in the HINSTANCE parameter as well, which is the second-to-last parameter.

Right-Click Menus

edit

Next Chapter

edit