Win32 Programming
Win32 Programming
Contents
The simplest Win32 program......................................................................................2
Calling Conventions.................................................................................................4
Win32 Data Types...................................................................................................4
Step 1: Registering the Window Class.....................................................................6
Step 2: Creating the Window...................................................................................8
Step 3: The Message Loop.....................................................................................10
Step 4: the Window Procedure..............................................................................10
Handling Messages...................................................................................................12
Understanding the Message Loop............................................................................17
What is a Message?............................................................................................17
Dialogs...............................................................................................................18
What is the Message Queue...............................................................................18
What is a Message Loop.....................................................................................18
Using Resources.......................................................................................................21
Menus and Icons.......................................................................................................23
The program file icon.........................................................................................28
Dialogs, GUI coders best friend................................................................................28
Modeless Dialogs......................................................................................................33
Standard Controls: Button, Edit, List Box..................................................................36
Controls..............................................................................................................37
Messages...........................................................................................................37
Edits......................................................................................................................37
Edits with Numbers............................................................................................39
List Boxes..............................................................................................................39
Adding Items......................................................................................................39
Notifications.......................................................................................................40
Getting Data from the ListBox............................................................................40
Statics...................................................................................................................41
Statics
Page |2
Basics
What this tutorial is all about
This tutorial is intended to present to you the basics (and common extras) of writing
programs using the Win32 API. The language used is C, most C++ compilers will
compile it as well. As a matter of fact, most of the information is applicable to any
language that can access the API, inlcuding Java, Assembly and Visual Basic. I will
not however present any code relating to these languages and you're on your own
in that regard, but several people have previously used this document in said
languages with quite a bit of success.
This tutorial will not teach you the C language, nor will it tell you how to run your
perticular compiler (Borland C++, Visual C++, LCC-Win32, etc...) I will however take
a few moments in the appendix to provide some notes on using the compilers I
have knowledge of.
If you don't know what a macro or a typedef are, or how a switch() statement works,
then turn back now and read a good book or tutorial on the C language first
#include <windows.h>
If that doesn't work, your first step is to read whatever errors you get and if you
don't understand them, look them up in the help or whatever documents
accompany your compiler. Make sure you have specified a Win32 GUI (NOT
"Console") project/makefile/target, whatever applies to your compiler.
Unfortunately I can't help much with this part either, as errors and how to fix them
vary from compiler to compiler (and person to person).
You may get some warnings about you not using the parameters supplied to
WinMain(). This is OK. Now that we've established you can in fact compile a
program, lets go through that little bit of code....
WinMain() is windows equivalent of main() from DOS or UNIX. This is where your
program starts execution. The parameters are as follows:
HINSTANCE hInstance
HINSTANCE hPrevInstance
LPSTR lpCmdLine
The command line arguments as a single string. NOT including the program name.
int nCmdShow
An integer value which may be passed to ShowWindow(). We'll get to this later.
hInstance is used for things like loading resources and any other task which is
performed on a per-module basis. A module is either the EXE or a DLL loaded into
your program. For most (if not all) of this tutorial, there will only be one module to
worry about, the EXE.
Page |4
hPrevInstance used to be the handle to the previously run instance of your program
(if any) in Win16. This no longer applies. In Win32 you ignore this parameter.
Calling Conventions
WINAPI specifies the calling convention and is defined as _stdcall. If you don't know
what this means, don't worry about it as it will not really affect us for the scope of
this tutorial. Just remember that it's needed here.
Just remember a few things and they will be easy to interpret. An LP prefix stands
for Long Pointer. In Win32 the Long part is obsolete so don't worry about it. And if
you don't know what a pointer is, you can either 1) Go find a book or tutorial on C,
or 2) just go ahead anyway and screw up a lot. I'd really recommend #1, but most
people go with #2 (I would :). But don't say I didn't warn you.
A Simple Window
Sometimes people come on IRC and ask "How do I make a window?"...Well it's not
entirely that simple I'm afraid. It's not difficult once you know what you're doing but
there are quite a few things you need to do to get a window to show up; And they're
more than can be simply explained over a chat room, or a quick note.
I always liked to do things first and learn them later...so here is the code to a simple
window which will be explained shortly.
#include <windows.h>
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
For most part this is the simplest windows program you can write that actually
creates a functional window, a mere 70 or so lines. If you got the first example to
compile then this one should work with no problems.
Page |7
The variable above stores the name of our window class, we will use it shortly to
register our window class with the system.
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
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 members of the struct affect the window class as follows:
cbSize
The size of the structure.
style
Class Styles (CS_*), not to be confused with Window Styles (WS_*) This can usually be
set to 0.
lpfnWndProc
Pointer to the window procedure for this window class.
cbClsExtra
Page |8
HWND hwnd;
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
Next we have the class name (g_szClassName), 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 name or
title which is the text that will be displayed in the Caption, or Title Bar on our
window.
Page |9
The next four parameters (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) are the X
and Y co-ordinates for the top left corner of your window, and the width and height
of the window. I've set the X and Y values to CW_USEDEFAULT to let windows
choose where on the screen to put the window. Remeber that the left of the screen
is an X value of zero and it increases to the right; The top of the screen is a Y value
of zero which increases towards the bottom. The units are pixels, which is the
smallest unit a screen can display at a given resolution.
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 I
almost never use) that can be used to send additional data to the window that is
being created is also NULL.
If you're wondering what this magic NULL is, it's simply defined as 0 (zero). Actually,
in C it's defined as ((void*)0), since it's intended for use with pointers. Therefore you
will possibly get warnings if you use NULL for integer values, depending on your
compiler and the warning level settings. You can choose to ignore the warnings, or
just use 0 instead.
Number one cause of people not knowing what the heck is wrong with their
programs is probably that they didn't check the return values of their calls to see if
they failed or not. CreateWindow() will fail at some point even if you're an
experianced coder, simply because there are lots of mistakes that are easy to
make. Untill you learn how to quickly identify those mistakes, at least give yourself
the chance of figuring out where things go wrong, and Always check return
values!
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
P a g e | 10
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.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
GetMessage() gets a message from your 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() you are
requesting the next available message to be removed from the queue and returned
to you for processing. If there is no message, GetMessage() Blocks. If you are
unfamiliar with the term, it means that it waits untill there is a message, and then
returns it to you.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
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.
WM_CLOSE is sent when the user presses the Close Button or types Alt-F4. This
will cause the window to be destroyed by default, but I like to handle it explicitly,
since this is the perfect spot to do cleanup checks, or ask the user to save files etc.
before exiting the program.
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.
P a g e | 12
P a g e | 13
Handling Messages
Example: window_click
In the next section I am going to show you how to modify what you already have to
do something new. This way I can just tell you "Handle this message, and do this in
it..." and you will know what I mean and be able to do so without seeing an entire
example. That's the hope anyway, so pay attention :P
Okay for starters take the example code for the last window we worked on and
make sure it compiles and runs as expected. Then you can either keep working on it
for the next little bit or copy it to a new project to modify.
We're going to add the capability to show the user what the name of our program is
when they click on our window. Not very exciting, it's basically to get the hang of
handling messages. Lets look at what we have in our WndProc():
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
P a g e | 14
If I or someone else refers to handling a message they mean to add it into the
WndProc() of your window class as follows:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN: // <-
// <- we just added this stuff
break; // <-
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
The order in which you handle your messages rarely matters. Just make sure you've got your
break; after each one. As you can see we added another case into our switch(). Now we want
something to happen when we get to this part of our program.
First I will present the code we want to add (that will show the user the filename of our program)
and then I will integrate it into our program. Later on I will probably just show you the code and
let you integrate it into your program. This is of course better for me as I don't have to type as
much and it's better for you because you will be able to add the code into ANY program and not
just the ones I present.
GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
Now this code does not stand on it's own, it can't just be slapped into our code any old place. We
specifically want it to run when the user clicks the mouse button so this is how I would merge
this small bit of code into our skeleton program:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
// BEGIN NEW CODE
{
P a g e | 15
char szFileName[MAX_PATH];
HINSTANCE hInstance = GetModuleHandle(NULL);
Now on to the second parameter, again turning to our trusty reference manual, we
see that it is " a pointer to a buffer that receives the path and file name of the
specified module" and the data type is LPTSTR (or LPSTR if your references are old).
Since LPSTR is equivalent to char* we can declare an array of char's like this:
char szFileName[MAX_PATH];
After GetModuleFileName() is called, the buffer szFileName will be filled with a null
terminated string containing the name of our .exe file. We pass this value to
MessageBox() as an easy way of displaying it to the user.
P a g e | 16
So if you've added in that code, compile it now. If it works, click on the window and
you should see a box with the name of the .exe pop up.
If it doesn't work, here's the full code to the program. Compare it to what you have
and see what, if any, mistakes you made.
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
{
char szFileName[MAX_PATH];
HINSTANCE hInstance = GetModuleHandle(NULL);
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
P a g e | 17
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
What is a Message?
A message is an integer value. If you look up in your header files (which is good and
common practice when investigating the workings of API's) you can find things like:
..and so on. Messages are used to communicate pretty much everything in windows
at least on basic levels. If you want a window or control (which is just a specialized
window) to do something you send it a message. If another window wants you to do
something it sends you a message. If an event happens such as the user typing on
the keyboard, moving the mouse, clicking a button, then messages are sent by the
system to the windows affected. If you are one of those windows, you handle the
message and act accordingly.
Each windows message may have up to two parameters, wParam and lParam.
Originally wParam was 16 bit and lParam was 32 bit, but in Win32 they are both 32
bit. Not every message uses these parameters, and each message uses them
differently. For example the WM_CLOSE message doesn't use either, and you should
ignore them both. The WM_COMMAND message uses both, wParam contains two
values, HIWORD(wParam) is the notification message (if applicable) and
LOWORD(wParam) is the control or menu id that sent the message. lParam is the
HWND (window handle) to the control which sent the message or NULL if the
messages isn't from a control.
HIWORD() and LOWORD() are macros defined by windows that single out the two
high bytes (High Word) of a 32 bit value (0xFFFF0000) and the low word
(0x0000FFFF) respectively. In Win32 a WORD is a 16bit value, making DWORD (or
Double Word) a 32bit value.
once the call to PostMessage() is done the message may or may not have been
processed yet. SendMessage() sends the message directly to the window and does
not return untill the window has finished processing it. If we wanted to close a
window we could send it a WM_CLOSE message like this PostMessage(hwnd,
WM_CLOSE, 0, 0); which would have the same effect as clicking on the button on
the top of the window. Notice that wParam and lParam are both 0. This is because,
as mentioned, they aren't used for WM_CLOSE.
Dialogs
Once you begin to use dialog boxes, you will need to send messages to the controls
in order to communicate with them. You can do this either by using GetDlgItem()
first to get the handle to the control using the ID and then use SendMessage(), OR
you can use SendDlgItemMessage() which combines the steps. You give it a window
handle and a child ID and it will get the child handle, and then send it the message.
SendDlgItemMessage() and similar APIs like GetDlgItemText() will work on all
windows, not just dialog boxes.
1. The message loop calls GetMessage(), which looks in your message queue. If
the message queue is empty your program basically stops and waits for one
(it Blocks).
3. We take the message (in the Msg variable) and pass it to TranslateMessage(),
this does a bit of additional processing, translating virtual key messages into
character messages. This step is actually optional, but certain things won't
work if it's not there.
5. In your window procedure you check the message and it's parameters, and
do whatever you want with them! If you aren't handling the specific message,
you almost always call DefWindowProc() which will perform the default
actions for you (which often means it does nothing).
6. Once you have finished processing the message, your windows procedure
returns, DispatchMessage() returns, and we go back to the beginning of the
loop.
This is a very important concept for windows programs. Your window procedure is
not magically called by the system, in effect you call it yourself indirectly by calling
DispatchMessage(). If you wanted, you could use GetWindowLong() on the window
handle that the message is destined for to look up the window's procedure and call
it directly!
I tried this with the previous example code, and it does work, however there are
various issues such as Unicode/ANSI translation, calling timer callbacks and so forth
that this method will not account for, and very likely will break all but trivial
applications. So do it to try it, but don't do it in real code :)
As you can see, your application spends the majority of it's time spinning round and
round in this message loop, where you joyfully send out messages to the happy
windows that will process them. But what do you do when you want your program
to exit? Since we're using a while() loop, if GetMessage() were to return FALSE (aka
0), the loop would end and we would reach the end of our WinMain() thus exiting
the program. This is exactly what PostQuitMessage() accomplishes. It places a
WM_QUIT message into the queue, and instead of returning a positive value,
GetMessage() fills in the Msg structure and returns 0. At this point, the wParam
member of Msg contains the value that you passed to PostQuitMessage() and you
can either ignore it, or return it from WinMain() which will then be used as the exit
code when the process terminates.
while(GetMessage(&Msg, NULL, 0, 0) != 0)
This, or code that has the same effect should always be used.
I hope you now have a better understanding of the windows message loop, if not,
do not fear, things will make more sense once you have been using them for a
while.
P a g e | 22
Using Resources
You may also want to refer to the Appendices at the end of this tutorial for more
information on resources with VC++ and BC++.
Before we get any deeper I will cover the topic of resources so that I won't have to
re-write it for each section.You don't actually need to compile the stuff in this
section, it's as example only.
Resources are pre-defined bits of data stored in binary format inside your
executable file. You create resources in a resources script, a file with an extension
of ".rc". comercial compilers will have a visual resource editor which allows you to
create resources without manually editing this file but sometimes editing it is the
only way to go, especially if your compiler has no visual editor, it sucks, or doesn't
support the exact feature you need.
Unfortunately different compiler suites handle resources differently. I will do the
best I can to explain the common features needed to work with resources in
general.
The resource editor included with MSVC++ makes it very difficult to edit the
resources manually, since it enforces a proprietary format on them, and will totally
mangle the file if you save one that you had created by hand. In general you
shouldn't bother with creating .rc files from scratch, but knowing how to modify
them manually can be very useful. Another annoyance is that MSVC++ will by
default name the resource header file "resource.h" even if you wanted to call it
something else. I will go with this for the sake of simplicity in this document, but will
show you how to change this in the appendix on compilers.
First lets take a very simple resource script, with a single icon.
#include "resource.h"
That's the entire file. IDI_MYICON is the identifier of the resource, ICON is the type and
"my_icon.ico" is the name of the external file which contains it. This should work on any
compiler.
Now what about this #include "resource.h" ? Well your program needs a way to identify the
icon, and the best way to do that is to assign it a unique ID (IDI_MYICON). We can do this by
creating the file "resource.h" and including it in both our resource script, and our source file
As you can see, we've assigned IDI_MYICON the value of 101. We could just forget about the
identifier and use 101 wherever we need to reference the icon, but IDI_MYICON is a lot clearer
P a g e | 23
as to what you are refering too, and easier to remember when you have large number of
resources.
Now lets say we add a MENU resource:
#include "resource.h"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
END
Again IDR_MYMENU is the name of the resource and MENU is the type. Now a fine point, see
the BEGIN and END up there? Some resource editors or compilers use { in place of BEGIN
and } in place of END. If your compiler supports both feel free to pick which one you use. If it
only supports one or the other, you will need to make the necessary replacements to get it to
work.
We've also added a new identifier, ID_FILE_EXIT, so we need to add this to our resource
header file, resource.h, in order to use it in our program.
#define IDI_MYICON 101
Generating and keeping track of all these ids can become a real chore with large projects, that's
why most people use a visual resource editor which takes care of all this for you. They still screw
up from time to time, and you could end up with multiple items with the same ID or a similar
problem, and it's good to be able to go in and fix it yourself.
Now an example of how to use a resource in your program.
HICON hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
The first parameter of LoadIcon() and many other resource using functions is the handle to the
current instance (which we are given in WinMain() and can also be retreived by using
GetModuleHandle() as demonstrated in previous sections). The second is the identifier of the
resource.
You're probably wondering what's up with MAKEINTRESOURCE() and possibly wondering
why LoadIcon() takes a parameter of type LPCTSTR instead of say UINT when we're passing it
an ID. All MAKEINTRESOURCE() does is cast from an integer (what our ID is) to LPCTSTR,
which LoadIcon() expects. This brings us to the second way of identifying resources, and that's
with strings. Almost nobody does this any more, so I won't go into details, but basically if you
don't use #define to assign an integer value to your resources then the name is interpreted as a
string, and can be referenced in your program like this:
HICON hMyIcon = LoadIcon(hInstance, "MYICON");
P a g e | 24
LoadIcon() and other resource loading APIs can tell the difference between an integer passed in
and a pointer to a string passed in by checking the high word of the value. If it's 0 (as would be
the case of any integer with a value less than or equal to 65535) then it assumes it is a resource
ID. This effectively limits your resources to using IDs below 65535, which unless you have a
whole lot of resources, should not be a problem. If it's not 0 then it assumes the value is a
pointer, and looks up the resource by name. Never rely on an API to do this unless it is
explicitely stated in the documentation.
For example, this doesn't work for menu commands like ID_FILE_EXIT, since they can only be
integers.
This is just a small section to show how to add basic menus to your window. Usually you use a
pre-made menu resource. This will be in an .rc file and will be compiled and linked into your
.exe. This is rather compiler specific, commercial compilers will have a resource editor that you
can use to create your menus, but for this example I will show the text of the .rc file so you can
add it in manually. I usually have an .h file as well which is included in both my .rc file and
my .c source files. This file contains the identifiers for controls and menu items etc.
For this example you can start with the window code from simple_window and add this code
into it as instructed.
First the .h file. Usually called "resource.h"
#define IDR_MYMENU 101
#define IDI_MYICON 201
Not much there, but our menu will be pretty simple. The names and values here are up to you for
the choosing. Now we write our .rc file.
#include "resource.h"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
P a g e | 25
END
POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END
END
You will want to add the .rc file to your project or makefile depending on what tools you are
using.
You also want to #include "resource.h" in your source file (.c) so that the menu command
identifiers and the menu resource id will be defined.
The easiest way to attach the menu and icon to your window is to specify them when you register
the window class, like this:
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU);
Change that and see what happens. Your window should now have a File and Stuff menu with
the respective items underneath. That is assuming your .rc file was properly compiled and linked
into your program. (again, see compiler notes)
The icon in the top left of the window and on the task bar should now display the small custom
icon that we specified. If you hit Alt-Tab, the large version of the icon should be displayed in the
application list.
I've used LoadIcon() to load the large icon because it's simpler, however it will only load icons at
the default resolution of 32x32, so in order to load the smaller image, we need to use
LoadImage(). Be aware that icon files and resources can contain multiple images, and in this
case the ones I've supplied contain the two sizes that I'm loading.
Example: menu_two
An alternative to using a menu resource is to create one on the fly (or when your program runs).
This is a bit more work programming wise, but adds flexibility and is sometimes necessary.
You can also use icons that aren't stored as resources, you could choose to store your icon as a
seperate file and load it at runtime. This would also give you the option of allowing the user to
select an icon of their choice with the common dialogs discussed later, or something to that
effect.
Start again from simple_window without the .h or .rc added. Now we will handle the
WM_CREATE message and add a menu to our window
#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
P a g e | 26
Put these two id's at the top of your .c file this time, underneath your #includes. Next we add the
following code into our WM_CREATE handler.
case WM_CREATE:
{
HMENU hMenu, hSubMenu;
HICON hIcon, hIconSm;
hMenu = CreateMenu();
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");
SetMenu(hwnd, hMenu);
This creates a menu almost the same as the one we had in the resource and attaches it to our
window. A menu that is assigned to a window is automatically removed when the program
terminates, so we don't need to worry about getting rid of it later. If we did though, we could use
GetMenu() and DestroyMenu().
The code for the icons is pretty simple, we call LoadImage() twice, to load the icon as both a
16x16 size and a 32x32 size. We can't use LoadIcon() at all because it will only load resources,
not files. We specify NULL for the instance handle parameter because we aren't loading a
resource from our module, and instead of a resource ID we pass in the name of the icon file we
want to load. Finally, we pass in the LR_LOADFROMFILE flag to indicate that we want the
function to treat the string we give it as a filename and not a resource name.
If each call succeeds we assign the icon handle to our window with WM_SETICON, and if it
fails we pop up a message box letting us know something went wrong.
P a g e | 27
NOTE: that the LoadImage() calls will fail if the icon file isn't in the current working directory
of the program. If you are using VC++ and you run the program from the IDE, the current
working directory will be the one the project file is in. However if you run the program from the
Debug or Release directories from explorer or the command shell, then you'll need to copy the
icon file into that directory in order for the program to find it. If all else fails, specify the full path
to the icon, "C:\\Path\\To\\Icon.ico".
Okay now that we have our menu, we need to make it do something. This is pretty simple, all we
need to do is handle the WM_COMMAND message. Also we'll need to check which command
we are getting and act accordingly. Now our WndProc() should look something like this.
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_CREATE:
{
HMENU hMenu, hSubMenu;
hMenu = CreateMenu();
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");
SetMenu(hwnd, hMenu);
break;
P a g e | 28
case ID_STUFF_GO:
break;
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
As you can see we've got our WM_COMMAND all set up, and it even has another switch() in it.
This switch()'s on the value of the low word of wParam, which in the case of WM_COMMAND
contains the control or menu id that sent the message.
We obviously want the Exit menu item to close the program. So in the WM_COMMAND,
ID_FILE_EXIT handler you can use the following code to do just that.
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
}
break;
There's hardly a windows program out there that doesn't use dialog boxes. Just go File -> Open
in any text editor or any other kind of editor for that matter and voila, you are presented with a
dialog box, one that probably allows you to select a file to be opened.
Dialogs aren't limited to the standard open file ones, they can look like and do whatever you
choose. The attractive point of dialogs is that they provide a quick way to arrange and create a
GUI (Graphic User Interface) and even some default processing, cutting down on the amount of
code you must write.
One thing to remember is that dialogs are just windows. The difference between a dialog and a
"normal" window is that the system does some additional default processing for dialogs, such as
creating and initialising controls, and handling tab order. Nearly all APIs that are applicable to
"normal" windows will work just as well on dialogs, and vice versa!
The first step is to create the dialog resource. As with any resource how you do this will depend
on your compiler/IDE. Here I will show you the plain text of the dilaog in the .rc file and let you
incorporate it into your project.
IDD_ABOUT DIALOG DISCARDABLE 0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "&OK",IDOK,174,18,50,14
PUSHBUTTON "&Cancel",IDCANCEL,174,35,50,14
GROUPBOX "About this program...",IDC_STATIC,7,7,225,52
CTEXT "An example program showing how to use Dialog
Boxes\r\n\r\nby theForger",
IDC_STATIC,16,18,144,33
END
On this first line, IDD_ABOUTDLG is the id of the resource. DIALOG is the resource type, and
the four number are the Left, Top, Width and Height co-ordinates. These ARE NOT PIXELS,
they are in Dialog Units, which are based on the size of the font used by the system (and chosen
by the user). If you have a large font selected, the dialog will be large, if you use a smaller font,
the dialog will be that much smaller. This is important as it makes sure that all of the controls are
the proper size to display their text in the current font. You can convert dialog units to pixels at
P a g e | 30
runtime using MapDialogRect(). DISCARDABLE tells the system it may swap the resource
memory to disk when it's not being used in order to conserve system resources (essentially
pointless).
The second line starts with STYLE and follows with the window styles that will be used to
create the dialog. These should be explained under CreateWindow() in your help files. In order
to use the predefined constants you may need to add #include "windows.h" to your .rc file, or in
the case of VC++, winres.h or afxres.h will do. If you use the resource editor these files will
certainly be included automatically if needed.
The CAPTION line should be self explanitory.
The FONT line specifies the size and name of the font you wish to use for this dialog box. This
might not end up exactly the same on each computer as different people will have different fonts
and may have specified different font sizes. You usually don't need to worry about that though.
Now we have the list of controls to create on the dialog
DEFPUSHBUTTON "&OK",IDOK,174,18,50,14
Here's the line for the OK button. The & in this case like with menus underlines the next letter
"O", so that by pressing Alt+O the user can activate this control (part of the default processing I
mentioned). IDOK is the control identifier. IDOK is pre-defined so we don't need to #define it
ourselves. The four numbers at the end are the left, top, width and height, all in dialog units.
This information should be purely academic, as you almost always use a resource editor to create
dialogs, but knowing how to do it from text is sometimes necessary, expecially if you have no
visual editor.
Two of the controls have an ID of IDC_STATIC (which is -1), this is used to indicate we never
need to access them, so they have no need of an identifier. However it doesn't hurt to give them
an ID and your resource editor might do so automatically.
The "\r\n" in the text of the static control is a CR-LF pair, the way windows represents a new
line.
So! Having added that to your .rc file we need to write a Dialog Procedure to process message
for this box. Don't worry this is nothing new, it's practicly the same as our main Window
Procedure (but not exactly).
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
P a g e | 31
break;
default:
return FALSE;
}
return TRUE;
}
There are a few important differences between a dialog procedure and window procedure. One is
that you DO NOT call DefWindowProc() for message you don't handle. With dialogs this is
done automatically for you (and will really screw things up if you do it).
Secondly, in general you return FALSE for messages you don't process, and TRUE for messages
you do process, UNLESS the message specifies you return something else. Note that this is what
we do above, the default is to do nothing and return FALSE, while messages we do handle break
the switch() and return TRUE.
Thirdy, You do not call DestroyWindow() to close a dialog, you call EndDialog(). The second
paramter is the value that is returned to whatever code called DialogBox().
Finally, instead of handling WM_CREATE, you handle WM_INITDIALOG to do any
processing that needs to be done before the dialog appears, and then return TRUE to have the
keyboard focus set to the default control. (You can actually handle WM_CREATE as well, but it
is sent BEFORE any of the controls have been created, so you can't access them. In
WM_INITDIALOG the controls have already been created).
Enough chit-chat, lets create it....
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_HELP_ABOUT:
{
int ret = DialogBox(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
if(ret == IDOK){
MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
MB_OK | MB_ICONINFORMATION);
}
else if(ret == IDCANCEL){
MessageBox(hwnd, "Dialog exited with IDCANCEL.", "Notice",
MB_OK | MB_ICONINFORMATION);
}
else if(ret == -1){
MessageBox(hwnd, "Dialog failed!", "Error",
MB_OK | MB_ICONINFORMATION);
}
}
break;
// Other menu commands...
}
break;
P a g e | 32
This is the code I used to create my about box, you can probably guess that this is to be merged
into your WM_COMMAND handler, if you aren't clear on this aspect, you might want to review
the section on menus. ID_HELP_ABOUT is the identifier of my Help -> About menu item.
Since we want the menu on our main window to create the dialog, we obviously want to put this
code in the WndProc() of our main window, not the dialog proc.
Now I stored the return value from the call to DialogBox(), this is just so you can observe the
effects of pressing the two buttons, hitting Esc, Enter etc... from inside the dialog. It also
illustrates how to use the return value from a dialog box to check for success, failure, a users
choice, or whatever other information you choose to send back to the caller from the Dialog
Procedure.
DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd,
AboutDlgProc);
This is the only important part, and you can choose to put it wherever in your code that you want
the dialog to come up. IDD_ABOUT is the id of the dialog resource. hwnd is the handle to the
parent window of the dialog. AboutDlgProc() is of course the dialog procedure to use to control
the dialog.
That's it! Sit IDD_UBU, sit.
A perticularly astute reader might eventually wonder, if DialogBox() doesn't return untill the
dialog closes we can't process messages while it's up, so how does it work? Well the nifty thing
about DialogBox() is that it has it's own message loop, so while the dialog is displayed, our
message loop is out of the picture and the default loop is handled by windows. This loop also
takes care of fun things like moving the keyboard focus from control to control when you press
Tab.
Another effect of using DialogBox is that your main window is disabled untill the dialog is
dismissed. Sometimes this is what we want, and sometimes it isn't, such as when we want to use
a dialog as a floating toolbar. In this case we want to be able to interact with both out dialog and
our main window, and this will be the focus of the next section.
Modeless Dialogs
Example: dlg_two
P a g e | 33
Now we take a look at CreateDialog(), DialogBox()'s sister function. The difference is that while
DialogBox() implements it's own message loop and does not return untill the dialog is closed,
CreateDialog() acts more like a window created with CreateWindowEx() in that it returns
immediately and depends on your message loop to pump the messages as it does for your main
window. This is termed Modeless, whereas DialogBox() creates Modal dialogs.
You can create the dialog resource just like you did for the last dialog example, you might also
want to set the "Tool window" extended style to give it's title bar the typical smaller caption of
toolbars. The dialog resource I created follows:
IDD_TOOLBAR DIALOGEX 0, 0, 98, 52
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "My Dialog Toolbar"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "&Press This Button",IDC_PRESS,7,7,84,14
PUSHBUTTON "&Or This One",IDC_OTHER,7,31,84,14
END
You may notice that the resource editor has replaced DIALOG with DIALOGEX indicating we
want to set an EXSTYLE on our dialog.
Next we want to create the dialog when our program runs, I want the dialog visible right away so
we do this in WM_CREATE. We also want to declare a global variable to hold the window
handle returned from CreateDialog() so that we can use it later. DialogBox() didn't return a
handle to us since when DialogBox() returns the window has been destroyed.
HWND g_hToolbar = NULL;
case WM_CREATE:
g_hToolbar = CreateDialog(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_TOOLBAR),
hwnd, ToolDlgProc);
if(g_hToolbar != NULL)
{
ShowWindow(g_hToolbar, SW_SHOW);
}
else
{
MessageBox(hwnd, "CreateDialog returned NULL", "Warning!",
MB_OK | MB_ICONINFORMATION);
}
break;
We check the return value, which is ALWAYS a good idea, and if it's valid (not NULL) we
show the window with ShowWindow(), with DialogBox() this isn't necessary since the system
calls ShowWindow() for us.
Now we need a dialog procedure for our toolbar.
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
P a g e | 34
switch(Message)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_PRESS:
MessageBox(hwnd, "Hi!", "This is a message",
MB_OK | MB_ICONEXCLAMATION);
break;
case IDC_OTHER:
MessageBox(hwnd, "Bye!", "This is also a message",
MB_OK | MB_ICONEXCLAMATION);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
Most of the same message handling rules apply to dialogs created with CreateDialog() as with
DialogBox(), don't call DefWindowProc(), return FALSE for messages you don't handle and
TRUE for those you do.
One change is that we don't call EndDialog() for modeless dialogs, we can use DestroyWindow()
just like for regular windows. In this case I destroy the dialog when the main window is
destroyed. In the main window's WndProc()...
case WM_DESTROY:
DestroyWindow(g_hToolbar);
PostQuitMessage(0);
break;
Last but not least, we want to be able to display and hide our toolbar whenever we choose so I've
added two commands to my menu to do this, and handled them so:
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_DIALOG_SHOW:
ShowWindow(g_hToolbar, SW_SHOW);
break;
case ID_DIALOG_HIDE:
ShowWindow(g_hToolbar, SW_HIDE);
break;
//... other command handlers
}
break;
You should be able to create your own menu using the resource editor or manually, but if not (as
always) take a look at the example project dlg_two provided with the tutorial.
P a g e | 35
Now when you run the program, you should be able to access both the dialog window, and main
window at the same time.
If you've run the program at this point and tried tabbing between the two buttons, you have
probably noticed it doesn't work, neither does hitting Alt-P or Alt-O to activate the buttons. Why
not? Whereas DialogBox() implements it's own message loop and handles these events by
default, CreateDialog() does not. We can do it ourselves though, by calling IsDialogMessage() in
our message loop which will do the default processing for us.
while(GetMessage(&Msg, NULL, 0, 0))
{
if(!IsDialogMessage(g_hToolbar, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
Here we first pass the message to IsDialogMessage(), if the message is destined for our toolbar
(indicated by the window handle we pass in) the system will perform the default processing and
return TRUE. Is this case the message has already been handled so we don't want to call
TranslateMessage() or DispatchMessage(). If the message is for another window we process as
usual.
It's also worth noting that IsDialogMessage() can also be used with windows that aren't dialogs
in order to to give them dialog-like behaviour. Remember, a dialog is a window, and most (if not
all) dialog APIs will work on any window.
And that is pretty much all there is to modeless dialogs! One issue that may arise is if you have
more than one toolbar... what do you do? Well one possible solution is to have a list (either an
array, an STL std::list, or similar) and loop through it in your message loop passing each handle
to IsDialogMessage() until the right one is found, and if none, do the regular processing. This is a
generic programming problem, not one that is Win32 related, and is left as an excersize to the
reader.
I realize I've already used buttons in previous examples, so you should already be more or less
familiar with them, however I figured that since I was using them in this example I might as well
add it to the title for the sake of being complete.
Controls
One thing to remember about controls is that they are just windows. Like any other window
they have a window procedure, a window class etc... that is registered by the system. Anything
you can do with a normal window you can do with a control.
Messages
As you may remember from our earlier discussion of the message loop, windows communicate
using messages, you send them to get a control to do something, and when an event occurs on
the control it will send you a notification message back. For the standard controls this
notification will be a WM_COMMAND message as we've already seen with buttons and menus. For
the Common Controls which I may get to later, it will be WM_NOTIFY.
The messages you send are widely varied between each control, and each control has it's own set
of messages. Once in a while the same message will be used for more than one kind of control,
but in general they will only work on the control they are intended for. This is especially
annoying with the listbox and combobox messages (LB_* and CB_*) which although they
perform nearly identical tasks, are NOT interchangeable, and I accidently get them mixed up
more than I'd like to admit :)
On the other hand, generic messages like WM_SETTEXT are supported by almost all controls. A
control is just a window after all.
You can send messages using the SendMessage() API, and use GetDlgItem() to retreive the
handle to the control, or you can use SendDlgItemMessage() which does both steps for you, the
results of both methods are identical.
P a g e | 37
Edits
One of the most commonly used controls in the windows environment, the EDIT control, is used
to allow the user to enter, modify, copy, etc... text. Windows Notepad is little more than a plain
old window with a big edit control inside it.
Here is the code used to interface with the edit control in this example:
SetDlgItemText(hwnd, IDC_TEXT, "This is a string");
That's all it takes to change the text contained in the control (this can be used for pretty much any
control that has a text value associated with it, STATICs, BUTTONs and so on).
Retreiving the text from the control is easy as well, although slightly more work than setting it...
GlobalFree((HANDLE)buf);
}
First of all, we need to allocate some memory to store the string in, it won't just return us a
pointer to the string already in memory. In order to do this, we first need to know how much
memory to allocate. There isn't a GetDlgItemTextLength(), but there is a
GetWindowTextLength(), so all we need to do it get the handle to the control yourself using
GetDlgItem().
Now that we have the length, we can allocate some memory. Here I've added a check to see if
there is any text to begin with, since most likely you don't want to be working with an empty
string... sometimes you might, but that's up to you. Assuming that there is something there to
work with, we call GlobalAlloc() to allocate some memory. GlobalAlloc() as I've used it
here is equivalent to calloc(), if you're used to DOS/UNIX coding. It allocates some memory,
initializes it's contents to 0 and returns a pointer to that memory. There are different flags you
can pass as the first paramter to make it behave differently for different purposes, but this is the
only way I will be using it in this tutorial.
Note that I added 1 to the length in two places, what's up with that? Well,
GetWindowTextLength() returns the number of characters of text the control contains NOT
INCLUDING the null terminator. This means that if we were to allocate a string without adding
1, the text would fit, but the null terminator would overflow the memory block, possibly
corrupting other data, causing an access violation, or any number of other bad things. You must
be careful when dealing with string sizes in windows, some APIs and messages expect text
lengths to include the null and others don't, always read the docs thoroughly.
P a g e | 38
If I lost you talking about null terminators, please refer to a basic C book or tutorial which
discusses strings.
Finally we can call GetDlgItemText() to retrieve the contents of the control into the memory
buffer that we've just allocated. This call expects the size of the buffer INCLUDING the null
terminator. The return value, which we ignored here, is the number of characters copied, NOT
including the null terminator.... fun eh? :)
After we're all done using the text (which we'll get to in a moment), we need to free up the
memory that we allocated so that it doesn't leak out and drip down onto the CPU and short
circuit your computer. To accomplish this, we simply call GlobalFree() and pass in our pointer.
You may be or become aware of a second set of APIs named LocalAlloc(), LocalFree(),
etc... which are legacy APIs from 16-bit windows. In Win32, the Local* and Global* memory
functions are identical.
Edits with Numbers
Entering text is all well and fine, but what if you want the user to enter in a number? This is a
pretty common task, and fortunately there is an API to make this simpler, which takes care of all
the memory allocation, as well as converting the string to an integer value.
BOOL bSuccess;
int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);
GetDlgItemInt() works much like GetDlgItemText(), except that instead of copying the
string to a buffer, it converts it internally into an integer and returns the value to you. The third
parameter is optional, and takes a pointer to a BOOL. Since the function returns 0 on failure, there
is no way to tell just from that whether or not the function failed or the user just entered 0. If you
are fine with a value of 0 in the event of an error, then feel free to ignore this parameter.
Another useful feature is the ES_NUMBER style for edit controls, which allows only the characters
0 through 9 to be entered. This is very handy if you only want positive integers, otherwise it's not
much good, since you can't enter any other characters, including - (minus) . (decimel) or ,
(comma).
List Boxes
Another handy control is the list box. This is the last standard control that I'm going to cover for
now, cause frankly they aren't that interesting, and if you aren't bored yet well, I am :)
Adding Items
The first thing you'll want to do with a listbox is add items to it.
int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0,
(LPARAM)"Hi there!");
As you can see, this is a pretty simple task. If the listbox has the LBS_SORT style, the
new item will be added in alphabetical order, otherwise it will just be added to the
end of the list.
This message returns the index of the new item either way, and we can use this to perform other
tasks on the item, such as associating some data with it. Usually this will be things like a pointer
to a struct containing more information, or maybe an ID that you will use to identify the item, it's
up to you.
P a g e | 39
Notifications
The whole purpose of listboxes is to allow the user to select things from a list. Now sometimes
we don't care when exactly they do this, for example with our Remove button, we don't need to
know when the selection changes right away, we just check when the user activates the button.
However, sometimes you want to be able to do something right away, perhaps display different
or updated information based on what items are selected. In order to do this we need to handle
the notification messages that the listbox passes to us. In this case, we are interested in
LBN_SELCHANGE, which tells us that the selection has been modified by the user. LBN_SELCHANGE
is sent via WM_COMMAND but unlike handling the WM_COMMAND from buttons or menu's, which are
usually only in response to a click, a list box sends WM_COMMAND for various reasons, and we need
a second check to find out what it's telling us. The Notification Code is passed as the HIWORD of
wParam, the other half of the parameter that gave us the control ID in the first place.
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_LIST:
// It's our listbox, check the notification code
switch(HIWORD(wParam))
{
case LBN_SELCHANGE:
// Selection changed, do stuff here.
break;
}
break;
// ... other controls
}
break;
In this example I've used a multiselection list box, so getting the list of selected items is a little
trickier. If it were a single selection listbox, than you could simply send LB_GETCURSEL to
retrieve the item index.
First we need to get the number of selected items, so that we can allocate a buffer to save the
indexes in.
HWND hList = GetDlgItem(hwnd, IDC_LIST);
int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);
Then we allocate a buffer based on the number of items, and send LB_GETSELITEMS to fill in the
array.
int *buf = GlobalAlloc(GPTR, sizeof(int) * count);
SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf);
GlobalFree(buf);
In this example, buf[0] is the first index, and so on up to buf[count - 1].
One of the things you would likely want to do with this list of indexes, is retreive the data
associated with each item, and do some processing with it. This is just as simple as setting the
data was originally, we just send another message.
int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);
If the data was some other type of value (anything that is 32-bits) you could simply cast to the
appropriate type. For example if you stored HBITMAPs instead of ints...
HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA,
(WPARAM)index, 0);
Statics
Like buttons, static controls are largely trivial, but for the sake or being complete I
include them here. Static controls are usually just that, static, meaning they don't
change or really do anything else very special, they're largely for displaying text to
the user. However you can make them slightly more useful by assigning them a
unique ID (VC++ assigns a default ID of IDC_STATIC, which is -1 and effectively
means "No ID") and then setting the text at runtime to present dynamic data to the
user.
In the example code, I use one to display the data of the item selected in the list box, assuming
one and only one is selected.
P a g e | 41