written by Cali
In this first tutorial I'd like to cover just the basics of Win32 programming. By the end of this document, you should be able to create the basic shell of a Win32 program and understand the how to use the necessary functions to create and manipulate your `Window'.
I'll introduce you to the code you'll need to use and try to explain it as best I can. There is a lot of information contained in this document and for some it will prove to be heavy going. I hope you persevere, as once you know the basics, a whole new world of code will open up for you. =) In writing this, I have assumed that the reader has some C/C++ knowledge.
What is Win32?
Win32 is the name given to an API that is common to all 32-bit Windows based platforms. These platforms include Win 95/98, Win NT and Win CE. This API contains functions and data types that allow you to access the functionality built into these platforms.
In today's `Visual' languages a lot of the inner workings of the application are hidden from the developer. With large-scale development and the requirements for Rapid Application Development (RAD), these visual languages can be more cost-effective and efficient for development purposes. However, this document is not about hiding the `ugly' code from the developers. So, lets get into it.
WinMain
Listing 1 : WinMain() example
#include <windows.h>
#include <windowsx.h>
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR szCmdLine,
int nCmdShow)
{
}
If you look at listing 1 you will see that there is no main() function in a Win32 application. The program's main entry point has been replaced by the WinMain() function. WinMain() is used in place of main() for all Windows applications and includes several new arguments.
Lets take a look at the arguments.
hInstance: This is a handle to the current `instance' of the program. This handle is used to register the window class.
hPrevInstance: A handle to the previous instance of the program. Under the Win16 API, this argument was necessary to save registering the window class more than once. With the Win32 API this argument is now obsolete. hPrevInstance is always NULL(0). To know about other instances of a program you will need to use other methods under Win32.
szCmdline: This is the entire program command line, without the program name. It is different to a non-windows application's argument list in that it uses one string rather than an array to hold the parameters.
nCmdShow: This argument is a flag to set the initial state of the program. Many states are available, but I've listed the most common ones below:
SW_SHOWNORMAL - opens the main window in its default size
and position;
SW_SHOW - opens the main window in its default size and
position;
SW_SHOWMAXIMIZED - opens the main window in a maximised
state;
SW_SHOWMINIMIZED - opens the main window in a minimized
state;
SW_SHOWMINNOACTIVE - opens the window in a minimised state.
WinMain() usually performs only three functions: register the window class, create the main window, and then execute an event loop.
You should start all your Win32 programs with the following windows header files:
windows.h this file contains a number of includes
necessary to successfully create Win32 programs.
windowsx.h this file contains a number of macros that
you will find useful as you do more Win32 development.
The window class
This task is solely involved in initialising the window class data and calling the registration function. Once you have created the class, you need to register it. This allows you to create windows by using the name of your class. Take a look at listing 2.
Listing 2 : The Window Class
WNDCLASS winclass; // This will hold the class you're
about to create
static char szClsName[] = "WINCLASS1"; // used to
hold the Window Class name
// Now we enter the information in the class structure.
winclass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW |
CS_DBLCLKS;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = szAppName;
// Now to register your new window class
if (!RegisterClass(&winclass))
return (0);
Lets take a look at some of the window class' properties.
style: This property specifies the class style(s). You can combine styles using the bitwise OR operator (|) The style property is used to define behaviour and characteristics of your window. Usually you can use the standard ones shown in the listing. If you'd like to see more, take a look at the Win32 SDK help file.
lpfnWndProc: This is a function pointer for the windows procedure that will handle all of the window's messages (Event Handler). I will be discussing the event handler shortly, but for now just understand that you need to provide the name of your Event Handler function to this property.
cbClsExtra: Specifies the number of extra bytes to allocate after the window-class structure. The operating system initialises the bytes to zero. This field is rarely used as most programmers prefer to store data elsewhere. Hence, this field should be set to 0.
cbWndExtra: Specifies the number of extra bytes to allocate after the window instance. The operating system initialises the bytes to zero. This field is rarely used as most programmers prefer to store data elsewhere. Hence, this field should be set to 0 also.
hInstance: This property stores a handle to the current instance of the program.
hIcon: A handle for the class icon. This icon is shown when the application is minimised or a shortcut is created. The icon can be loaded from a resource file, or a default icon can be used. For the purposes of this document, we will use a default icon by using the LoadIcon() function. (see Listing)
A list of constants:
IDI_APPLICATION - the default application icon;
IDI_HAND - a icon in the shape of a hand;
IDI_EXCLAMATION - an exclamation point icon;
IDI_ASTERISK - an asterisk icon;
IDI_QUESTION - a question mark icon.
hCursor: A handle for the class cursor. This is where you put your program's cursor. Once again, this can be loaded from a resource file, or a default can be used. For the purposes of this document, we will use a default cursor by using the LoadCursor() function. (See Listing)
A list of constants:
IDC_ARROW - the standard arrow pointer;
IDC_WAIT - the standard hourglass pointer;
IDC_IBEAM - the standard text pointer;
IDC_CROSS - the standard crosshair pointer;
IDC_NO - the circle with a line through it pointer.
hbrBackground: A handle for the background brush. A background brush is needed so a window's background can be drawn. Once again, you can use a default or create your own. We'll use a default for this document - but feel free to experiment. Thats what its all about!
A couple of default brushes:
BLACK_BRUSH - A black brush (surprise, surprise);
WHITE_BRUSH - A white brush (are you getting the picture?);
GRAY_BRUSH - A grey brush.
lpszMenuName: A string specifying the class menu name. This is the name of a resource for your menu. Set it to NULL in this example. In the next document I'll talk more about resource files and adding menus to your programs.
lpszClassName: The name of your new window class.
Your new class is registered by calling the RegisterClass function and passing the address of your new window class to it. Its a good idea to test the return value for 0, as this means an error has occurred in the registration process.
Once all this has been done and your window class is registered you can start creating windows. Yay!!
Creating the main window
Take a look at Listing 3. To create a window you need to call the CreateWindow() function. This function has several parameters which I'll discuss below.
Listing 3 : Creating the Window
// Now to create the window
if (!(hwnd = CreateWindow(szClsName,
"Hello Underworld", // Window title
return(0);
The prototype of the CreateWindow function looks like this:
HWND CreateWindow(
LPCTSTR lpClassName, // This is a pointer to the class name
LPCTSTR lpWindowName, // This is a pointer to the window title
DWORD dwStyle, // These set the window's style flags
int x, // The horizontal position of the window
int y, // The vertical position of the window
int nWidth, // The width of the window
int nHeight, // The height of the window
HWND hWndParent, // This is a handle to the parent
HMENU hMenu, // A handle to the menu
HANDLE hInstance, // A handle to the application instance
LPVOID lpParam, // Not used in this document - set it to NULL.
);
CreateWindow() returns a handle to a window. This will be non-NULL if the window creation has been sucessful.
lpClassName: A string containing your window class name.
lpWindowName:The string you wish to see on your window's title bar.
dwStyle: This parameter specifies the window's style. Once again, you can use the logical OR operator (|) to connect more than one flag. Some common values are listed below:
WS_OVERLAPPED - Creates a window with a title bar and
border;
WS_OVERLAPPEDWINDOW - Creates a window with a title bar,
border, and a standard Windows system menu;
WS_POPUP - Creates a standard popup window;
WS_POPUPWINDOW - Creates a standard popup window with a
system menu;
WS_VISIBLE - Creates a visible window. If this is used a call
to ShowWindow() is not required.
For more options, check out my other Win32 documents or take a look at the Win32 help file.
x and y: These set up the screen position where the window is to open. You can use CW_USEDEFAULT if you don't want to specify startup positions.
nWidth and nHeight: These are used to set the width and height of the window in pixels. you can use CW_USEDEFAULT if you don't want to specify a width and height.
hWndParent: This is the handle of the parent window. For our purposes this will be NULL.
hMenu: This is the handle to the window menu. In this document we have mentioned menus at the class registration, so this will be NULL.
lParam: Forget about this one for now.
CreateWindow() creates the window, but doesn't make it visible (if you didn't use the WS_VISIBLE style, there is another way). You need to call ShowWindow() which makes it visible.
To use ShowWindow() you need to pass it the handle of a window and the state of the window. If its succesful it returns TRUE, if its unsuccessful it returns FALSE. I've listed some of the common states below.
SW_SHOW - This displays the window in its current size and
position and activates it;
SW_HIDE - This hides the window;
SW_MAXIMIZE - This maximises the specified window;
SW_MINIMIZE - This minimises the specified window;
SW_RESTORE - This displays and activates the window from
being minimised or maxamised;
SW_SHOWMAXIMIZED -This shows the window in a maximized state;
SW_SHOWMINIMIZED -This shows the window in a minimized state.
So far we have covered everything you need to know to create, register and display a window. Next we will discuss Windows events and writing a Message Loop and an Event Handler.
The Message Loop
WinMain() doesn't do very much after it has performed the initialisation. For the rest of the program, it will spend its time processing and distributing messages. These 'messages' can be generated by moving the mouse, resizing a window, pressing a key, clicking on a menu item - just about anything that can happen to the computer (except maybe when you drop it <grin>).
The message loop is responsible for determining which window should handle a particular message. It then passes the message on to the Event Handler
Writing an event loop is fairly easy. I'll just give a quick rundown on the code required. Basically, there are three functions that are required:
BOOL GetMessage(LPMSG lpMsg, // This is a pointer to a
message struct
HWND hWnd, // This is the handle of a window
UINT wMsgFilterMin, // This is the first message
UINT wMsgFilterMax); //last message
All GetMessage() does is retrieves messages from the message queue. It waits until the message queue has a message.
BOOL TranslateMessage(CONST MSG *lpMsg); // This is a pointer to a message struct
TranslateMessage() is a more advanced function that essentially translates accelerator keys into simpler events. I will cover this in greater detail in a future essay.
LONG DispatchMessage(CONST MSG *lpMsg); // This is a pointer to a message struct
DispatchMessage() sends the message off for processing.
There are different ways to write a message loop, but this is the one I prefer. I like it because getmessage doesn't tie it up until a message is in the queue. You can use PeekMessage() to determine when a message is waiting in the queue.
Listing 4 : Message Loop example
MSG msg; // declared to hold the message
// This loops until there is a quit message
while(1)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// testing for a msg to quit
if (msg.message == WM_QUIT)
break;
// If there are accelerator keys, translate them
TranslateMessage(&msg);
// Dispatch the message to the event handler
DispatchMessage(&msg);
}
}
The Event Handler
The event handler is where a lot of the work is done. Within this function you must write code to take care all of the events you wish to `handle'. You don't have to write code for every conceivable event - only for the ones you want to respond to. Don't worry, because everything you don't handle is send to the Windows default message handler - DefWindowProc()
Lets take a look at the function prototype for WindowProc().
LRESULT
CALLBACK WindowProc(HWND hWnd, // A handle to the window
UINT msg, // The message
WPARAM wParam, // extra message
information
LPARAM lParam); //extra message
information
CALLBACK is a function that is called when an event
occurs.
LRESULT is a 32-bit integer.
hWnd: The handle of the window that sent the message.
msg: A number identifying the message (not a MSG struct)
wParam and lParam: additional information about the message to further qualify it.
Here is a list of Windows messages that are usually handled.
WM_ACTIVATE - Occurs when a window gets focus or is
activated;
WM_CLOSE - Occurs when a window is closed;
WM_CREATE - Occurs when a window is created for the first
time;
WM_DESTROY - Occurs when a window is being destroyed;
WM_KEYDOWN - Occurs when a key is pressed;
WM_KEYUP - Occurs when a key is released;
WM_MOUSEMOVE - Occurs when the mouse has been moved;
WM_PAINT - Occurs when a window needs to be repainted;
WM_QUIT - Occurs when a program is terminating;
WM_SIZE - Occurs when a window has been resized;
WM_TIMER - Occurs when a timer event activates;
WM_USER - Allows you to send messages to be handled.
I've written a pretty basic event handler for you to take a look at.
Listing 5 : A Basic Event Handler
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// This is the function to handle messages sent to your
application window.
HDC hdc; // This is a device context used for graphics
PAINSTRUCT ps; // This is also used for graphics
// check which message was sent.
switch(msg)
{
case WM_CREATE:
{
// This event occurs when your window is first created. Normally you'd write
// initialization code here.
return(0); // return to the message loop
} break;
case WM_DESTROY:
{
// This event occurs when your window is about to be destroyed.
// You must post a quit message to the event queue. This sends a WM_QUIT
// message to the message queue.
PostQuitMessage(0);
return(0); // return to the message loop
} break;
default: break;
}
//The windows default event handler will process what you
didn't,
return(DefWindowProc(hwnd, msg, wParam, lParam));
} // and so ends a basic introduction to WindowProc.
To write one that will work for a basic windows program we will also need to include a WM_PAINT event to be handled. This occurs when your window needs to be repainted. I'll include an example of this in the code at the end of this document.
Creating the project
In Visual C++, all you need to do is create a new project, and make sure you specify a Win32 application. You then need to add a source file by either adding the file (if the source is in a text file) or select New from the file menu and then choose C++ Source file. This will create an empty text file ready for you to type in your program. The final step is to compile and run the program.
Summary
You finally made it through to the end. Congratulations! By now you should be able to create windows to your heart's content. Obviously there is much more to Win32 programming than this, so in my next document - I'll give some more information to work with. But for now, try playing around with some of the various flags.
Hope I helped!
Cheers,
Cali
Final Listing : The full listing of what you have been working on.
// Include Files
#include <windows.h>
#include <windowsx.h>
HWND main_window_handle = NULL; // This is used to store the window handle
// Main Event Handler Function
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM
wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch(msg)
{
case WM_CREATE:
{
// This event occurs when your window is first created. Normally you'd write
// initialization code here.
return(0);
} break;
case WM_PAINT:
{
hdc = BeginPaint(hwnd,&ps);
EndPaint(hwnd,&ps);
return(0);
} break;
case WM_DESTROY:
{
// Kill your application
PostQuitMessage(0);
return(0);
} break;
default: break;
} // End message test
return
(DefWindowProc(hwnd, msg, wParam, lParam));
} // End
WindowProc
// The WinMain Function
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR szCmdLine,
int nCmdShow)
{
static char szClsName[] = "WINCLASS1";
WNDCLASS winclass; // This will hold the class you're about
to create
HWND hwnd;
MSG msg; // To hold a standard message
// Now we enter the information in the class structure.
winclass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW |
CS_DBLCLKS;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = szClsName;
// Now to register your new window class
if (!RegisterClass(&winclass))
return (0);
// Now to create the window
if (!(hwnd = CreateWindow(szClsName,
"Hello Underworld", // Window title
WS_OVERLAPPEDWINDOW | WS_VISIBLE, // Style flags
0,0, // setting the start-up position of the window
400,300, // setting the width and height of the window
NULL, // the handle to the parent (not necessary in this document)
NULL, // the handle to a menu (read below)
hinstance, // the handle to the window
NULL)))
return(0);
main_window_handle = hwnd;
// This loops until there is a quit message
while(1)
{
if
(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// testing for a msg to quit
if (msg.message == WM_QUIT)
break;
// If there are accelerator keys, translate them
TranslateMessage(&msg);
// Dispatch the message to the event handler
DispatchMessage(&msg);
}
}
return(msg.wParam); // And this is how you return to windows
} // The end of WinMain