Top

C++/Win32: Basic Window Creation

March 2, 2008

The first thing you need to do when creating a Windows application, obviously, is to create a window.This is the one subject that seems to be covered all over the place, so we’ll just go through it quickly. If you need more information on this subject, check out any of the links provided below, as they cover the topic in more depth. But it really is quite a simple procedure.

Its going to seem like we’re blowing right through this, and we are. But don’t worry, the real details will be explored in the next lesson, when we wrap our window creation procedure in a class.

Creating your main app window is a three step process:

1. Register a window class with Windows
2. Create a window object from your window class
3. Show the window

Yes, it really is that easy. Let’s take it step by step:

Registering a window class

You register a window class by declaring a WNDCLASS or WNDCLASSEX structure, populating it with details about your window, and then passing it to RegisterClass or RegisterClassEx.

Example:


WNDCLASSEX WndClsEx;// Populate the WNDCLASSEX structure
WndClsEx.cbSize        = sizeof(WNDCLASSEX);
WndClsEx.style         = CS_HREDRAW | CS_VREDRAW;
WndClsEx.lpfnWndProc   = WndProcedure;
WndClsEx.cbClsExtra    = 0;
WndClsEx.cbWndExtra    = 0;
WndClsEx.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
WndClsEx.hCursor       = LoadCursor(NULL, IDC_ARROW);
WndClsEx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndClsEx.lpszMenuName  = NULL;
WndClsEx.lpszClassName = "GlowdotWin32TutorialPartI";
WndClsEx.hInstance     = hInstance;
WndClsEx.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

// Register the class
RegisterClassEx(&WndClsEx);

So what do all these structure elements mean? You don’t have to worry about them in much detail for now, so we’ll cover them quickly:

cbSize

Always set this to sizeof(WNDCLASSEX) or sizeof(WNDCLASS), depending on which you use.

style

Pass here the class style constants you wish to use to define the style of your window. Here we are designating a window that redraws the entire window if the window is resized vertically (CS_VREDRAW) or horizontally (CS_HREDRAW). You can view the entire list of class styles here.

lpfnWndProc

A pointer to the start of the window’s message handler. We will discuss this in more detail in a bit.

hIcon

The icon you want to use for your window. We will cover this, and hIconSm when we discuss resources in a later lesson.

hInstance

The handle to the instance of your app. This is given to your WinMain function, and should be passed on from there.

lpszClassName

A unique name that we give to our new window class. We will use this name when we call CreateWindow(Ex) to let the function know which window class we want an object instance of.

The rest of the structure fields can be studied here.

Create a window object from your window class

We will need to declare an HWND variable (a handle to a window object), and call either CreateWindow or CreateWindowEx in order to create our window object. The return value of CreateWindow(Ex) is the handle to our new window object, so we assign it to our HWND variable.

Example:


HWND hWnd = CreateWindow("GlowdotWin32TutorialPartI",
 		  "Glowdot Win32 Tutorial - Part I",
 		  WS_OVERLAPPEDWINDOW,
 		  CW_USEDEFAULT,
 		  CW_USEDEFAULT,
 		  CW_USEDEFAULT,
 		  CW_USEDEFAULT,
 		  NULL,
 		  NULL,
 		  hInstance,
 		  NULL);

Again, I won’t go into much detail about this function call either. You can find out more about CreateWindow(Ex) here, or by visiting the further reading links below.

With this function call, we have an opportunity to choose window styles and we have a second opportunity to add a menu to the window. We will do neither in this simple example. The most important thing is the first parameter. We need to give CreateWindow the name of the class (which we defined when we filled our WNDCLASSEX structure, remember?) so it knows what kind of object we want. We can also set the text in the title bar by passing a string as the second paramter.

The third parameter sets the window styles. You can see all the available window styles here. In this example, we have asked for an overlapped window using the style WS_OVERLAPPEDWINDOW. WS_OVERLAPPEDWINDOW is a short way of specifying the style set WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX. Look those styles up if you want to know exactly what kind of window we’ve asked for.

