csammisrun

A rare situation

Chapter 4

without comments

Creating our first window

Note: I’m making this up as I go along! Until the entire tutorial is finished, the projects are subject to change without a whole lot of warning from me. Don’t come complaining if you do something silly like take everything I say here as absolute truth until I’m done ;)

For an introduction to actual GUI-type code, we’ll start with an old classic: Notepad. Notepad is an excellent example of what can be accomplished with simplicity, and simplicity is what we’re going to be doing for a while. In chapters four, five, and six, we are going to build a simple text editor called JotStuffDownPad. The project code in its entirety will be posted at the end of chapter six, but I strongly recommend using Visual Studio and following along the chapters by copying and pasting the code presented. Working along with the code as it’s laid out will give you an idea of the ebb and flow of the program. So fire up Visual Studio, and let’s get coding!

Starting things off

Following the directions outlined in chapter one, create a Visual Studio project called “JotStuffDownPad”. Once the project has been created, click on the FileView tab to get a listing of the files in your project.

The FileView tab
If you do not see the FileView area, or the text field at the bottom of the screen,
go to the View menu and make sure that “Workspace” and “Output” are visible

Double-click on JotStuffDownPad.cpp. Visual Studio has already created a small chunk of code for you. Let’s step through it and see what there is:

// JotStuffDownPad.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	// TODO: Place code here.

	return 0;
}

That doesn’t look too horrible, does it? The directive simply includes the standard header file that all the .cpp files in this project will include; eventually we will place additional header files there. The only other bit of code that Visual Studio starts you off with is the entry point, (if you didn’t read chapter two, now would be a good time to do so). The arguments to are:

  • — A handle to the current instance of the application. This handle will be used to load data from resource files, among other things.
  • — Ostensibly, this once held a handle to any previously running instance of the application. It’s obsolete now, and will always be .
  • — This is a pointer to a null-terminated string (a normal “C-type” string) that contains the commandline passed to the application, minus the name of the application itself (if you didn’t already know, the name of the application is always the first parameter passed).
  • — A value that specifies how the window is to be shown initially (this could be: minimized, maximized, hidden, shown, or some combination of the above). We will use this value when we create the first window for the application.

If you think that this code looks really thin and fairly useless, you’re dead-on right. Build this file (press F7 or go to “Build -> Build JotStuffDownPad.exe”), and press Ctrl-F5 to execute (run) it. This will result in…nothing! The application starts with , but since the very first thing it encounters is , it exits immediately. If you’re as bored with this as you should be, don’t worry, it’s time to add some code in that’ll make the application dance!

Initializing a GUI application

If you read chapter two, you know that any GUI application must have some initialization done to it in . If you then read chapter three, you know that the initialization in question involves creating a window class. Let’s add in a function that does exactly that. Put the following prototype above :

bool InitApp(HINSTANCE hInstance);

This is where we’ll put all the initialization that we need to do. Because the window class requires a handle the application’s instance, we pass that parameter in directly from the arguments to . Below the prototype, let’s flesh out the function:

