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.