At this point, we have registered our window class, and created an object from this class. But it still isn’t up on the screen.

Show the window

To get the window up on the screen, we need to call ShowWindow:

// Show the window
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);

As you can see, we are simply passing in the handle to our window, plus a constant defining how it should be shown. In our case, we are using the SW_SHOWNORMAL show method.

Note that you can skip the ShowWindow step by passing in the window style constant WS_VISIBLE upon calling CreateWindow. This will cause the window to go visible upon creation, reducing the number of steps here to two. You won’t always want to do this, however, since more complicated apps will require you to register and create all your windows during initialization, and then show them only when the user requests that you do.

The Message Handler

Well all of that was quite simple, but how do we make the window do something? Go back and take a look at the WNDCLASSEX structure. We needed to pass it a pointer to the start of a function. This function is a special kind of function that is called directly by Windows whenever Windows has a message for your window. Because Windows doesn’t know or care what language we’re writing our application in, it demands that your function take its parameters in the manner it dictates. Thus we declare the function to be a CALLBACK function. This simply tells the compiler that this function will receive its parameters in the way Windows expects it to. The function declaration for our message handler looks like this:

LRESULT CALLBACK WndProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

You no doubt want to know what these parameters are. Windows hands messages to your windows in a rather clever way, using an integer message code along with two 32-bit values that provide more information about the message. For example, if a key is pressed while your window is in focus, Windows will call your window’s message handler function, passing it the appropriate message value (WM_KEYDOWN in this case) and will pass the virtual-key code of the key pressed in the wParam parameter. Whenever you need your window to respond to a particular message, you need to find the message constant, and you need to know what will be contained in wParam and lParam when that message is sent. Your message handler’s job is to act accordingly.

The Message Pump

Now, if WinMain just registers your class, calls CreateWindow, and calls ShowWindow, and nothing else, your app will just show your window for a millisecond and terminate. To keep it running, you will need some kind of loop in WinMain that will run until the user requests an exit. This loop is called the message pump.

You can think of the message pump as your first chance to process messages before they get sent to the window they are meant for. Every application gets a message queue from Windows, in which all messages meant for windows under the control of the app go. Windows will not deliver these messages to your windows until you tell it to do so. The message pump in our example can make use of several functions that deal with the message queue: PeekMessage, GetMessage, TranslateMessage and DispatchMessage. Our message pump is as follows:

// our message pump
while( (hRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
     if (hRet == -1)
     {
         // handle the error and possibly exit
     }
     else
     {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
     }
}

A lot of examples of message pumps you’ll find on the net will tell you to use the return value of GetMessage alone as the conditional of the loop, but GetMessage can also return -1 on an error, and to avoid a hideous crash, you should be prepared for that situation. That is as simple as checking for a -1 return value and dealing with it if necessary. In this example, hRet is of type HRESULT. So, our message pump pulls a message out of the queue, translates it and dispatches it. By translating it and dispatching it, we are packing it and shipping it off to the message handler of the window it is intended for.

I mentioned previously the PeekMessage function, which is almost identical to the GetMessage function, except that it does not necessarily remove the message from the queue, while GetMessage always does.

Wrap Up:

We just breezed right through basic window creation. Odds are you’ve seen this before, and if not, I will again recommend visiting the site links for this lesson. Don’t concern yourself with it too much, as we will go into much more detail in the next lesson, when we wrap these function calls inside a class. We are using C++ after all, and we should be taking advantage of the object oriented paradigm here. As you’ll see, that presents a few problems, but none too difficult.

Next: Creating a Win32 Wrapper Class (Part 1)


Source Code: win32tut_part1.zip [314kb zipped]Additional Information:· Win32 Tutorial Lesson 10: Window Creation· Reliable Software Win32 Tutorial

Further Reading:

· Nitty Gritty Windows Programming with C++ by Henning Hansen.

· Thinking in C++ by Bruce Eckel.

Comments

Got something to say?





Bottom