bool InitApp(HINSTANCE hInstance)
{
	WNDCLASSEX wcx;

	// cbSize: The size of the structure itself.
	wcx.cbSize = sizeof(WNDCLASSEX);
	// style: Defines how Windows will update (redraw) this window.
	wcx.style = CS_HREDRAW | CS_VREDRAW;
	// lpfnWndProc: A pointer to the application-defined window procedure.
	//              We define this a bit later in the code.
	wcx.lpfnWndProc = (WNDPROC)WindowProc;
	// cbClsExtra, cbWndExtra: These are rarely used (by me), set to zero.
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	// hInstance: handle to the instance of the application
	//            that contains the window procedure
	//            (usually always the current application).
	wcx.hInstance = hInstance;
	// hIcon: a handle to the icon to display in the Alt-Tab window
	//        and on the desktop (and large icon view in Explorer).
	//        Here we load a default icon supplied by Windows.
	wcx.hIcon = LoadIcon(NULL,IDI_APPLICATION);
	// hCursor: a handle to the mouse pointer to display when the mouse
	//          enters our window. Here we load the default arrow.
	wcx.hCursor = LoadCursor(NULL,IDC_ARROW);
	// hbrBackground: a handle to the brush that Windows will use to
	//                "paint" the background of our window. We'll
	//                use the color of a button (defaults to a nice light gray),
	//                but we *must* add 1 to the value for it to be usable in a class
	wcx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	// lpszMenuName: a string identifying the menu resource to load.
	//               Since we don't have a menu resource (yet), this
	//               is NULL.
	wcx.lpszMenuName = NULL;
	// lpszClassName: a string that uniquely identifies this class
	wcx.lpszClassName = "JSDP";
	// hIconSm: a handle to the icon to display in the taskbar and the
	//          title bar of the application. Since we don't have a custom
	//          one, set this to NULL and Windows will search the hIcon member
	//          of this structure to load a little icon
	wcx.hIconSm = NULL;

	// Register the class with Windows
	if(!RegisterClassEx(&wcx))
		return false;

	return true;
}

Whoooooo! We got the window class going by filling in a structure with values appropriate to our application, and then registered that class with Windows by passing the structure to . We add to and get this:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	if(InitApp(hInstance) == false)
		return 0;

	return 0;
}

As you continue to write applications, you would probably want to add an alert to the user that the program failed to initialize and that’s why double-clicking on it didn’t do squat.

Creating the initial window

With the initialization done, it’s time to display a window for all to see! We’ll put this code into another function that will return a handle to the newly created window:

HWND BuildWindow(int nCmdShow);

Since we have to show our window in a manner consistent with the way it was launched, we pass from into . Now, let’s put some definition to this!

HWND BuildWindow(int nCmdShow)
{
	DWORD style, xstyle;
	HWND retval;
	// The window style defines how the window looks, and to
	// some extent how it behaves. Here we specify
	// WS_OVERLAPPEDWINDOW, which creates a window with a titlebar,
	// a minimize box, a maximize box, a system menu, and a sizable
	// border
	style = WS_OVERLAPPEDWINDOW;
	// We define the extended window style to be WS_EX_LEFT,
	// which is the default for all windows
	xstyle = WS_EX_LEFT;

	retval = CreateWindowEx(xstyle,
                            "JSDP",         // The class name (defined in InitApp)
                            "JotStuffDownPad", // The title for our window (appears in the titlebar)
                            style,
                            CW_USEDEFAULT,  // The initial x coordinate, here we let Windows place it
                            CW_USEDEFAULT,  // The initial y coordinate, here we let Windows place it
                            500,400,        // The initial width and height of the window
                            NULL,           // A handle to the parent window...there is no parent
                                            // window here, but there will be for others later on
                            NULL,           // A handle to the menu for the window. We don't have one yet
                            NULL,           // A handle to the application creating the window; ignored
                            NULL);          // A pointer to extra data for the window. I never use it
	if(retval == NULL)
		return NULL;  // Something went wrong here
	ShowWindow(retval,nCmdShow);  // Show the newly created window
	return retval; // ...and return its handle
}

The comments in the code should explain all the arguments to . creates a new window based on the attributes that you pass to the function, and returns a handle to that window. If something went wrong during the window creation, returns . We test for this case, and if the window was created successfully, we call to display the new window. takes for its arguments a handle to the window and a specific value that describes how to display that window (in our case, we use the value that Windows specifies). See the MSDN entry for the complete list of values that accepts.

Now we have to add this in to :

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	if(InitApp(hInstance) == false)
		return 0;

	if(BuildWindow(nCmdShow) == NULL)
		return 0;

	return 0;
}

Adding in the window procedure and message loop

Only two more pieces of the puzzle left! If you’ve tried to build the code we’ve put together so far, you’ll notice that the compiler complains about WindowProc being undefined. We haven’t given the window a window procedure yet, so let’s do that now by adding this prototype…

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

…and then adding this function somewhere below it…

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

