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
editTo 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
editThere 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
editThere 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
editThere 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 than 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
editAfter 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
editWindows 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
editThe "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
editThere 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
editTo 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
editFinally, 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
editThe 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
editDialog 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
editThere 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.
Menus
editThere 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.
Menu Bar: From Resource Script
editThe 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.
When creating menus, it is polite to create menus with common names, and in a commonly-accepted order, so that computer users know where to find things. |
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.
Menu Bar: From API Calls
editMenu Bar: Loading a Menu
editTo 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.