Introduction to Windows Programming
2 Introduction to Windows Programming
Windows are the most fundamental element of a Microsoft Windows application. All applications have a main “parent” window that can have a menu and icon associated with it. Windows are actually used in almost every aspect of an applications user interface, ranging from the parent window to the individual buttons and edit controls on dialog boxes. Dialog boxes themselves are just another type of window.
To create a window there is only one function, the CreateWindow() function. The CreateWindow() function is the most important and complex function in the Windows API. In Windows API’s term each window controls are also termed as windows. Controls are the child windows. There are many types of predefined windows that can easily be created. These include buttons, static controls, list boxes, combo boxes, edit controls, scrollbars etc. However there is no predefined window available for the first window that an application needs to create. The first window is the main window of the application
2.1 A First Windows Program
Before coming to the actual procedure for creating windows application using Win32 API lets first see the whole program’s source code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <windows.h>
LPCTSTR lpszAppName = TEXT("WINAPP");
LPCTSTR lpszTitle = TEXT("First Windows Application");
HINSTANCE hInst; // current instance of the running program
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
//Windows Entry Point
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wcex;
//The Initialization portion of the main window
//before initialization we fill the window class structure
//to describe the main window
wcex.cbSize = sizeof( WNDCLASSEX );
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = lpszAppName;
wcex.hIconSm = NULL;
//Register the Window Class
if (!RegisterClassEx(&wcex))
return FALSE;
//Save the instance handle of the application for future use
hInst = hInstance;
//Create the main window for the applicaiton
hWnd = CreateWindow(lpszAppName, //Window class name
lpszTitle, //Window caption on title bar
WS_OVERLAPPEDWINDOW, //Window Style
CW_USEDEFAULT, 0, //Initial x,y position
CW_USEDEFAULT, 0, //Initial width,height
NULL, //Parent window handle
NULL, //Window menu handle
hInstance, //This instance own’s this window
NULL //Pointer to window creation data
);
if (!hWnd)
return FALSE;
//Show the window
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
//The message loop
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//The program termination
return msg.wParam;
}
//WindowProc for the windows message handling
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
//Select the message and handle it
switch(uMsg)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd,&rect);
DrawText(hDC,TEXT("First Windows Application"),-1,&rect,
DT_SINGLELINE | DT_VCENTER | DT_CENTER);
EndPaint (hWnd, &ps);
break;
//We don't need to process WM_CLOSE when nothing is done
//except calling DestroyWindow() function
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY :
PostQuitMessage(0);
break;
default :
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
}
return 0L;
}
This program creates a normal application window and displays “First Windows Application”. Don’t be worried, we will break this program into parts and explain each part in following portion of this note.
A Windows application requires a main window to process messages and user input. This window is also the parent for all top-level windows created in the application. Every windows application should have a function with the name WinMain in it. It is the entry point to our application as we view from the code level. WinMain is like main in standard C program. WinMain is where the main window is created and the message processing loop resides. To create a window a “window class” should be registered first. Registering is done by RegisterClass (or RegisterClassEx) function. After the new class is registered, the main window can be created. The WinMain is defined as follows.
int APIENTRY
WinMain (HINSTANCE hInstance, //Current instance
HINSTANCE hPrevInstance, //Previous instance (unused)
LPSTR lpszCmdLine, //Pointer to command line
int nCmdShow) //Show window parameter
{
/* Initialization, message loop and termination. */
}
Straight away we see some of those Windows types. APIENTRY resolves to WINAPI, which then resolves to __stdcall which is used for all windows API calls. HINSTANCE is a new type, its a handle to an instance. LPSTR is Microsoft's way of saying 'long pointer to a string' it normally resolves to char*. A description of each parameter is shown below:
- HINSTANCE hInstance - when the program runs it is considered one instance of the executable running. We could in fact run many, creating multiple instances, and this value would be different for each e.g. we could open ten notepads. Since the instance handle uniquely identifies the application it is used in Windows API calls elsewhere, so it is common to make a global copy of this.
- hPrevInstance is always NULL in modern version of Windows so we can ignore it.
- LPSTR lpszCmdLine - when we run a program we can start it with a set of commands. If you look at shortcuts in Windows for programs there is a space where you can put a command line. When running programs from the command line the command line arguments except the program name will be passed on this argument.
- int nCmdShow - This is a flag describing how the window should be shown to start with. Examples include SW_SHOWMAXIMIZED and SW_SHOWMINIMIZED.
WinMain function performs three critical actions
- It performs all necessary initialization. For example loading resource used by the program, registering window classes and creating windows.
- It executes message loop fetching messages for the Application and dispatching to the appropriate message handling procedure.
- It terminates the application when the message loop detects a WM_QUIT message after freeing any resources possibly reserved by the initialization code.
2.2 Creating the Main Window
To create a main window first the window is to be registered with the Windows operationg system by calling RegisterClassEx(). After registering the window with the Windows the window is created by calling CreateWindow() function with the class name assigned when registering windows class.
2.2.1 Registering the Window Class
A Window Class stores information about a type of window, including it’s Window Procedure which controls the window, the small and large icons for the window, and the background color. This way, we can register a class once, and create as many windows as we want from it, without having to specify all those attributes over and over. Most of the attributes that are set in the window class can be changed on a per-window basis if desired.
A Window Class has NOTHING to do with C++ classes.
To for the class name the above program uses a variable lpszAppName is used. This variable is declared on line 3 of the above program.
LPCTSTR lpszAppName = TEXT("WINAPP");
The variable above stores the name of our window class, we will use it shortly to register our window class with the system.
In order to register a class you call the API function:
ATOM RegisterClassEx(CONST WNDCLASSEX *wcex);
This function takes a pointer to a structure WNDCLASSEX describe your window class. The return value ATOM is one of those Microsoft typedefs again and resolves to unsigned short. This function returns 0 if there was an error. The const indicates that the function will not change the structure.
So we need to fill a WNDCLASSEX structure with a description of how we want our window. Line no 21 to 32 in the above program fills this structure. Line no 35 registers the window class. The snippet of the code to fill the WNDCLASSEX structure and registering the window class is shown below.
wcex.cbSize = sizeof( WNDCLASSEX );
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = lpszAppName;
wcex.hIconSm = NULL;
//Register the Window Class
if (!RegisterClassEx(&wcex))
return FALSE;
This is the code we use in WinMain()
to register our window class. We fill out the members of a WNDCLASSEX
structure and call RegisterClassEx()
.
The WNDCLASSEX structure is defined as
typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
cbSize
This must always be set to sizeof(WNDCLASSEX).
style
This specifies the class styles. Take a look at your SDK documentation for the values this member can take.
lpfnWndProc
Pointer to the WndProc which will handle this windows' messages.
cbClsExtra
Number of extra bytes to allocate at the end of the WNDCLASSEX structure.
cbWndExtra
Number of extra bytes to allocate at the end of the window instance.
hInstance
Identifies the instance that the window procedure of this class is within.
hIcon
Handle to the icon associated with windows of this class.
hCursor
Handle to the cursor for windows of this class.
hbrBackground
Identifies the class background brush.
lpszMenuName
Identifies the menu for windows of this class.
lpszClassName
Pointer to a NULL terminated string or an atom specifying the class of this structure.
hIconSm
Handle to the small icon associated with this class.
We then call RegisterClassEx()
and check for failure, if it fails it our program returns FALSE from WinMain() and the program is terminated.
2.2.2 Creating the Window
Now that we have registered our window class with Windows so the main window can be created. When creating the window we can apply additional styles that affect the way the window is shown. We decide if we want borders, menus, system menu, close box, resize bar etc. The choice is up to us. After successfully creating a window the API returns a handle to that window. The handle is a unique identifier that we can use in later calls to API functions that apply to this instance of our window class. Earlier we saw that HINSTANCE was a handle to the application instance. So, HWND is a handle to a window instance. The main window is created with the CreateWindow() API function. The CreateWindow API function has following prototype.
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName,DWORD style,int x, int y, int width, int height, HWND hWndParent,HMENU hMenu,HANDLE hInstance,LPVOID lpParam);
This function is used on line no 42 to line no 51. The snippet of the code is as follows.
hWnd = CreateWindow(lpszAppName, //Window class name
lpszTitle, //Window caption on title bar
WS_OVERLAPPEDWINDOW, //Window Style
CW_USEDEFAULT, 0, //Initial x,y position
CW_USEDEFAULT, 0, //Initial width,height
NULL, //Parent window handle
NULL, //Window menu handle
hInstance, //This instance own’s this window
NULL //Pointer to window creation data
);
if (!hWnd)
return FALSE;
The first parameter is the class name (lpszAppName
), this tells the system what kind of window to create. Since we want to create a window from the class we just registered, we use the name of that class. After that we specify our window caption for the title bar, which is the text that will be displayed in the Caption, or Title Bar on our window.
The parameter we have as WS_OVERLAPPEDWINDOW
is the Window Style parameter. There are other windows styles such as WS_BORDER, WS_VISIBLE, WS_CAPTION, WS_CHILD etc. These will be covered more later.
The next four parameters (CW_USEDEFAULT, 0, CW_USEDEFAULT, 0
) are the x and y co-ordinates for the top left corner of your window, and the width and height of the window. Here x is set to CW_USEDEFAULT and y is set to 0. If x is set to CW_USEDEFAULT, the system selects the default position for the window’s upper-left corner and ignores the y parameter. Remeber at the left of the screen x has a value of zero and it increases to the right; at the top of the screen y has value of zero and increases towards the bottom. The units are pixels, which is the smallest unit a screen can display at a given resolution. Here width is set to CW_USEDEFAULT and height is set to 0. If width is specified as CW_USEDEFAULT system ignores the height.
Next (NULL, NULL, g_hInst, NULL
) we have the Parent Window handle, the menu handle, the application instance handle, and a pointer to window creation data. In windows, the windows on your screen are arranged in a heirarchy of parent and child windows. When you see a button on a window, the button is the Child and it is contained within the window that is it's Parent. In this example, the parent handle is NULL
because we have no parent, this is our main or Top Level window. The menu is NULL
for now since we don't have one yet. The instance handle is set to the value that is passed in as the first parameter to WinMain()
. The creation data (which is almost never use) that can be used to send additional data to the window that is being created is also NULL
. NULL
is simply defined as 0
(zero). Actually, in C it's defined as ((void*)0)
, since it's intended for use with pointers. Therefore we may get warnings if NULL is used for integer values, depending on the compiler and the warning level settings. We can choose to ignore the warnings, or just use 0
instead.
If CreateWindow() is successful it returns handle to the window and if it fails to create window it returns NULL. We have to check whether the window is created or not, if not we have to terminate the application by returning FALSE from the WinMain() function. This is done on line no 53,54 or can be seen on the above snippet of the code for CreateWindow().
After we've created the window and checked to make sure we have a valid handle we show the window, using the last parameter in WinMain()
and then update it to ensure that it has properly redrawn itself on the screen. This is done on line no 57,58 on the programs code.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
The nCmdShow
parameter is optional, we could simply pass in SW_SHOWNORMAL
all the time and be done with it. However using the parameter passed into WinMain()
gives whoever is running this program to specify whether or not they want this window to start off visible, maximized, minimized, etc... This is options are found on the properties of windows shortcuts, and this parameter is how the choice is carried out.
Every time whenever any function fails it is better to inform the user about the error. In our program we have just returned FALSE value whenever error occurs. We haven’t informed the user about the error. We can use the MessageBox() function to display the error message when error occurs. We can use MessageBox() as follows.
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
2.2.3 The Message Loop
This is the heart of the whole program, pretty much everything that your program does pass through this point of control. This can be seen on line no 60 to 68 in the program above. That part is shown on the following part of the program.
//The message loop
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//The program termination
return msg.wParam;
GetMessage()
gets a message from the application's message queue. Any time the user moves the mouse, types on the keyboard, clicks on your window's menu, or does any number of other things, messages are generated by the system and entered into your program's message queue. By calling GetMessage()
we are requesting the next available message to be removed from the queue and returned to the program for processing. If there is no message, GetMessage()
Blocks, that is, it waits untill there is a message, and then returns it to the program.
TranslateMessage()
does some additional processing on keyboard events like generating WM_CHAR
messages to go along with WM_KEYDOWN
messages. Finally DispatchMessage()
sends the message out to the window that the message was sent to. This could be our main window or it could be another one, or a control, and in some cases a window that was created behind the scenes by the sytem or another program. We don’t need to worry about this because all we are concerned with is that we get the message and send it out, the system takes care of the rest making sure it gets to the proper window.
2.3 The Window Procedure
If the message loop is the heart of the program, the window procedure is the brain. This is where all the messages that are sent to our window get processed. Line no 72 to line no 105 on the program shows the windows procedure that will handle the messages.
//WindowProc for the windows message handling
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
//Select the message and handle it
switch(uMsg)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd,&rect);
DrawText(hDC,TEXT("First Windows Application"),-1,&rect,
DT_SINGLELINE | DT_VCENTER | DT_CENTER);
EndPaint (hWnd, &ps);
break;
//We don't need to process WM_CLOSE when nothing is done
//except calling DestroyWindow() function
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY :
PostQuitMessage(0);
break;
default :
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
}
return 0L;
}
The window procedure is called for each message, the HWND
parameter is the handle of your window, the one that the message applies to. This is important since you might have two or more windows of the same class and they will use the same window procedure (WndProc()
). The difference is that the parameter hWnd
will be different depending on which window it is. For example when we get the WM_CLOSE
message we destroy the window. Since we use the window handle that we received as the first paramter, any other windows will not be affected, only the one that the message was intended for.
The WM_PAINT message is sent every time when some portion of the window is to be redrawn. Out program will print “First Windows Application” when every portion of the window is to be redrawn causing the text to be displayed even if the text unhidden from the hidden state.
WM_CLOSE
is sent when the user presses the Close Button or types Alt-F4. WM_CLOSE message is sent to indicate that the application is to be closed. An application can prompt the user for confirmation, prior to destroying a window, by processing the WM_CLOSE message and calling the DestroyWindow() function only if the user confirms the choice. If unprocessed default action will cause the window to be destroyed by default, but it can be handled explicitly, since this is the perfect spot to do cleanup checks, or ask the user to save files etc. before exiting the program. If unprocessed the DefWindowProc() function will destroy the window by sending the WM_DESTRY message.
When we call DestroyWindow()
the system sends the WM_DESTROY
message to the window getting destroyed, in this case it’s our window, and then destroys any remaining child windows before finally removing our window from the system. Since this is the only window in our program, we are all done and we want the program to exit, so we call PostQuitMessage()
. This posts the WM_QUIT
message to the message loop. We never receive this message, because it causes GetMessage()
to return FALSE
, and as you'll see in our message loop code, when that happens we stop processing messages and return the final result code, the wParam
of WM_QUIT
which happens to be the value we passed into PostQuitMessage()
. The return value is only really useful if your program is designed to be called by another program and you want to return a specific value.
The DefWindowProc function calls the default window procedure to provide default processing for any window messages that an application does not process. This function ensures that every message is processed.