…and we’re done with piece one! As described in chapter three, accepts everything that the programmer doesn’t process him/herself. Since we’re not processing anything (yet), we just pass the parameters from straight into and wash our hands clean of it. Now that there’s a place for the messages to be received, we have to have a way to get the messages there. Enter piece two, the application’s message loop. We insert it into thusly:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	if(InitApp(hInstance) == false)
		return 0;

	if(BuildWindow(nCmdShow) == NULL)
		return 0;

	MSG msg; int retval;
	while((retval = GetMessage(&msg,NULL,0,0)) != 0)
	{
		if(retval == -1)
			return 0;	// an error occured while getting a message
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

There it is, the life and times of a Win32 GUI application, the message loop! , as the name might imply, retrieves the next message from the application’s message queue and puts it into the variable . translates virtual keypresses into character messages that are put back on the message queue, and then eventually retrieved again by (that comes straight from MSDN, putting a message *back* into the queue in a different form doesn’t make much sense to me either). Finally, sends the message off to the window procedure to be processed. This keeps going ’round and ’round until a special message gets posted, more on that in a bit…

Build…and what’s wrong with it??

At this point, press F7 to compile and build JotStuffDownPad.exe. If you’ve copied the code down correctly, you should end up with zero compile errors and a nicely put together EXE. Huzzah for you, mighty programmer! Now to see the fruits of your labor, press F5 to start the debugger (*not* Ctrl-F5, Execute Program…this is important). Visual Studio will think for a second, change its titlebar to say “[run]” (indicating that it’s busy debugging), and then display your window to the world.

It's...beautiful...

Play with your new window…move it around, resize it, minimize it, marvel at it. Now close it. Go back to Visual Studio, but look carefully at the titlebar. Visual Studio still says “[run]“…but we closed the window!

Here’s the catch: We closed the window. Closing a window that belongs to an application doesn’t stop the application; JotStuffDownPad.exe is still happily accepting messages and is thusly caught in its message loop. Press Shift-F5 to terminate the debugger. If you did exactly what I told you not do to, and executed the program instead of running it in the debugger, you’re going to have to press Ctrl-Alt-Del (or Ctrl-Shift-Esc in Windows 2000 or XP) to bring up the list of running processes and kill JotStuffDownPad.exe (Helpful hint: always run your programs in the debugger until you’re sure they work).

Since we want our application to quit when the user closes the window, we have to process the message that tells the window to close itself and break out of the message loop at that point. This specific message is called , and to do this, we add the following code into to examine the message and act accordingly:

switch(uMsg)
{
case WM_DESTROY:
	// Exit the application when the window closes
	PostQuitMessage(1);
	return true;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);

At this point, a more detailed explaination of the arguments to is appropriate. is a handle to the window that is receiving the messages. , as you can probably divine from the code above, is the message that is currently being processed. and are parameters for that message, whose contents vary depending on the message that’s being received. The code that we just added in will give us the framework for event handling for our window; if we want to intercept any message in the future, we only have to add in another case to the statement.

As mentioned in the example I gave in chapter three, sends the message onto the message queue. When receives this, it causes the message loop to stop and to exit.

Press F5 to start the debugger again (Visual Studio will ask you if you want to recompile the program, say “Yes”). This time, when you close the window, Visual Studio will happily also stop the debugger because your program exited naturally.

Summary

Well, we did a lot in this chapter! The ideas presented in chapter three were put to practice as we implemented the creation, showing, and message processing of a window. Hopefully, you got some idea of how the control of the application “flows” from initialization, window creation, and eventual destruction.

At the end of each chapter that involves actual code, I’ll create a list of API functions and structures that were used, as well as important concepts presented.

Newly used API functions, structures, and messages

  • — be sure that you have an idea of how this works, we’ll be using it a lot. Don’t worry about knowing *everything* right now, you’re expected to learn as we go

Now that we have a window, let’s learn about what we can add into it

Back to contents

Written by Chris

May 29th, 2007 at 6:31 pm

Posted in

Leave a Reply