Win32 API Tutorials
Win32 API Tutorials
7
What this tutorial is all about ................................................................................................. 7
Important notes ........................................................................................................................ 7
Names of WindowsParts.......................................................................................................... 8
The simplest Win32 program ............................................................................................... 10
About the simplest Win32 program ..................................................................................... 13
Calling Conventions............................................................................................................. 13
Hugarian Notation............................................................................................................ 13
Systems vs. Apps Hungarian ........................................................................................... 14
Examples .............................................................................................................................. 15
Advantages of Hungarian notation ...................................................................................... 16
Disadvantages of Hungarian notation .................................................................................. 17
Win32 Data Types .................................................................................................................. 18
Modify the above program as follows ..................................................................................... 18
Question ........................................................................................................................... 19
2. Application Creation ........................................................................................................ 20
The Main Window Class ..................................................................................................... 21
The Size of the Window Class ............................................................................................. 22
Additional Memory Request ................................................................................................ 23
The Application's Instance ................................................................................................... 23
Window Extra-Memory ....................................................................................................... 23
The Main Window's Style.................................................................................................... 24
Window Styles ................................................................................................................. 25
Message Processing ............................................................................................................. 26
The Application Main Icon .................................................................................................. 26
Introduction to Cursors ........................................................................................................ 28
The Window's Background Color ....................................................................................... 31
The Application's Main Menu ............................................................................................. 32
3. Finalizing an Application ................................................................................................. 33
Step 1: Registering the Window Class................................................................................. 33
MB_ICONEXCLAMATION | MB_OK); .............................................................................. 34
Step 2: Creating the Window ............................................................................................... 34
WS_EX_CLIENTEDGE, ......................................................................................................... 34
MB_ICONEXCLAMATION | MB_OK); .............................................................................. 36
Step 3: The Message Loop ................................................................................................... 36
Windows Messages.......................................................................................................... 37
Message Types ................................................................................................................. 37
Message Routing.............................................................................................................. 40
Message Handling................................................................................................................ 41
Message Loop .................................................................................................................. 42
Step 4: the Window Procedure ............................................................................................ 42
The Complete Program ........................................................................................................ 44
Step 5: Assignment 2 to see if you can visualized ―a little‖ on what is going on................ 47
Question ............................................................................................................................... 47
4. Handling More Messages ................................................................................................ 48
Button click Example: window_click ................................................................................... 49
QUESTIONS ....................................................................................................................... 55
PAINTSTRUCT ps;.............................................................................................................. 55
HDC hdc; .............................................................................................................................. 55
Assignment 2b ..................................................................................................................... 55
5. Understanding the Message Loop.................................................................................... 56
What is a Message? .............................................................................................................. 56
Structure of message ............................................................................................................ 56
Message: WM_COMMAND ................................................................................................. 57
Message: WM_KEYDOWN ................................................................................................. 57
Message: WM_MOUSEMOVE ...................................................................................... 58
Messages: WM_LBUTTONDOWN, WM_RBUTTONDOWN ..................................... 59
Messages: WM_LBUTTONUP, WM_RBUTTONUP ................................................................. 59
Posting message manually ................................................................................................... 59
Message that must be processed .......................................................................................... 59
InvalidateRect(hWnd,NULL,TRUE); ....................................................................... 59
Dialogs ................................................................................................................................. 60
What is the Message Queue ................................................................................................. 60
What is a Message Loop ...................................................................................................... 60
QUESTIONS ....................................................................................................................... 61
In the following code ............................................................................................................... 61
6. Windows Messages.......................................................................................................... 62
Classes Type: ....................................................................................................................... 62
Classes Message: ................................................................................................................. 62
7. Message Boxes................................................................................................................. 64
8................................................................................................................................................ 65
Message Box Creation ......................................................................................................... 65
Practical Learning: Introducing Additional Resources ........................................................ 68
9. Modal and Modeless Windows Forms ............................................................................ 69
10. Resources Fundamentals ............................................................................................... 69
Introduction .......................................................................................................................... 70
Resource Creation ................................................................................................................ 70
11. Component of Resource Script ..................................................................................... 71
Identifiers ............................................................................................................................. 71
DISCARDABLE.................................................................................................................. 71
Icons ..................................................................................................................................... 71
Bitmaps ................................................................................................................................ 72
Mouse Cursors ..................................................................................................................... 74
String Tables ........................................................................................................................ 74
Accelerators ......................................................................................................................... 75
Menus................................................................................................................................... 77
Version Information ............................................................................................................. 78
Dialog Boxes ........................................................................................................................ 78
12. Resource-Definition Statements ................................................................................... 80
Resources ............................................................................................................................. 80
Controls ................................................................................................................................ 81
Statements ............................................................................................................................ 82
13. Using Resources............................................................................................................ 83
Creating resource ................................................................................................................... 83
14. Menus and Icons using resource ................................................................................... 86
Creating menu using resource script. ................................................................................... 86
Defining id numbers ........................................................................................................ 87
Processing the Messages generated by menu resources. ................................................. 88
Creating menu using child process. ..................................................................................... 88
Example: menu_two ............................................................................................................ 88
QUESTIONS ....................................................................................................................... 95
15. Dialogs Box Using Resource ........................................................................................ 96
16. Modeless Dialogs Using Resource ............................................................................. 104
17. Windows Controls ...................................................................................................... 107
Button Control Window Class ........................................................................................... 109
Styles .............................................................................................................................. 109
Constant Definitions ...................................................................................................... 111
Combo Box Control Window Class .................................................................................. 112
Styles .............................................................................................................................. 112
Constant Definitions ...................................................................................................... 113
Edit Control Window Class ............................................................................................... 113
Styles .............................................................................................................................. 113
Constant Definitions ...................................................................................................... 114
IP Address Control's Window Class .................................................................................. 115
Styles .............................................................................................................................. 115
List Box Control Window Class ........................................................................................ 115
Styles .............................................................................................................................. 115
Constant Definitions ...................................................................................................... 116
Scroll Bar Control Window Class...................................................................................... 117
Styles .............................................................................................................................. 117
Constant Definitions ...................................................................................................... 118
Static Control Window Class ............................................................................................. 118
Styles .............................................................................................................................. 118
Constant Definitions ...................................................................................................... 120
18. Styles Common to All Classes.................................................................................... 121
Base Window Styles .......................................................................................................... 121
Styles .............................................................................................................................. 121
Constant Definitions ...................................................................................................... 122
Extended Window Styles ................................................................................................... 122
Extended Styles.............................................................................................................. 122
Constant Definitions ...................................................................................................... 124
19. App Part 1: Creating controls at runtime .................................................................... 125
Example: app_one .............................................................................................................. 125
Static control ...................................................................................................................... 125
Button................................................................................................................................. 127
Check box .......................................................................................................................... 128
The SetWindowText() function sets the title of the window. ................................................ 130
Edit Control........................................................................................................................ 130
Radio buttons and GroupBox............................................................................................. 132
ComboBox ......................................................................................................................... 135
Progress bar........................................................................................................................ 137
20. App Part 2: Using files and the common dialogs ....................................................... 140
The Open File Dialogs ....................................................................................................... 141
Reading and Writing Files ................................................................................................. 142
Reading .......................................................................................................................... 143
Writing ........................................................................................................................... 144
The full program .................................................................................................................... 145
ListBox............................................................................................................................... 150
21. Creating Custom Controls........................................................................................... 153
The Burning control ........................................................................................................... 153
22. The GDI ...................................................................................................................... 156
Rectangle............................................................................................................................ 158
Pen...................................................................................................................................... 159
Brush .................................................................................................................................. 161
Hatch brushes ..................................................................................................................... 163
Shapes ................................................................................................................................ 165
Fonts................................................................................................................................... 166
23. Timers and Animation ................................................................................................ 169
Setting up ........................................................................................................................... 169
Setting the Timer................................................................................................................ 170
Animating in WM_TIMER................................................................................................ 171
Double Buffering ............................................................................................................... 172
Faster Double Buffering ................................................................................................ 173
Killing the Timer................................................................................................................ 173
Loading Fonts .................................................................................................................... 173
Default Fonts.................................................................................................................. 174
Drawing Text ..................................................................................................................... 175
Choosing Fonts .................................................................................................................. 176
Choosing Colours............................................................................................................... 177
Control Fonts ..................................................................................................................... 178
Books ................................................................................................................................. 178
Links .................................................................................................................................. 179
Getting It ........................................................................................................................... 179
Download Free VC++ 2008 ................................................................................................. 179
24. Solutions to Common Errors ...................................................................................... 179
Error LNK2001: unresolved external symbol _main......................................................... 180
Fixing ............................................................................................................................. 180
Error C2440: cannot convert from 'void*' to 'HICON__ *' (or similar) ............................ 180
Fixing ............................................................................................................................. 180
Fatal error RC1015: cannot open include file 'afxres.h'..................................................... 181
Error LNK2001: unresolved external symbol InitCommonControls ................................ 181
Dialog does not display when certain controls are added .................................................. 181
25. Why you should learn the API before MFC ............................................................... 182
The Controversy................................................................................................................. 182
My Answer......................................................................................................................... 182
So basically... ..................................................................................................................... 183
1. Getting Started
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.
Important notes
Sometimes throughout the text I will indicate certain things are IMPORANT to read. Because
they screw up so many people, if you don't read it, you'll likely get caught too. The first one
is this:
The source provided in the example ZIP file is not optional! I don't include all the code in
the text itself, only that which is relevant to whatever I'm currently discussing. In order to see
how this code fits in with the rest of the program, you must take a look at the source provided
in the ZIP file.
Read the whole thing! If you have a question during one section of the tutorial just have a
little patience and it might just be answered later on. If you just can't stand the thought of not
knowing, at least skim or search (yes computers can do that) the rest of the document before
asking the nice folks on IRC or by email.
Another thing to remember is that a question you might have about subject A might end up
being answered in a discussion of B or C, or maybe L. So just look around a little.
Ok I think that's all the ranting I have to do for the moment, lets try some actual code.
Names of WindowsParts
The simplest Win32 program
If you are a complete beginner lets make sure you are capable of compiling a basic windows
application. Slap the following code into your compiler and if all goes well you should get
one of the lamest programs ever written.
Remember to compile this as C, not C++. It probably doesn't matter, but since all the code
here is C only, it makes sense to start off on the right track. In most cases, all this requires if
you add your code to a .c file instead of a .cpp file. If all of this hurts your head, just call the
file test.c and be done with it.
#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.
Before you can solve the error you must know about the MessageBox function.
This error occur because MessageBox by definition support Unicode data while the string
constant "Goodbye, cruel world!" and "Note" are ANSI data.
Select Project|Test Properties and in the test Property Pages|Character Set select Use Muti-
Byte Character Set.
Solution #1
Solution #2
#include <windows.h>
Solution #4
#include <windows.h>
#define MessageBox MessageBoxA
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox (NULL,"Goodbye, cruel world!", "Note", MB_OK);
return 0;
}
How to fix them vary from compiler to compiler (and person to person). While you fix error
you be more literate on the language and the windows system. Use MSDN to get help and be
more skilful in Windows API and C++ programming.
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, let’s go through that
little bit of code....
About the simplest Win32 program
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
WinMain() is windows equivalent of main() from DOS or UNIX. This is where your
program starts execution. The parameters are as follows:
HINSTANCE hInstance
Handle to the programs executable module (the .exe file in memory)
HINSTANCE hPrevInstance
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.
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.
Hugarian Notation
Hungarian notation was designed to be language-independent, and found its first major use
with the BCPL programming language. Because BCPL has no data types other than the
machine word, nothing in the language itself helps a programmer remember variables' types.
Hungarian notation aims to remedy this by providing the programmer with explicit
knowledge of each variable's data type.
In Hungarian notation, a variable name starts with a group of lower-case letters which are
mnemonics for the type or purpose of that variable, followed by whatever the name the
programmer has chosen; this last part is sometimes distinguished as the given name. The first
character of the given name can be capitalised to separate it from the type indicators (see also
CamelCase). Otherwise the case of this character denotes scope.
Where Systems notation and Apps notation differ is in the purpose of the prefixes.
In Systems Hungarian notation, the prefix encodes the actual data type of the variable. For
example:
f: Flag (Boolean, logical). If qualifier is used, it should describe the true state of the
flag. Exception: the constants fTrue and fFalse.
w: Word with arbitrary contents.
lp: long integer pointer
wcx: WNDCLASSEX structure.
ch: Character, usually in ASCII text.
b: Byte, not necessarily holding a coded character, more akin to w. Distinguished
from the b constructor by the capital letter of the qualifier in immediately
following.
sz: Pointer to first character of a zero terminated string.
st: Pointer to a string. First byte is the count of characters cch.
Hpp: heap structure
u: unsigned integer
Example:
lAccountNum : variable is a long integer ("l");
arru8NumberList : variable is an array of unsigned 8-bit integers ("arru8");
szName : variable is a zero-terminated string ("sz"); this was one of Simonyi's original
suggested prefixes.
hWnd: variable is a handle
lpText: long integer pointer to a string
lpCaption: long integer pointer to a string
uType: using integer
Apps Hungarian notation doesn't encode the actual data type, but rather, it gives a hint as to
what the variable's purpose is, or what it represents.
Most, but not all, of the prefixes Simonyi suggested are semantic in nature. The following are
examples from the original paper: [1]
While the notation always uses initial lower-case letters as mnemonics, it does not prescribe
the mnemonics themselves. There are several widely used conventions (see examples below),
but any set of letters can be used, as long as they are consistent within a given body of code.
It is possible for code using Apps Hungarian notation to sometimes contain Systems
Hungarian when describing variables that are defined solely in terms of their type.
Examples
bBusy : boolean
cApples : count of items
dwLightYears : double word (systems)
fBusy : boolean (flag)
nSize : integer (systems) or count (application)
iSize : integer (systems) or index (application)
fpPrice: floating-point
dbPi : double (systems)
pFoo : pointer
rgStudents : array, or range
szLastName : zero-terminated string
u32Identifier : unsigned 32-bit integer (systems)
stTime : clock time structure
fnFunction : function name
The mnemonics for pointers and arrays, which are not actual data types, are usually followed
by the type of the data element itself:
While Hungarian notation can be applied to any programming language and environment, it
was widely adopted by Microsoft for use with the C language, in particular for Microsoft
Windows, and its use remains largely confined to that area. In particular, use of Hungarian
notation was widely evangelized by Charles Petzold's "Programming Windows", the original
(and for many readers, the definitive) book on Windows API programming. Thus, many
commonly-seen constructs of Hungarian notation are specific to Windows:
The naming convention guidelines for .NET Framework, Microsoft's more recent software
development platform, advise that Hungarian notation should not be used.[2]
The notation is sometimes extended in C++ to include the scope of a variable, separated by
an underscore. This extension is often also used without the Hungarian type-specification:
heightWindow = window.getWidth()
The Hungarian notation is redundant when type checking is done by the compiler.
Compilers for languages providing type checking ensure the usage of a variable is
consistent with its type automatically; checks by eye are redundant and subject to
human error.
Some modern Integrated development environments display variable types on
demand, and automatically flag operations which use incompatible types, making the
notation largely obsolete.
Hungarian Notation becomes confusing when it is used to represent several
properties, as in a_crszkvc30LastNameCol: a constant reference argument, holding
the contents of a database column LastName of type varchar(30) which is part of the
table's primary key.
It may lead to inconsistency when code is modified or ported. If a variable's type is
changed, either the decoration on the name of the variable will be inconsistent with
the new type, or the variable's name must be changed. A particularly well known
example is the standard WPARAM type, and the accompanying wParam formal
parameter in many Windows system function declarations. The 'w' stands for 'word',
where 'word' is the native word size of the platform's hardware architecture. It was
originally a 16 bit type on 16-bit word architectures, but was changed to a 32-bit on
32-bit word architectures, or 64-bit type on 64-bit word architectures in later versions
of the operating system while retaining its original name (its true underlying type is
UINT_PTR, that is, an unsigned integer large enough to hold a pointer). The semantic
impedance, and hence programmer confusion and inconsistency from platform-to-
platform, is on the assumption that 'w' stands for 16-bit in those different
environments.
Most of the time, knowing the use of a variable implies knowing its type.
Furthermore, if you don't know what a variable is used for, knowing its type won't
help you.
You will find that many of the normal keywords or types have windows specific definitions,
UINT for unsigned int, LPSTR for char* etc... Which you choose is really up to you. If you
are more comfortable using char* instead of LPSTR, feel free to do so. Just make sure that
you know what a type is before you substitute something else.
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.
You might also see a T mixed in there. Don't worry about this for now, unless you are
intentionally working with Unicode, it means nothing.
#include <windows.h>
#define MessageBox MessageBoxA
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox (NULL,lpCmdLine, "SEL 4263", MB_YESNOCANCEL);
return 0;
}
Build the program and resolve any errors. The Open a command prompt window at the
directory where the executable file of the successfully compiled program stored
(\C:\Users\UserName\Documents\Visual Studio 2008\Projects\testcmdline for Windows
Vista).
Run the program at the command prompt with a parameter Computer System &
Multimedia as above. The output will be as follow.
Question
// SimpleWindow.cpp
#include <windows.h>
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
else
{
MessageBox(NULL, "Window Registration Succeed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
}
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.
There are two primary things you must do in order to create even the simplest window: you must create
the central point of the program, and you must tell the operating system
Just like a C++ program always has a main() function, a Win32 program needs a central function call
WinMain. The syntax of that function is:
Unlike the C++ main() function, the arguments of the WinMain() function are not optional. Your
program will need them to communicate with the operating system.
The first argument, hInstance, is a handle to the instance of the program you are writing.
The second argument, hPrevInstance, is used if your program had any previous instance. If not, this
argument can be ignored, which will always be the case.
The third argument, lpCmdLine, is a string that represents all items used on the command line to
compile the application.
The last argument, nCmdShow, controls how the window you are building will be displayed.
An object that displays on your screen is called a window. Because there can be various types of
windows in your programs, your first responsibility is to control them, know where they are, what they
are doing, why, and when. The first control you must exercise on these different windows is to host
them so that all windows of your program belong to an entity called the main window. This main
window is created using an object that can be called a class (strictly, a structure).
The Win32 library provides two classes for creating the main window and you can use any one of
them. They are WNDCLASS and WNDCLASSEX. The second adds only a slight feature to the first.
Therefore, we will mostly use the WNDCLASSEX structure for our lessons.
To create a window, you must "fill out" this class, which means you must provide a value for each of
its members so the operating system would know what your program is expected to do.
The first thing you must do in order to create an application is to declare a variable of either
WNDCLASS or WNDCLASSEX type. Here is an example of a WNDCLASSEX variable:
return 0;
}
After declaring a WNDCLASSEX variable, you must specify its size. This is done by initializing your
variable with the sizeof operator applied to the window class as follows:
WndClsEx.cbSize = sizeof(WNDCLASSEX);
return 0;
}
Upon declaring a WNDCLASSEX variable, the compiler allocates an amount of memory space for it,
as it does for all other variables. If you think you will need more memory than allocated, assign the
number of extra bytes to the cbClsExtra member variable. Otherwise, the compiler initializes this
variable to 0. If you do not need extra memory for your WNDCLASSEX variable, initialize this
member with 0. Otherwise, you can do it as follows:
WndClsEx.cbSize = sizeof(WNDCLASSEX);
WndClsEx.cbClsExtra = 0;
return 0;
}
Creating an application is equivalent to creating an instance for it. To communicate to the WinMain()
function that you want to create an instance for your application, which is, to make it available as a
resource, assign the WinMain()'s hInstance argument to your WNDCLASS variable:
WndClsEx.cbSize = sizeof(WNDCLASSEX);
WndClsEx.cbClsExtra = 0;
WndClsEx.hInstance = hInstance;
return 0;
}
Window Extra-Memory
When an application has been launched and is displaying on the screen, which means an instance of the
application has been created, the operating system allocates an amount of memory space for that
application to use. If you think that your application's instance will need more memory than that, you
can request that extra memory bytes be allocated to it. Otherwise, you can let the operating system
handle this instance memory issue and initialize the cbWndExtra member variable to 0:
WndClsEx.cbSize = sizeof(WNDCLASSEX);
WndClsEx.cbClsExtra = 0;
WndClsEx.cbWndExtra = 0;
WndClsEx.hInstance = hInstance;
return 0;
}
The style member variable specifies the primary operations applied on the window class. The actual
available styles are constant values. For example, if a user moves a window or changes its size, you
would need the window to be redrawn to get its previous characteristics. To redraw the window
horizontally, you would apply the CS_HREDRAW. In the same way, to redraw the window vertically,
you can apply the CS_VREDRAW.
The styles are combined using the bitwise OR (|) operator. The CS_HREDRAW and the
CS_VREDRAW styles can be combined and assigned to the style member variable as follows:
WndClsEx.cbSize = sizeof(WNDCLASSEX);
WndClsEx.style = CS_HREDRAW | CS_VREDRAW;
WndClsEx.cbClsExtra = 0;
WndClsEx.cbWndExtra = 0;
WndClsEx.hInstance = hInstance;
return 0;
}
Window Styles
WS_BORDER Creates a window that has a border.
WS_CAPTION Creates a window that has a title bar (implies the WS_BORDER style). Cannot be
used with the WS_DLGFRAME style.
WS_CHILD Creates a child window. Cannot be used with the WS_POPUP style.
WS_CHILDWINDOW Same as the WS_CHILD style.
WS_CLIPCHILDREN Excludes the area occupied by child windows when you draw within the
parent window. Used when you create the parent window.
WS_CLIPSIBLINGS Clips child windows relative to each other; that is, when a particular child
window receives a paint message, the WS_CLIPSIBLINGS style clips all other overlapped child
windows out of the region of the child window to be updated. (If WS_CLIPSIBLINGS is not given
and child windows overlap, when you draw within the client area of a child window, it is possible to
draw within the client area of a neighboring child window.) For use with the WS_CHILD style only.
WS_DISABLED Creates a window that is initially disabled.
WS_DLGFRAME Creates a window with a double border but no title.
WS_GROUP Specifies the first control of a group of controls in which the user can move from one
control to the next with the arrow keys. All controls defined with the WS_GROUP style FALSE after
the first control belong to the same group. The next control with the WS_GROUP style starts the
next group (that is, one group ends where the next begins).
WS_HSCROLL Creates a window that has a horizontal scroll bar.
WS_ICONIC Creates a window that is initially minimized. Same as the WS_MINIMIZE style.
WS_MAXIMIZE Creates a window of maximum size.
WS_MAXIMIZEBOX Creates a window that has a Maximize button.
WS_MINIMIZE Creates a window that is initially minimized. For use with the WS_OVERLAPPED
style only.
WS_MINIMIZEBOX Creates a window that has a Minimize button.
WS_OVERLAPPED Creates an overlapped window. An overlapped window usually has a caption
and a border.
WS_OVERLAPPEDWINDOW Creates an overlapped window with the WS_OVERLAPPED,
WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and
WS_MAXIMIZEBOX styles.
WS_POPUP Creates a pop-up window. Cannot be used with the WS_CHILD style.
WS_POPUPWINDOW Creates a pop-up window with the WS_BORDER, WS_POPUP, and
WS_SYSMENU styles. The WS_CAPTION style must be combined with the WS_POPUPWINDOW
style to make the Control menu visible.
WS_SIZEBOX Creates a window that has a sizing border. Same as the WS_THICKFRAME style.
WS_SYSMENU Creates a window that has a Control-menu box in its title bar. Used only for
windows with title bars.
WS_TABSTOP Specifies one of any number of controls through which the user can move by using
the TAB key. The TAB key moves the user to the next control specified by the WS_TABSTOP style.
WS_THICKFRAME Creates a window with a thick frame that can be used to size the window.
WS_TILED Creates an overlapped window. An overlapped window has a title bar and a border.
Same as the WS_OVERLAPPED style.
WS_TILEDWINDOW Creates an overlapped window with the WS_OVERLAPPED, WS_CAPTION,
WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX styles. Same
as the WS_OVERLAPPEDWINDOW style.
WS_VISIBLE Creates a window that is initially visible.
WS_VSCROLL Creates a window that has a vertical scroll bar.
Message Processing
The name of the window procedure we reviewed in the previous lesson must be assigned to the
lpfnWndProc member variable of the WNDCLASS or WNDCLASSEX variable. This can be
defined as follows:
#include <windows.h>
WndClsEx.cbSize = sizeof(WNDCLASSEX);
WndClsEx.style = CS_HREDRAW | CS_VREDRAW;
WndClsEx.lpfnWndProc = WndProcedure;
WndClsEx.cbClsExtra = 0;
WndClsEx.cbWndExtra = 0;
WndClsEx.hInstance = hInstance;
return 0;
}
LRESULT CALLBACK WndProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
The hInstance argument is a handle to the file in which the icon was created. This file is usually
stored in a library (DLL) of an executable program. If the icon was created as part of your
application, you can use the hInstance of your application. If your are using one of the icons below,
set this argument to NULL.
The lpIconName is the name of the icon to be loaded. This name is added to the resource file when
you create the icon resource. It is added automatically if you add the icon as part of your resources;
otherwise you can add it manually when creating your resource script. Normally, if you had created
and designed an icon and gave it an identifier, you can pass it using the MAKEINTRESOURCE
macro.
To make your programming a little faster, Microsoft Windows installs a few icons you can use for
your application. These icons have identification names that you can pass to the LoadIcon() function
as the lpIconName argument. The icons are:
ID Picture
IDI_APPLICATION
IDI_INFORMATION
IDI_ASTERISK
IDI_QUESTION
IDI_WARNING
IDI_EXCLAMATION
IDI_HAND
IDI_ERROR
If you designed your own icon (you should make sure you design a 32x32 and a 16x16 versions, even
for convenience), to use it, specify the hInstance argument of the LoadIcon() function to the instance
of your application. Then use the MAKEINTRESOURCE macro to convert its identifier to a null-
terminated string. This can be done as follows:
The LoadIcon() member function returns an HICON object that you can assign to the hIcon member
variable of your WNDCLASS object. Besides the regular (32x32) icon, the WNDCLASSEX
structure allows you to specify a small icon (16x16) to use in some circumstances. You can specify
both icons as follows:
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.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
Introduction to Cursors
If you designed your own icon (you should make sure you design a 32x32 and a 16x16 versions, even
for convenience), to use it, specify the hInstance argument of the LoadIcon() function to the instance
of your application. Then use the MAKEINTRESOURCE macro to convert its identifier to a null-
terminated string. This can be done as follows:
The icon can be specified by its name, which would be a null-terminated string passed as
lpszResourceName. If you had designed your icon and gave it an ID, you can pass this identifier to
the LoadIcon() method.
The LoadIcon() member function returns an HICON object that you can assign to the hIcon member
variable of your WNDCLASS object. Besides the regular (32x32) icon, the WNDCLASSEX
structure allows you to specify a small icon (16x16) to use in some circumstances. You can specify
both icons as follows:
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX WndClsEx;
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.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
A cursor is used to locate the position of the mouse pointer on a document or the screen. To use a
cursor, call the Win32 LoadCursor() function. Its syntax is:
The hInstance argument is a handle to the file in which the cursor was created. This file is usually
stored in a library (DLL) of an executable program. If the cursor was created as part of your
application, you can use the hInstance of your application. If your are using one of the below cursors,
set this argument to NULL.
When Microsoft Windows installs, it also installs various standard cursors you can use in your
program. Each one of these cursors is recognized by an ID which is simply a constant integers. The
available cursors are:
ID Picture Description
IDC_HAND The Hand is standard only in Windows 2000. If you are using a
previous operating system and need this cursor, you may have to
create your own.
IDC_HELP The combined arrow and question mark cursor is used when
providing help on a specific item on a window object
IDC_SIZEALL The four arrow cursor pointing north, south, east, and west is
highly used to indicate that an object is selected or that it is ready
to be moved
IDC_SIZENESW The northeast and southwest arrow cursor can be used when
resizing an object on both the length and the height
IDC_SIZENS The north - south arrow pointing cursor can be used when
shrinking or heightening an object
IDC_SIZENWSE The northwest - southeast arrow pointing cursor can be used when
resizing an object on both the length and the height
IDC_SIZEWE The west - east arrow pointing cursor can be used when narrowing
or enlarging an object
IDC_UPARROW The vertical arrow cursor can be used to indicate the presence of
the mouse or the caret
The LoadCursor() member function returns an HCURSOR value. You can assign it to the hCursor
member variable of your WNDCLASS object. Here is an example:
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.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
To paint the work area of the window, you must specify what color will be used to fill it. This color is
created as an HBRUSH and assigned to the hbrBackground member variable of your WNDCLASS
or WNDCLASSEX variable. The color you are using must be a valid HBRUSH or you can cast a
known color to HBRUSH. The Win32 library defines a series of colors known as stock objects. To
use one of these colors, call the GetStockObject() function. For example, to paint the windows
background in black, you can pass the BLACK_BRUSH constant to the GetStockObject() function,
cast it to HBRUSH and assign the result to hbrBackground.
In addition to the stock objects, the Microsoft Windows provides a series of colors for its own
internal use. These are the colors used to paint the borders of frames, buttons, scroll bars, title bars,
text, etc. The colors are named (you should be able to predict their appearance or role from their
name) COLOR_ACTIVEBORDER, COLOR_ACTIVECAPTION,
COLOR_APPWORKSPACE, COLOR_BACKGROUND, COLOR_BTNFACE,
COLOR_BTNSHADOW, COLOR_BTNTEXT, COLOR_CAPTIONTEXT,
COLOR_GRAYTEXT, COLOR_HIGHLIGHT, COLOR_HIGHLIGHTTEXT,
COLOR_INACTIVEBORDER, COLOR_INACTIVECAPTION, COLOR_MENU,
COLOR_MENUTEXT, COLOR_SCROLLBAR, COLOR_WINDOW,
COLOR_WINDOWFRAME, and COLOR_WINDOWTEXT. You can use any of these colors to
paint the background of your window. First cast it to HBRUSH and assign it to hbrBackground:
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 = GetStockObject(WHITE_BRUSH);
WndClsEx.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
If you want the window to display a menu, first create or design the resource menu (we will
eventually learn how to do this). After creating the menu, assign its name to the lpszMenuName name
to your WNDCLASS or WNDCLASSEX variable. Otherwise, pass this argument as NULL. Here is
an example:
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 = GetStockObject(WHITE_BRUSH);
WndClsEx.lpszMenuName = NULL;
WndClsEx.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
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 = GetStockObject(WHITE_BRUSH);
WndClsEx.lpszMenuName = NULL;
WndClsEx.lpszClassName = ClsName;
WndClsEx.hInstance = hInstance;
WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
return 0;
}
3. Finalizing an Application
Step 1: Registering the Window Class
In the above program we register a window class in the windows system using the Win32
API function RegisterClass(&wc)of which address of structure wc is given the
parameter. wc is defined as the object of structure WNDCLASSEX. This structure data defines
the properties of the window including its class name.
When you register a class once, and create as many windows as you want from it, without
having to specify all those attributes over and over. Most of the attributes you set in the
window class can be changed on a per-window basis if desired.
A Window Class has NOTHING to do with C++ classes.
The following statement in the program must be executed initialize the member of the object
of wc. Before you can register the class in the window 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 must fill out the
members of a WNDCLASSEX structure before calling RegisterClassEx () as explained above
in The Main Window Class.
We then call RegisterClassEx() and check for failure, if it fails we pop up a message
which says so and abort the program by returning from the WinMain() function.
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.
RegisterClassEx() may fail so we us the ―if(!RegisterClassEx(&wc))‖ structure to
check and informbus if it does.
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);
The first parameter (WS_EX_CLIENTEDGE) is the extended windows style, in this case I have
set it to give it a sunken inner border around the window. Set it to 0 if you'd like to see the
difference. Also play with other values to see what they do.
Next we have the class name (g_szClassName in this example), which 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. (In other application, we may use windows defines class like
―EDIT‖, ―BUTTONS‖, etc to create dialogboxes) 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.
The parameter we have as WS_OVERLAPPEDWINDOW is the Window Style parameter. There are
quite a few of these and you should look them up and experiment to find out what they do.
These will be covered more later.
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. If you have successfully created a window and store the handle
and the next window you are opening is a child so you must put the value of the created
window in this parameter (Parent Window handle). The menu is NULL for now since we don't
have one yet. If you are creating a window with menu defined in the resource file (which is of
the same name but with the .rc e extension), fill the name of the menu here. If you are
opening a window, 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.
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;
}
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);
The nCmdShow parameter is optional, you could simply pass in SW_SHOWNORMAL all the time
and be done with it. However using the parameter passed into WinMain() gives whoever is
running your program to specify whether or not they want your window to start off visible,
maximized, minimized, etc... You will find options for these in the properties of windows
shortcuts, and this parameter is how the choice is carried out.
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.
Finally DispatchMessage() sends the message out to the window that the message was sent
to. This could be our main window or it could be another one, or a control, and in some cases
a window that was created behind the scenes by the system or another program. This isn't
something you need to worry about because all we are concerned with is that we get the
message and send it out, the system takes care of the rest making sure it gets to the proper
window.
Windows Messages
The system passes input to a window procedure in the form of messages. Messages are
generated by both the system and applications. The system generates a message at each input
event — for example, when the user types, moves the mouse, or clicks a control such as a
scroll bar. The system also generates messages in response to changes in the system brought
about by an application, such as when an application changes the pool of system font
resources or resizes one of its windows. An application can generate messages to direct its
own windows to perform tasks or to communicate with windows in other applications.
The system sends a message to a window procedure with a set of four parameters: a window
handle, a message identifier, and two values called message parameters. The window handle
identifies the window for which the message is intended. The system uses it to determine
which window procedure should receive the message.
A message identifier is a named constant that identifies the purpose of a message. When a
window procedure receives a message, it uses a message identifier to determine how to
process the message. For example, the message identifier WM_PAINT tells the window
procedure that the window's client area has changed and must be repainted.
Message parameters specify data or the location of data used by a window procedure when
processing a message. The meaning and value of the message parameters depend on the
message. A message parameter can contain an integer, packed bit flags, a pointer to a
structure containing additional data, and so on. When a message does not use message
parameters, they are typically set to NULL. A window procedure must check the message
identifier to determine how to interpret the message parameters.
Message Types
System-Defined Messages
Application-Defined Messages
System-Defined Messages
Each system-defined message has a unique message identifier and a corresponding symbolic
constant (defined in the software development kit (SDK) header files) that states the purpose
of the message. For example, the WM_PAINT constant requests that a window paint its
contents.
Symbolic constants specify the category to which system-defined messages belong. The
prefix of the constant identifies the type of window that can interpret and process the
message. Following are the prefixes and their related message categories.
BM Button control
DBT Device
EM Edit control
TB Toolbar
TBM Trackbar
WM General window
General window messages cover a wide range of information and requests, including
messages for mouse and keyboard input, menu and dialog box input, window creation and
management, and Dynamic Data Exchange (DDE).
Application-Defined Messages
An application can create messages to be used by its own windows or to communicate with
windows in other processes. If an application creates its own messages, the window
procedure that receives them must interpret the messages and provide appropriate processing.
The system reserves message-identifier values in the range 0x0000 through 0x03FF
(the value of WM_USER – 1) for system-defined messages. Applications cannot use
these values for private messages.
Values in the range 0x0400 (the value of WM_USER) through 0x7FFF are available
for message identifiers for private window classes.
If your application is marked version 4.0, you can use message-identifier values in the
range 0x8000 (WM_APP) through 0xBFFF for private messages.
The system returns a message identifier in the range 0xC000 through 0xFFFF when
an application calls the RegisterWindowMessage function to register a message. The
message identifier returned by this function is guaranteed to be unique throughout the
system. Use of this function prevents conflicts that can arise if other applications use
the same message identifier for different purposes.
Message Routing
The system uses two methods to route messages to a window procedure: posting messages to
a first-in, first-out queue called a message queue, a system-defined memory object that
temporarily stores messages, and sending messages directly to a window procedure.
Messages posted to a message queue are called queued messages. They are primarily the
result of user input entered through the mouse or keyboard, such as WM_MOUSEMOVE,
WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR messages. Other queued
messages include the timer, paint, and quit messages: WM_TIMER, WM_PAINT, and
WM_QUIT. Most other messages, which are sent directly to a window procedure, are called
nonqueued messages.
Queued Messages
Nonqueued Messages
Queued Messages
The system can display any number of windows at a time. To route mouse and keyboard
input to the appropriate window, the system uses message queues.
The system maintains a single system message queue and one thread-specific message queue
for each graphical user interface (GUI) thread. To avoid the overhead of creating a message
queue for non–GUI threads, all threads are created initially without a message queue. The
system creates a thread-specific message queue only when the thread makes its first call to
one of the specific user functions; no GUI function calls result in the creation of a message
queue.
Queued Messages
Whenever the user moves the mouse, clicks the mouse buttons, or types on the keyboard, the
device driver for the mouse or keyboard converts the input into messages and places them in
the system message queue. The system removes the messages, one at a time, from the system
message queue, examines them to determine the destination window, and then posts them to
the message queue of the thread that created the destination window. A thread's message
queue receives all mouse and keyboard messages for the windows created by the thread. The
thread removes messages from its queue and directs the system to send them to the
appropriate window procedure for processing.
With the exception of the WM_PAINT message, the WM_TIMER message, and the
WM_QUIT message, the system always posts messages at the end of a message queue. This
ensures that a window receives its input messages in the proper first in, first out (FIFO)
sequence. The WM_PAINT message, the WM_TIMER message, and the WM_QUIT
message, however, are kept in the queue and are forwarded to the window procedure only
when the queue contains no other messages. In addition, multiple WM_PAINT messages for
the same window are combined into a single WM_PAINT message, consolidating all invalid
parts of the client area into a single area. Combining WM_PAINT messages reduces the
number of times a window must redraw the contents of its client area.
The system posts a message to a thread's message queue by filling an MSG structure and then
copying it to the message queue. Information in MSG includes: the handle of the window for
which the message is intended, the message identifier, the two message parameters, the time
the message was posted, and the mouse cursor position. A thread can post a message to its
own message queue or to the queue of another thread by using the PostMessage or
PostThreadMessage function.
An application can remove a message from its queue by using the GetMessage function. To
examine a message without removing it from its queue, an application can use the
PeekMessage function. This function fills MSG with information about the message.
After removing a message from its queue, an application can use the DispatchMessage
function to direct the system to send the message to a window procedure for processing.
DispatchMessage takes a pointer to MSG that was filled by a previous call to the
GetMessage or PeekMessage function. DispatchMessage passes the window handle, the
message identifier, and the two message parameters to the window procedure, but it does not
pass the time the message was posted or mouse cursor position. An application can retrieve
this information by calling the GetMessageTime and GetMessagePos functions while
processing a message.
A thread can use the WaitMessage function to yield control to other threads when it has no
messages in its message queue. The function suspends the thread and does not return until a
new message is placed in the thread's message queue.
You can call the SetMessageExtraInfo function to associate a value with the current thread's
message queue. Then call the GetMessageExtraInfo function to get the value associated with
the last message retrieved by the GetMessage or PeekMessage function.
Nonqueued Messages
Nonqueued messages are sent immediately to the destination window procedure, bypassing
the system message queue and thread message queue. The system typically sends nonqueued
messages to notify a window of events that affect it. For example, when the user activates a
new application window, the system sends the window a series of messages, including
WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR. These messages notify the
window that it has been activated, that keyboard input is being directed to the window, and
that the mouse cursor has been moved within the borders of the window. Nonqueued
messages can also result when an application calls certain system functions. For example, the
system sends the WM_WINDOWPOSCHANGED message after an application uses the
SetWindowPos function to move a window.
Message Handling
An application must remove and process messages posted to the message queues of its
threads. A single-threaded application usually uses a message loop in its WinMain function to
remove and send messages to the appropriate window procedures for processing.
Applications with multiple threads can include a message loop in each thread that creates a
window. The following sections describe how a message loop works and explain the role of a
window procedure:
Message Loop
Window Procedure
Message Loop
A simple message loop consists of one function call to each of these three functions:
GetMessage, TranslateMessage, and DispatchMessage. Note that if there is an error,
GetMessage returns -1 -- thus the need for the special testing.
The case selection determine what the window procedure (WinProc()) shoud process upon
receiving a message. The case selection may be added to process more windows message.
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.
The Complete Program
#include <windows.h>
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) //Entry of program
{
WNDCLASSEX wc; //Create WNDCLASSEX object
2. And when you exit use MessageBox() to display the window’s class name as the
caption of the windows and display your name as the message.
Question
Ans: Yes
Reference: Open Windows API guide. Under Contents Tab, open ref folder and select
Windows API Reference: Messages. The jump to button and look under mouse title. You will
find the following message listed.
Mouse
o WM_LBUTTONDBLCLK
o WM_LBUTTONDOWN
o WM_LBUTTONUP
o WM_MBUTTONDBLCLK
o WM_MBUTTONDOWN
o WM_MBUTTONUP
o WM_MOUSEMOVE
o WM_RBUTTONDBLCLK
o WM_RBUTTONDOWN
o WM_RBUTTONUP
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;
}
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. If you aren't sure how to do it, look at the
example zip file included with the section.
Note the new set of curly braces {} . These are required when declaring variables inside a
switch() statement. This should be basic C knowledge but I thought I should point it out
anyway for those of you doing things the hard way.
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.
You'll notice we've added two variables, hInstance and szFileName. Look up
GetModuleFileName() and you will see that the first parameter is a HINSTANCE refering to
the executable module (our program, the .exe file). Where do we get such a thing?
GetModuleHandle() is the answer. The references for GetModuleHandle() indicate that
passing in NULL will return us "a handle to the file used to create the calling process",
which is exactly what we need, the HINSTANCE just mentioned. Putting all this information
together we end up with the following declaration:
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];
MAX_PATH is a handy macro included via <windows.h> that is defined to the maximum length
of a buffer needed to store a filename under Win32. We also pass MAX_PATH to
GetModuleFileName() so it knows the size of the buffer.
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.
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>
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) //Entry of program
{
WNDCLASSEX wc; //Create WNDCLASSEX object
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 0, 0, "Hello, Windows!", 15);
EndPaint(hwnd, &ps);
return 0L;
Assignment 2b
Modify the program such that:
If you left right-click the mouse in the window, message box a messagebox like this will be
generated the following message.
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:
#define WM_INITDIALOG 0x0110
#define WM_COMMAND 0x0111
Structure of message
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.
Message: WM_COMMAND
Sent when a menu item or accelerator key is pressed. The low word or wParam is the id of
the menu item. So if you had defined in the resource editor a menu with two items, save and
load and given them the ids IDM_SAVE and IDM_LOAD you might write:
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDM_SAVE:
SaveProject()
break;
case IDM_LOAD:
LoadProject()
break;
default:
break;
}
}
Message: WM_KEYDOWN
This message is sent to your window when a key is pressed. Note that there is also a
WM_KEYUP message. The wParam contains a virtual key code and the lParam specifies
some extra information like the repeat count, extended-key flag, context code, previous key-
state flag etc.
case WM_KEYDOWN:
{
switch(wParam)
{
case 'W':
// w key pressed
break;
case VK_RIGHT:
// Right arrow pressed
break;
default:
break;
}
}
There are many virtual key codes available. As you can see above you can type the character
(upper case) for letters and numbers but for special keys you need to use a code like
VK_RIGHT, VK_LEFT, VK_HOME etc. These are all defined in Winuser.h.
It is not recommended to use WM_KEYDOWN for text entry as the keyboard layout depends
a lot on the locality etc. instead you should use WM_CHAR.
Message: WM_MOUSEMOVE
This message is sent to your window when the mouse is moved over its surface area.
wParam indicates if a specific key or mouse button is held down. The low word of lParam
is the x position of the mouse and the high word is the y position. So to retrieve the position
and button states:
case WM_MOUSEMOVE:
{
In your game you may want to control a camera rotation dependant on the change in the
mouse position. To do this you need to remember the previous mouse position and use the
difference to map onto camera rotations. If you are running your game in a window you can
get into problems when the mouse leaves the window area as you stop receiving mouse
messages. To solve this you can capture the mouse, this means only your window receives
messages from the mouse. Obviously you do not want to do this all the time or the user
would not be able to work outside of your program so one way to do it is to capture the
mouse only when the left button is first held down and then release it when the button is
lifted.
SetCapture(hWnd);
ReleaseCapture();
Messages: WM_LBUTTONDOWN, WM_RBUTTONDOWN
These are sent if the left button or right button on the mouse has been pressed.
These are sent if the left button or right button on the mouse has been released.
The WM_DESTROY message must be handled, this message is sent if the user has closed the
window. You should post a message telling windows to destroy the window as shown above.
The WM_COMMAND message is sent to your window when a menu item is selected or an
accelerator key pressed. The low word of the wParam contains the id of the menu item (as
you defined it in the resource editor).
The WM_PAINT message is sent when your window needs redrawing, this can happen when
the window is created, when another window is removed from over the top of our window,
when the window is maximized etc. As I mentioned earlier you can only draw to your
window when Windows tells you to, however if you want to change what is displayed
without waiting you can tell Windows that your window is dirty and needs a redraw by
using this function:
InvalidateRect(hWnd,NULL,TRUE);
The first parameter is the handle of the window, the second is the rectangular are of the
window that needs redrawing or NULL to indicate the whole window. The third parameter is
TRUE if you want the current contents to be erased first.
Note: if you handle the WM_PAINT message you must actually do some drawing otherwise
the window will not be refreshed. So if you do not want to do anything do not trap the
message.
Note: there are loads and loads of types of messages that can be sent to your window, look in
the MSDN help for details, I will just describe a few of the most useful here:
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).
2. When an event occures causing a message to be added to the queue (for example the
system registers a mouse click) GetMessages() returns a positive value indicating
there is a message to be processed, and that it has filled in the members of the MSG
structure we passed it. It returns 0 if it hits WM_QUIT, and a negative value if an error
occured.
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.
4. Once that's done we pass the message to DispatchMessage(). What
DispatchMessage() does is take the message, checks which window it is for and
then looks up the Window Procedure for the window. It then calls that procedure,
sending as parameters the handle of the window, the message, and wParam and
lParam.
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().
As you can see, your application spends the majority of its 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.
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.
QUESTIONS
In the following code
1. The program get out of the while loop if it receive a WM_QUIT message. Explain why.
2. Why does the program need to exit the loop if it receive a WM_QUIT message.
3. What happen when DispatchMessage(&Msg); is executed?
6. Windows Messages
Below is a categorical list of the API messages currently documented on this web site. Please
keep in mind that this site does not encompass the entire API yet, so unfortunately may not
find what you are looking for. To suggest any additions you would like to see made, please
contact the author with your request. All pages added since the last update of this site are
clearly marked with NEW.
Classes Type:
Buttons
Combo Boxes
Edit Controls
IP Address Control
List Boxes
Media Control Interface (MCI)
Menus
Mouse
Timers
Windows
Classes Message:
Buttons
o BM_CLICK
o BM_GETCHECK
o BM_GETSTATE
o BM_SETCHECK
o BM_SETSTATE
Combo Boxes
o CB_ADDSTRING
o CB_DELETESTRING
o CB_GETCOUNT
o CB_GETCURSEL
o CB_GETDROPPEDSTATE
o CB_GETLBTEXT
o CB_GETLBTEXTLEN
o CB_INSERTSTRING
o CB_RESETCONTENT
o CB_SETCURSEL
o CB_SHOWDROPDOWN
Edit Controls
o EM_CANUNDO
o EM_GETFIRSTVISIBLELINE
o EM_GETLINE
o EM_GETPASSWORDCHAR
o EM_GETSEL
o EM_LINEINDEX
o EM_LINELENGTH
o EM_REPLACESEL
o EM_SETPASSWORDCHAR
o EM_SETSEL
o EM_UNDO
IP Address Control
o IPM_CLEARADDRESS
o IPM_GETADDRESS
o IPM_ISBLANK
o IPM_SETADDRESS
o IPM_SETFOCUS
o IPM_SETRANGE
List Boxes
o LB_ADDSTRING
o LB_DELETESTRING
o LB_GETCOUNT
o LB_GETCURSEL NEW
o LB_GETSEL NEW
o LB_GETSELCOUNT NEW
o LB_GETSELITEMS NEW
o LB_GETTEXT
o LB_GETTEXTLEN
o LB_INSERTSTRING
o LB_RESETCONTENT
o LB_SETCURSEL NEW
o LB_SETSEL NEW
Media Control Interface (MCI)
o MM_MCINOTIFY
Menus
o WM_COMMAND
o WM_INITMENU
o WM_SYSCOMMAND
Mouse
o WM_LBUTTONDBLCLK
o WM_LBUTTONDOWN
o WM_LBUTTONUP
o WM_MBUTTONDBLCLK
o WM_MBUTTONDOWN
o WM_MBUTTONUP
o WM_MOUSEMOVE
o WM_RBUTTONDBLCLK
o WM_RBUTTONDOWN
o WM_RBUTTONUP
Timers
o WM_TIMER
Windows
o WM_CLOSE
o WM_GETTEXT
o WM_GETTEXTLENGTH
o WM_HELP
o WM_SETTEXT
7. Message Boxes
8.
Introduction
A message box is a rectangle object that displays short message to the user. The message can be made
of one sentence, one paragraph, or a few paragraphs. To make the creation of a message box easy, the
Win32 library provides a specific function that can be used to for this purpose.
To create a message box, use the MessageBox() function. Its syntax is:
The first argument, hWnd, can be a handle to the window from where the message box will be called.
Otherwise, it can NULL.
The second argument, lpText, is a null-terminated string, such as an array of characters. This is the
actual message that will be presented to the user. As stated already, it can be one word, a whole
sentence, a paragraph, even a hew paragraphs.
The third argument, lpCaption, is the title that will display on the title bar. It also can be a null-
terminated string, if you know what title you would like to display. Otherwise, it can be NULL, in
which case the title bar would display Error.
The simplest way you can create a message is by calling the MessageBox() function with all
arguments set to NULL, in which case the message box would not make any sense:
As stated already, the first argument is either a handle of the window that is calling it, or NULL.
The simplest way to specify the second argument is by including a word or a sentence in double-
quotes. Here is an example:
You can also use string editing techniques to create a more elaborate message. This means that you
can use functions of the C string library to create your message.
The caption of the message can be any word or sentence but convention wisdom would like this
sentence to be in tune with the actual message. After all, unless the message is about bad news, Error
as a title is not particularly cute.
The fourth argument actually does three things. First it displays one or a few buttons. The buttons
depend on the value specified for the argument. If this argument is NULL, the message box displays
(only) OK. The values and their buttons can be as follows:
MB_OK
MB_OKCANCEL
MB_ABORTRETRYIGNORE
MB_YESNOCANCEL
MB_YESNO
MB_RETRYCANCEL
MB_CANCELTRYCONTINUE
MB_HELP
Besides the buttons, the message box can also display a friendly icon that accompanies the message.
Each icon is displayed by specifying a constant integer. The values and their buttons are as follows:
MB_ICONINFORMATION
Informing the user of a non-critical situation
MB_ICONASTERISK
The icons are used in conjunction with the buttons constant. To combine these two flags, use the
bitwise OR operator ―|‖.
The second thing this fourth argument does is to let the user close the message box after selecting one
of the buttons. Once the user clicks one of the buttons, the message box is closed.
The third role of this fourth argument is to control the result derived from the user dismissing the
message box. For example, clicking OK usually means that the user acknowledges what the message.
Clicking Cancel usually means the user is changing his or her mind about the action performed
previously. Clicking Yes instead of No usually indicates that the user agrees to perform an action.
In reality, the message box only displays a message and one or a few buttons. It is your responsibility
as the programmer to decide what to do when what button is clicked.
When a message box is configured to display more than one button, the operating system is set to decide
which button is the default. The default button has a thick border that sets it apart from the other
button(s). If the user presses Enter, the message box would behave as if the user had clicked the
default button. Fortunately, if the message box has more than one button, you can decide what button
would be the default. To specify the default button, use one of the following constants:
To specify the default button, use the bitwise OR operator to combine the constant integer of the
desired default button with the button's constant and the icon.
Practical Learning: Introducing Additional Resources
//---------------------------------------------------------------------------
#include <windows.h>
//---------------------------------------------------------------------------
return 0;
}
//---------------------------------------------------------------------------
A modal form or dialog box must be closed or hidden before you can continue working with
the rest of the application. For more information about working with dialog boxes, see User
Input to Dialog Boxes.
Dialog boxes that display important messages should always be modal. The About dialog
box in Visual Studio is an example of a modal dialog box.
Modeless forms let you shift the focus between the form and another form without having to
close the initial form. The user can continue to work elsewhere in any application while the
form is displayed.
Modeless forms are harder to program, because users can access them in an unpredictable
order. You have to keep the state of the application consistent no matter what the user
does. Often, tool windows are shown in a modeless fashion. The Find dialog box,
accessible from the Edit menu in Visual Studio, is an example of a modeless dialog box.
Use modeless forms to display frequently used commands or information.
A resource is an object that cannot be defined in C++ terms but that is needed to complete a program.
In the strict sense, it is text that contains a series of terms or words that the program can interpret
through code. Examples of resources are menus, icons, cursors, dialog boxes, sounds, etc.
There are various means of creating a resource and the approach you use depends on the resource. For
example, some resources are completely text-based, such is the case for the String Table or the
Accelerator Table. Some other resources must be designed, such is the case for icons and cursors.
Some other resources can be imported from another, more elaborate application, such is the case for
high graphic pictures. Yet some resources can be a combination of different resources.
Resource Creation
As mentioned already, resources are not a C++ concept but a Microsoft Windows theory of
completing an application. Therefore, the programming environment you use may or may not
provide you with the means of creating certain resources. Some environments like Borland
C++ Builder or Visual C++ (6 and .NET) are complete with (almost) anything you need to
create (almost) any type of resources. Some other environments may appear incomplete,
allowing you to create only some resources, the other resources must be created using an
external application not provided; such is the case for C++BuilderX.
Upon creating a resource, you must save it. Some resources are created as their own file, such
is the case for pictures, icons, cursors, sound, etc. Each of these resources has a particular
extension depending on the resource. After creating the resources, you must add them to a
file that has the extension .rc. Some resources are listed in this file using a certain syntax.
That's the case for icons, cursors, pictures, sounds, etc. Some other resources must be created
directly in this file because these resources are text-based; that's the case for menus, strings,
accelerators, version numbers, etc.
After creating the resource file, you must compile it. Again, some environments, such as
Microsoft Visual C++, do this automatically when you execute the application. Some other
environments may require you to explicitly compile the resource. That's the case for Borland
C++ Builder and C++BuilderX. (The fact that these environments require that you compile
the resource is not an anomaly. For example, if you create a Windows application that is
form-based in C++ Builder 6 or Delphi, you can easily add the resources and they are
automatically compiled and added to the application. If you decide to create a Win32
application, C++ Builder believes that you want to completely control your application; so, it
lets you decide when and how to compile a resource. This means that it simply gives you
more control).
11. Component of Resource Script
Identifiers
Identifiers are generally named in a certain way, although the reader and all programmers are
free to alter this naming scheme. It is simply a suggestion. Identifiers generally start with the
prefix "ID", followed by a letter that denotes the type of identifier:
Sometimes, the command identifiers in a menu are given an "IDM_" prefix, to distinguish
between commands from other sources.
DISCARDABLE
Resources are loaded into memory when the program is run. However, if a resource is not in
use, and if Windows does not need them immediately, resources can be optionally unloaded
from memory until needed. To specify that it is okay to unload an unused resource from
memory, you may list the DISCARDABLE keyword with the resource. DISCARDABLE
resources allow more efficient memory usage, but can slow down your program if they need
to be loaded from disk.
The DISCARDABLE keyword is ignored for 32-bit Windows, but remains for compatibility.
Icons
Icons can be stored in a resource file using the ICON keyword. Here is a general example of
using an Icon in a resource script:
Windows explorer will display the program executable with the first icon from the script. For
instance, if we load 2 icons, as such:
#define IDI_ICON1 1
#define IDI_ICON2 2
To Load an icon from an executable module, assuming we have an instance handle to the
module ("hInst" in this example), we can get a handle to the icon as such:
HICON hIcon;
hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON1));
This will return a handle to the icon associated with the identifier "IDI_ICON1". Icon
identifiers are generally prefixed with an "IDI_" which is short for "ID for an Icon".
The second parameter to the LoadIcon function is a pointer to a string. String pointers are 32
bit values. However, if the most signficant 16 bits are all zero, Windows will treat the value
as a resource number, and not a string. To make the conversion between a string and a 16-bit
integer, Microsoft provides the MAKEINTRESOURCE macro. Similarly, we could have
used a string to define our Icon:
HICON hIcon;
hIcon = LoadIcon(hInst, "MYICON1");
WNDCLASSEX has handle values for 2 icons: a large icon and a small icon. The small icon
is the icon used in the upper-left corner. Small icons are generally 16 pixels square. Larger
icons are 32 pixels square. If no small icon handle is provided, the large icon will be shrunk
down to fit.
If the LoadIcon function is supplied with a NULL instance handle, Windows will supply a
default icon for use.
Recently, the Win32 API provides the LoadImage function for loading icons, bitmaps, and
mouse cursors from a single function. You can find more information about this function on
MSDN.
Bitmaps
Bitmaps can be loaded similarly to Icons in resource files:
Bitmaps can be accessed with the aptly named LoadBitmap function (again, new versions of
the Win32 API prefer you use LoadImage to load a bitmap, icon, or cursor). LoadBitmap
returns an HBITMAP handle type:
HBITMAP hBmp;
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
Bitmaps are large resources, and if windows can't load the bitmap into memory (or if the ID
or name value is invalid), the function will return a NULL value. Make sure you test this
value before you use the handle.
Bitmaps must be unloaded from memory by passing the handle to the DestroyObject
function. You can find more information about this on MSDN
Bitmap identifiers generally use a "IDB_" prefix, to indicate that it is the ID of a bitmap.
Mouse Cursors
Mouse cursors are specified similarly to icons and bitmaps, and are loaded with the
LoadCursor function.
String Tables
A resource script can have many string tables, although this is unneccessary: the tables aren't
differentiated, and each string object, in any table, must have a unique identifier. Strings in a
string table also may not use names, but instead must use numeric identifiers. After all, it
doesn't make any sense to have to address a string with a string, does it?
STRINGTABLE DISCARDABLE
BEGIN
IDS_STRING1, "This is my first string"
IDS_STRING2, "This is my second string"
...
END
It is important to note that in place of the BEGIN and END keywords, the programmer may
also use the more C-like curly brackets, as such:
STRINGTABLE DISCARDABLE
{
IDS_STRING1, "This is my first string"
IDS_STRING2, "This is my second string"
...
}
Some people prefer one over the other, but they are all the same to the resource compiler.
Strings can be loaded using the LoadString function. LoadString is more involved then the
LoadBitmap or LoadIcon functions:
The hInstance parameter, as we know, is the instance handle for the module that contains the
string. The uID parameter contains the string number that we are trying to access. lpBuffer is
the character array variable that will receive the string, and the nBufferMax number tells
windows what the maximum number of characters that can be loaded is. This count is a
security precaution, so make sure not to allow Windows to write character data beyond the
end of the string. MSDN displays a large warning on the page for this function, and it is
important that programmers heed this warning. msdn
Windows will automatically zero-terminate the string, once it is written to the buffer.
LoadString will return the number of characters that were actually written into the string, in
case the number of characters is less then the maximum number allowed. If this return value
is 0, the string resource does not exist, or could not be loaded.
Accelerators
Keyboard accelerators are a common part of nearly every windows application, and therefore
it is a good idea to simplify the job of creating accelerators by putting them in a resource
script. Here is how to create an accelerator table:
Key combinations are specified in terms of either a string literal character ("A" for instance)
or a virtual key code value. Here are some examples:
Now, when the key combination "Shift+A" is pressed, your window procedure will receive a
WM_COMMAND message with the value IDA_ACTION_A in the WPARAM field of the
message.
If we want to use combinations of the "Alt" key, or the "Ctrl" key, we can use the ALT and
CONTROL keywords, respectively:
Also, we can use the "^" symbol to denote a CONTROL key code:
Similarly, if we want to be super hackers, could use the ASCII code directly:
Or, we could refer to keys (including non-alphanumeric keys) with their Virtual Key Code
identifiers, by using the VIRTKEY identifier:
IDA_ACCEL_TABLE ACCELERATORS DISCARDABLE
BEGIN
VK_F12, IDA_ACTION_F12, VIRTKEY //press the "F12 Key"
VK_DELETE, IDA_ACTION_DEL, VIRTKEY, CONTROL //Ctrl+Delete
END
Now, If we make an accelerator correspond to a menu command, the menu command will
light up when we press the accelerator. That is, the menu will light up unless we specify the
"NOINVERT" keyword:
HACCEL hAccel;
hAccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDA_ACCEL_TABLE));
Again, we could have given our resource a string name, and used that string to load the table.
When using accelerators, we need to alter our message loop to intercept the keypress
messages, and translate them into command messages according to our accelerator table
rules. We use the TranslateAccelerator function, to intercept the keypress messages, and
translate them into command messages, as such:
Also, if we are writing an MDI application, we need to intercept Accelerator messages from
the child windows, we use the TranslateMDISysAccel function also:
Where "hwndFrame" is the handle to the frame window, and "hwndClient" is the handle to
the MDI client window.
Menus
Menus can be defined in a resource script using the MENU keyword. There are 2 types of
items that appear in a menu, the top level "POPUP" menu items, and the secondary
"MENUITEM" items. These are defined in a menu as such:
We have included a few examples here, so that you can see the difference between a POPUP
and a MENUITEM. When we have a menu with the ID_MENU identifier, we can load it into
our program as such:
HMENU hmenu;
hmenu = LoadMenu(hInst, MAKEINTRESOURCE(ID_MENU));
Once we have this handle, we can pass it to the CreateWindow function, and apply it to our
window.
When a menu item is selected, the host program receives a WM_COMMAND message, with
the menu item identifier in the WPARAM parameter. If we have a basic window procedure
switch-case statement, we can see this as follows:
case WM_COMMAND:
switch(WPARAM)
{
case IDM_EDIT_COPY:
//handle this action
break;
case IDM_EDIT_PASTE:
//handle this action
break;
}
break;
In a menu, if we want to associate a menu item with an accelerator, we can define it as such:
ID_MENU MENU DISCARDABLE
BEGIN
POPUP "File"
POPUP "Edit"
BEGIN
MENUITEM "&Copy", IDM_EDIT_COPY
MENUITEM "&Paste", IDM_EDIT_PASTE
END
...
END
Notice how we put the ampersand (&) in front of the "C" in "Copy" and the "P" in "Paste".
This means that those letters will be underlined, but more importantly, if an accelerator key
combination is pressed, those items in the menu will be highlighted (unless the NOINVERT
tag is specified in the accelerator table). If an ampersand is placed before a POPUP menu
item, pressing ALT+ that letter will popup that menu. For instance, lets define our menu:
Now, if we press ALT+F, we will pop open the File menu, and if we press ALT+E it will
open the Edit menu. That's pretty nice functionality for only a single extra character to type.
Version Information
A program can include certain information about its version, and its author in a resource
script. This version information appears when you right-click the executable in Windows, and
click "Properties". In the properties dialog box, this information appears on the "Version" tab.
Dialog Boxes
Dialog box resources follow a general pattern:
if a dialog box is not being associated with a class, the CLASS field does not need to be filled
in. All strings listed as being in quotes must be in quotes in the resource script or there will
be an error. Individual items in a dialog box are then specified between the BEGIN and END
tags.
12. Resource-Definition Statements
The resource-definition statements define the resources that the resource compiler puts in the resource (.Res)
file. After the .Res file is linked to the executable file, the application can load its resources at run time as
needed. All resource statements associate an identifying name or number with a given resource.
Resources
Controls
Statements
The following tables describe the resource-definition statements.
Resources
Resource Description
BITMAP Defines a bitmap by naming it and specifying the name of the file that contains it. (To use a particular bitmap
name.)
CURSOR Defines a cursor or animated cursor by naming it and specifying the name of the file that contains it. (To use
application requests it by name.)
DIALOG Defines a template that an application can use to create dialog boxes.
DIALOGEX Defines a template that an application can use to create dialog boxes.
ICON Defines an icon or animated icon by naming it and specifying the name of the file that contains it. (To use a p
requests it by name.)
MESSAGETABLE Defines a message table by naming it and specifying the name of the file that contains it. The file is a binary
themessage compiler.
POPUP Defines a menu item that can contain menu items and submenus.
PLUGPLAY Obsolete.
RCDATA Defines data resources. Data resources let you include binary data in the executable file.
STRINGTABLE Defines string resources. String resources are Unicode or ASCII strings that can be loaded from the executab
TEXTINCLUDE A special resource that is interpreted by Visual C++. For more information, see TN035.
TYPELIB A special resource that is used with the /TLBID and /TLBOUT linker options.
VERSIONINFO Defines a version-information resource. Contains information such as the version number, intended operating
VXD Obsolete.
For more information about pre-defined MFC resources, see TN023 and TN024.
Controls
Control Description
ICON Creates an icon control. This control is an icon displayed in a dialog box.
CHARACTERISTICS Specifies information about a resource that can be used by tool that can read or write resource-definition
FONT Sets the font with which the system will draw text for the dialog box.
LANGUAGE Sets the language for all resources up to the next LANGUAGE statement or to the end of the file. When t
appears before the beginning of the body of an ACCELERATORS, DIALOG, MENU, RCDATA, or STRING
the specified language applies only to that resource.
VERSION Specifies version information for a resource that can be used by tool that can read or write resource-defin
13. Using Resources
In Microsoft Windows, resources are read-only data embedded in EXE or DLL files.
The Windows API provides for easy access to all applications' resources.
Each resource has a type and a name, both being either numeric identifiers or strings.
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.
Creating resource
You ca create resources in a resources script, a file with an extension of ".rc". commercial
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 let’s 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 search for ―*,ico‖ files in your hard disk and copy one of the icon file of your choice, to
the same directory as where ―FirstMenu.cpp‖ is located. Rename the copied icon file to
"my_icon.ico".
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
as to what you are refering too, and easier to remember when you have large number of
resources.
#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.
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.
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:
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 explicitly stated in the documentation.
For example, this doesn't work for menu commands like ID_FILE_EXIT, since they can only
be integers.
14. Menus and Icons using resource
A menu is one of the text-based resources. It is created directly in the rc file. As with other
resources, the process of creating a menu depends on the environment you are using. If you
are using Borland C++ Builder, you can open your rc file and manually create your menu.
If you are using Microsoft Visual C++, you can use the built-in menu editor. In this case, the
actual text that defines and describes the menu would be automatically added to the rc file.
But for Microsoft Visual C++ express edition the menu editor is not built in.
For this example you can start with the window code from simple_window and add this code
into it as instructed.
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
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 (.cpp) 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);
wc.hIcon = LoadIcon(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDI_MYICON));
wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);
Search for an icon file (*.ico) in your file system and copy it to the same directory as where
menu_one.cpp is located and rename as menu_one.ico.
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.
Defining id numbers
The declaration defines id number for the respective identifier. It can be of any value of the
16 bit number (0 to 65535) but must be unique of each other or else the identifier of the same
id number will be defined as duplicates of each other.
This resource.h file is included in menu_one ,cpp and menu_one,rc, so both of these file will
have the same id declaration.
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 separate 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 a new empty project name menu_two then create menu_two.cpp and copy the
program SimpleWindow into menu_two.cpp. Do not create the .h or .rc. Now we will handle
the WM_CREATE message and add a menu to our window.
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);
When built, you will get errors. You will need to type cast data (in bold) in the following
statement to remove the errors.
After successfully compiled, the program creates a menu almost the same as the one we had
in the resource and attaches it to our window.
The program cannot load the ―menu_two.ico‖ file inthe following two instance:
hIcon = (HICON)LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32,
LR_LOADFROMFILE);
hIconSm = (HICON)LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16,
16, LR_LOADFROMFILE);
which cause her to display the two messages. To solve the error you need to create the file
―menu_two.ico‖ in the same directory as the ―.cpp‖ file.
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.
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.
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);
MessageBox(NULL,myName,"ID_STUFF",MB_ICONEXCLAMATION | MB_OK);
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.
To read message from menu item we must the LOWORD(wParam) parameter. So your
WM_COMMAND handler should now look like this:
switch(Message)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
case ID_STUFF_GO:
break;
}
break;
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);
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) //Entry of program
{
WNDCLASSEX wc; //Create WNDCLASSEX object
You may have noticed that the menu_one.exe file and menu_two load an external file for the
icon, the former declares the file name in menu_one.rc as a declaration and the latter is in the
menu_two.cpp in the program code.
With the former the icon cannot be change dynamically by program execution though you
can change it externally by changing icon file with a different image data.
With the later you can modify the program such that the icon file can be selected.
QUESTIONS
1. You may want to write build a window with menu that can be loaded or unload as and
when you need it. Which of the following method will you use?
a) using resource script
b) using child process
2. Base on the above program, give the reason why the following statements will
compiles with no errors but will have not never and cannot find ―case
ID_FILE_EXIT:‖ and ―case ID_STUFF_GO:‖ to be TRUE.
switch(Message)
{
case WM_CLOSE:
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
case ID_STUFF_GO:
break;
}
}
If [Yes] is selected, the program will exit. If [No] is selected, program will return to
application window.
15. Dialogs Box Using Resource
Example: FirstDialog
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 dialog in the .rc file
and let you incorporate it into your project.
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,8001 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 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 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.
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).
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 parameter is the value that is returned to whatever code called DialogBox().
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;
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.
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.
This is the full program:
FirstDialog.cpp:
#include <windows.h>
#include "Resource.h"
const char g_szClassName[] = "myWindowClass";
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
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);
}
}
case ID_FILE_EXIT:
DestroyWindow(hwnd);
break;
case ID_STUFF_GO:
MessageBox(hwnd, "Stuff to go",
"Second Menu", MB_OK );
break;
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
FirstDialog.rc:
#include "Resource.h"
#include <windows.h>
IDI_MYICON ICON "my_icon.ico"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END
POPUP "Help"
BEGIN
MENUITEM "About", ID_HELP_ABOUT
END
END
IDD_ABOUT DIALOG DISCARDABLE 0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | 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\nto the SEL 4263 Class",
IDD_ABOUT,16,18,144,33
END
Resource.h:
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:
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.
In Windows Procesure
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.
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:
In Windows Procedure add,
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.
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.
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.
Of course, you are not limited to using only the predefined classes. By calling
RegisterClassEx your program can create its own window classes. However, for most
purposes, using one of the predefined classes is best when creating some sort of control,
especially if one already exists.
Typically, a program uses these classes to create controls. Each class also has a unique set of
window style flags that only apply to it. These unique styles typically control properties
specific to that type of control. Below is a list of some of the window classes predefined in
the Windows API. Note that this list does not (yet) include all classes defined in the Windows
API. Information that applies to all window classes in general appears first in the list.
The common controls overview documentation describes the common controls delivered in
Microsoft Windows and the programming elements used to create and manipulate them.
Control Description
Button Notifies the parent window when the user selects the control.
A combination of a list box and an edit control, enabling the user to select
Combo Box
or add items.
An extension of the combo box control that provides native support for
ComboBoxEx
item images.
Date and Time A simple and intuitive interface through which to exchange date and time
Picker information with a user.
A type of list box that enables the user to drag items from one position to
Drag List Box
another.
Edit A window within the user can view and edit text.
A window in which the user can enter an Internet Protocol (IP) address in
IP Address
an easily understood format.
List Box A simple list from which the user can select one or more items.
List-View A list box that provides several ways to arrange and display the items.
Month Calendar A calendar that enables the user to select a date or dates.
Progress Bar An animated control that indicates the progress of a lengthy operation.
A dialog box that enables the user to view and edit the properties of an
Property Sheet
item. Pages may be viewed as tabs, or in succession as a wizard.
A window in which the user can view and edit text with character and
Rich Edit
paragraph formatting. It can also contain embedded COM objects.
A control that enables the user to choose the direction and distance to
Scroll Bar
scroll information in a related window.
Static Non-interactive text, including labels for other controls.
Toolbar A window that contains one or more buttons and possibly other controls.
A message that appears automatically when the mouse pointer hovers over
Tooltip
a tool.
Trackbar A slider with optional tick marks, used to set a value within a range.
A pair of arrow buttons that the user can click to increment or decrement a
Up-Down value, such as a scroll position or a number displayed in a companion
control.
Class-Specific Information
The button control's window class is registered automatically when Windows starts. The
name of the class is "BUTTON".
Styles
BS_3STATE
The button is a check box which has three states: checked, grayed, and cleared.
BS_AUTO3STATE
The button is a check box which has three states: checked, grayed, and cleared. The button
automatically changes its state when the user selects it.
BS_AUTOCHECKBOX
The button is a check box whose state toggles when the user selects it.
BS_AUTORADIOBUTTON
The button is a radio button whose state automatically changes to selected (and the state of all
other radio buttons in the group to unselected) when the user selects it.
BS_BITMAP
The button displays a bitmap.
BS_BOTTOM
The button's text appears at the bottom of the button rectangle.
BS_CENTER
The button's text appears centered horizontally within the button rectangle.
BS_CHECKBOX
The button is a check box.
BS_DEFPUSHBUTTON
The button is the default push button in a dialog box, having a heavy black border.
BS_GROUPBOX
The window is a rectangular grouping frame in which other controls can be grouped.
BS_FLAT
The button is flat, not using the default 3D shading.
BS_ICON
The button displays an icon.
BS_LEFT
The text in the button rectangle is left-justified.
BS_LEFTTEXT, BS_RIGHTBUTTON
The radio or check box's text appears to the left of the button instead of to the right.
BS_MULTILINE
The button's text is wrapped across multiple lines if it cannot fit on a single line.
BS_NOTIFY
The button sends focus notification messages to its parent window.
BS_OWNERDRAW
The owner of the window is responsible for manually drawing it whenever it needs to be
redrawn. This window style cannot be combined with any other button styles.
BS_PUSHBUTTON
The button is a push button.
BS_PUSHLIKE
The radio or check button takes on the appearance of a push button. A checked state makes
the button look depressed; a cleared state makes the button look normal.
BS_RADIOBUTTON
The button is a radio button.
BS_RIGHT
The text in the button rectangle is right-justified.
BS_TEXT
The button displays text.
BS_TOP
The text appears at the top of the button rectangle.
BS_USERBUTTON
Obsolete; use BS_OWNERDRAW instead.
BS_VCENTER
The text appears centered vertically within the button rectangle.
Constant Definitions
Const BS_3STATE = &H5
Const BS_AUTO3STATE = &H6
Const BS_AUTOCHECKBOX = &H3
Const BS_AUTORADIOBOX = &H9
Const BS_BITMAP = &H80
Const BS_BOTTOM = &H800
Const BS_CENTER = &H300
Const BS_CHECKBOX = &H2
Const BS_DEFPUSHBUTTON = &H1
Const BS_FLAT = &H8000
Const BS_GROUPBOX = &H7
Const BS_ICON = &H40
Const BS_LEFT = &H100
Const BS_LEFTTEXT = &H20
Const BS_MULTILINE = &H2000
Const BS_NOTIFY = &H4000
Const BS_OWNERDRAW = &HB
Const BS_PUSHBUTTON = &H0
Const BS_PUSHLIKE = &H1000
Const BS_RADIOBUTTON = &H4
Const BS_RIGHT = &H200
Const BS_RIGHTBUTTON = &H20
Const BS_TEXT = &H0
Const BS_TOP = &H400
Const BS_USERBUTTON = &H8
Const BS_VCENTER = &HC00
The button control's window class is registered automatically when Windows starts. The
name of the class is "COMBOBOX".
Styles
CBS_AUTOHSCROLL
Automatically scroll text to the right when the user types a character at the end of the line.
CBS_DISABLENOSCROLL
Show a disable the vertical scroll bar in the drop-down list box if it does not contain enough
items to scroll. If this flag is not specified, such a scroll bar is not displayed.
CBS_DROPDOWN
Display the list box whenever the user clicks the drop-down button.
CBS_DROPDOWNLIST
Display the list box whenever the user click the drop-down button, and do not allow the user
to change the combo box's selection if the list is not dropped down.
CBS_HASSTRINGS
The combo box is drawn manually by the application and contains strings. Either
CBS_OWNERDRAWFICED or CBS_OWNERDRAWVARIABLE must also be specified.
CBS_LOWERCASE
Convert all text in the combo box to lowercase letters.
CBS_NOINTEGRALHEIGHT
Force the combo box to be exactly the size specified by the application, instead of allowing
the operating system to slightly resize it to prevent displaying partial selections.
CBS_OEMCONVERT
Ensure that text in the combo box can readily be converted into the OEM character set.
CBS_OWNERDRAWFIXED
The owner of the combo box is fully responsible for drawing it manually, and all of the items
in the combo box have the same height.
CBS_OWNERDRAWVARIABLE
The owner of the combo box is full responsible for drawing it manually, and the heights of
the items in the combo box have can be different.
CBS_SIMPLE
Display the list box portion of the combo box at all times.
CBS_SORT
Automatically sort the strings added to the combo box.
CBS_UPPERCASE
Convert all text in the combo box to uppercase letters.
Constant Definitions
Const CBS_AUTOHSCROLL = &H40
Const CBS_DISABLENOSCROLL = &H800
Const CBS_DROPDOWN = &H2
Const CBS_DROPDOWNLIST = &H3
Const CBS_HASSTRINGS = &H200
Const CBS_LOWERCASE = &H4000
Const CBS_NOINTEGRALHEIGHT = &H400
Const CBS_OEMCONVERT = &H80
Const CBS_OWNERDRAWFIXED = &H10
Const CBS_OWNERDRAWVARIABLE = &H20
Const CBS_SIMPLE = &H1
Const CBS_SORT = &H100
Const CBS_UPPERCASE = &H2000
The button control's window class is registered automatically when Windows starts. The
name of the class is "EDIT".
Styles
ES_AUTOHSCROLL
Automatically scroll the text to the right when the user types a character at the end of the line.
When the user pressed ENTER, scroll the text all the way back to the left.
ES_AUTOVSCROLL
Automatically scroll the text back up when the user presses ENTER on the last line.
ES_CENTER
Center the text horizontally.
ES_LEFT
Left-align the text.
ES_LOWERCASE
Convert all the characters to lowercase as they are typed.
ES_MULTILINE
The edit control displays multiple lines of text. If this flag is not specified, it can only display
a single line of text.
ES_NOHIDESEL
Do not hide the selected text in the edit control even if the control loses focus.
ES_NUMBER
Only allow digits to be entered into the edit control.
ES_OEMCONVERT
Ensure that text in the edit control can readily be converted into the OEM character set.
ES_PASSWORD
Display an asterisk for each character typed into the edit control. This cannot be used with
ES_MULTILINE.
ES_READONLY
Do not allow the user to edit the text in the control.
ES_RIGHT
Right-align the text.
ES_UPPERCASE
Convert all the characters to uppercase as they are typed.
ES_WANTRETURN
Insert a carriage return into a multi-line edit control when the user pressed ENTER, instead of
implementing the default behavior of ENTER.
Constant Definitions
Const ES_AUTOHSCROLL = &H80
Const ES_AUTOVSCROLL = &H40
Const ES_CENTER = &H1
Const ES_LEFT = &H0
Const ES_LOWERCASE = &H10
Const ES_MULTILINE = &H4
Const ES_NOHIDESEL = &H100
Const ES_NUMBER = &H2000
Const ES_OEMCONVERT = &H400
Const ES_PASSWORD = &H20
Const ES_READONLY = &H800
Const ES_RIGHT = &H2
Const ES_UPPERCASE = &H8
Const ES_WANTRETURN = &H1000
Before creating windows from this class, your program must first register it by calling
InitCommonControlsEx with the appropriate parameters. The name of the class is
"SysIPAddress32".
Styles
None.
The button control's window class is registered automatically when Windows starts. The
name of the class is "LISTBOX".
Styles
LBS_DISABLENOSCROLL
Show a disable the vertical scroll bar in the list box if it does not contain enough items to
scroll. If this flag is not specified, such a scroll bar is not displayed.
LBS_EXTENDEDSEL
Allow the user to select multiple items.
LBS_HASSTRINGS
The list box is drawn manually by the application and contains strings. Either
LBS_OWNERDRAWFICED or LBS_OWNERDRAWVARIABLE must also be specified.
LBS_MULTICOLUMN
The list box contains multiple columns which are scrolled horizontally.
LBS_MULTIPLESEL
Toggle the selection of a string every time the user clicks or double-clicks it.
LBS_NODATA
Intended for list boxes having more than 1000 items, do not have the operating system handle
any of the entries. LBS_OWNERDRAWFIXED must also be specified, and neither
LBS_SORT nor LBS_HASSTRINGS can be specified.
LBS_NOINTEGRALHEIGHT
Force the list box to be exactly the size specified by the application, instead of allowing the
operating system to slightly resize it to prevent displaying partial selections.
LBS_NOREDRAW
Do not redraw the list box when changes are made.
LBS_NOSEL
Do not allow the user to select items in the list box.
LBS_NOTIFY
Notify the parent window whenever the user clicks or double-clicks a string in the list box.
LBS_OWNERDRAWFIXED
The owner of the list box is fully responsible for drawing it manually, and all of the items in
the list box have the same height.
LBS_OWNERDRAWVARIABLE
The owner of the list box is full responsible for drawing it manually, and the heights of the
items in the list box have can be different.
LBS_SORT
Automatically sort the strings in the list box alphabetically.
LBS_STANDARD
Automatically sort the strings in the list box alphabetically, notify the parent window
whenever the user clicks or double-clicks a string, and display a border around the list box.
LBS_USETABSTOPS
Allow the list box to expand tab characters when drawing its strings.
LBS_WANTKEYBOARDINPUT
Notify the owner of the list box whenever the user types a key while the list box has the
focus.
Constant Definitions
Const LBS_DISABLENOSCROLL = &H1000
Const LBS_EXTENDEDSEL = &H800
Const LBS_HASSTRINGS = &H40
Const LBS_MULTICOLUMN = &H200
Const LBS_MULTIPLESEL = &H8
Const LBS_NODATA = &H2000
Const LBS_NOINTEGRALHEIGHT = &H100
Const LBS_NOREDRAW = &H4
Const LBS_NOSEL = &H4000
Const LBS_NOTIFY = &H1
Const LBS_OWNERDRAWFIXED = &H10
Const LBS_OWNERDRAWVARIABLE = &H20
Const LBS_SORT = &H2
Const LBS_STANDARD = &HA00006
Const LBS_USETABSTOPS = &H80
Const LBS_WANTKEYBOARDINPUT = &H400
The button control's window class is registered automatically when Windows starts. The
name of the class is "SCROLLBAR".
Styles
SBS_BOTTOMALIGM
Align the bottom edge of the scroll bar with the bottom edge of its rectangle, using its default
height. SBS_HORZ must also be specified.
SBS_HORZ
The scroll bar is a horizontal scroll bar.
SBS_LEFTALIGN
Align the left edge of the scroll bar with the left edge of its rectangle, using its default width.
SBS_VERT must also be specified.
SBS_RIGHTALIGN
Align the right edge of the scroll bar with the right edge of its rectangle, using its default
width. SBS_VERT must also be specified.
SBS_SIZEBOX
The scroll bar is a size box.
SBS_SIZEBOXBOTTOMRIGHTALIGN
Align the lower-right corner of the size box with the lower-right corner of its rectangle, using
its default width and height. SBS_SIZEBOX must also be specified.
SBS_SIZEBOXTOPLEFTALIGN
Align the upper-left corner of the size box with the upper-left corner of its rectangle, using its
default width and hieght. SBS_SIZEBOX must also be specified.
SBS_SIZEGRIP
The scroll bar is a size box with a raised edge.
SBS_TOPALIGN
Align the top edge of the scroll bar with the top edge of its rectangle, using its default height.
SBS_HORZ must also be specified.
SBS_VERT
The scroll bar is a vertical scroll bar.
Constant Definitions
Const SBS_BOTTOMALIGN = &H4
Const SBS_HORZ = &H0
Const SBS_LEFTALIGN = &H2
Const SBS_RIGHTALIGN = &H4
Const SBS_SIZEBOX = &H8
Const SBS_SIZEBOXBOTTOMRIGHTALIGN = &H4
Const SBS_SIZEBOXTOPLEFTALIGN = &H2
Const SBS_SIZEGRIP = &H10
Const SBS_TOPALIGN = &H2
Const SBS_VERT = &H1
The button control's window class is registered automatically when Windows starts. The
name of the class is "STATIC".
Styles
SS_BITMAP
Display the bitmap specified by the static control's text.
SS_BLACKFRAME
Draw a frame around the static control in the same color as a window frame.
SS_BLACKRECT
Fill the static control with the same color as a window frame.
SS_CENTER
Center the text in the static control.
SS_CENTERIMAGE
If the bitmap or icon is smaller than the size of the static control, fill the rest of the control
with whatever color is at the image's upper-left corner.
SS_ENDELLIPSIS
Windows NT, 2000: Replace the end of the string with an ellipsis if it is too long to fit in the
static control.
SS_ENHMETAFILE
Display the enhanced metafile identified by the static control's text. Scale the enhanced
metafile to fit the static control.
SS_ETCHEDFRAME
Draw the frame of the static control using the etched edge style.
SS_ETCHEDHORZ
Draw only the top and bottom edges of the static control using the etched edge style.
SS_ETCHEDVERT
Draw only the left and right edges of the static control using the etched edge style.
SS_GRAYFRAME
Draw a frame around the static control in the same color as the screen background.
SS_GRAYRECT
Fill the static control with the same color as the screen background.
SS_ICON
Display the icon identified by the static control's text. The static control automatically resizes
to the size of the icon.
SS_LEFT
Left-align the text in the static control.
SS_LEFTNOWORDWRAP
Left-align the text in the static control, but do not word wrap.
SS_NOPREFIX
Do not use an amperstand character in the string to identify an accelerator prefix, instead
displaying the amperstands as regular characters.
SS_OWNERDRAW
The owner of the static control is fully responsible for drawing the control.
SS_PATHELLIPSIS
Windows NT, 2000: Replace characters in the middle of a string holding a path with an
ellipsis if it is too long to fit in the static control.
SS_REALSIZEIMAGE
Clip an image or bitmap if it does not fit inside the static control instead of resizing the
control.
SS_RIGHT
Right-align the text in the static control.
SS_RIGHTJUST
Do not move the lower-right corner of the static control when resizing it to accomodate a
bitmap or icon.
SS_SIMPLE
Draw a simple rectangle and display a single line of left-aligned text in the static control.
SS_SUNKEN
Draw a half-sunken border around the static control.
SS_WHITEFRAME
Draw a frame around the static control in the same color as the window background.
SS_WHITERECT
Fill the static control with the same color as the window background.
SS_WORDELLIPSIS
Windows NT, 2000: Truncate text and add ellipses to text which does not fit into the static
control.
Constant Definitions
Const SS_BITMAP = &HE
Const SS_BLACKFRAME = &H7
Const SS_BLACKRECT = &H4
Const SS_CENTER = &H1
Const SS_CENTERIMAGE = &H200
Const SS_ENDELLIPSIS = &H4000
Const SS_ENHMETAFILE = &HF
Const SS_ETCHEDFRAME = &H12
Const SS_ETCHEDHORZ = &H10
Const SS_ETCHEDVERT = &H11
Const SS_GRAYFRAME = &H8
Const SS_GRAYRECT = &H5
Const SS_ICON = &H3
Const SS_LEFT = &H0
Const SS_LEFTNOWORDWRAP = &HC
Const SS_NOPREFIX = &H80
Const SS_NOTIFY = &H100
Const SS_OWNERDRAW = &HD
Const SS_PATHELLIPSIS = &H8000
Const SS_REALSIZEIMAGE = &H800
Const SS_RIGHT = &H2
Const SS_RIGHTJUST = &H400
Const SS_SIMPLE = &HB
Const SS_SUNKEN = &H1000
Const SS_WHITEFRAME = &H9
Const SS_WHITERECT = &H6
Const SS_WORDELLIPSIS = &HC000
18. Styles Common to All Classes
Base Window Styles
The following window styles are shared by all windows, regardless of their class. They
generally describe the window's general appearance, although many of the styles apply best
to non-control windows (particularly overlapped windows).
Styles
WS_BORDER
The window has a thin-line border.
WS_CAPTION
The window has a title bar.
WS_CHILD, WS_CHILDWINDOW
The window is a child window. These windows cannot have menu bars nor
can have the WS_POPUP style.
WS_CLIPCHILDREN
For a parent window, exclude the areas occupied by child windows when
drawing within the window (to avoid drawing on any child windows).
WS_CLIPSIBLINGS
For a child window, exclude the areas occupied by fellow children of the
window's parent when drawing within the window (to avoid drawing on any
sibling windows).
WS_DISABLED
The window is disabled.
WS_DLGFRAME
The window has a border style typical of dialog boxes. These windows
cannot have a title bar.
WS_GROUP
Identifies the first control in a group of controls. Any controls
following this one are assumed to be part of this control's group, until
a control with the WS_GROUP style is encountered.
WS_HSCROLL
The window has a horizontal scroll bar.
WS_MAXIMIZE
The window is maximized.
WS_MAXIMIZEBOX
The window has a maximize button. This cannot be used on windows having
the WS_EX_CONTEXTHELP extended window style. The WS_SYSMENU window style
must also be specified.
WS_MINIMIZE, WS_ICONIC
The window is minimized.
WS_MINIMIZEBOX
The window has a minimize button. This cannot be used on windows having
the WS_EX_CONTEXTHELP extended window style. The WS_SYSMENU window style
must also be specified.
WS_OVERLAPPED, WS_TILED
The window is an overlapped window, which as a title bar and a border.
WS_OVERLAPPEDWINDOW, WS_TILEDWINDOW
The window is an overlapped window, having a title bar, a sizing border,
a title bar, a system menu, and minimize and maximize boxes.
WS_POPUP
The window is a popup window. This cannot be used with the WS_CHILD
window style.
WS_POPUPWINDOW
The window is a popup window, having a thin-line border and system menu.
The window can only be visible if it also has the WS_CAPTION window
style.
WS_SIZEBOX
Same as WS_THICKFRAME.
WS_SYSMENU
The window has a system menu on its title bar. The WS_CAPTION window
style must also be specified.
WS_TABSTOP
The control can press Tab repeatedly to set the focus to this control.
WS_THICKFRAME
The window has a sizing border.
WS_VISIBLE
The window is visible.
WS_VSCROLL
The window has a vertical scroll bar.
Constant Definitions
Const WS_BORDER = &H800000
Const WS_CAPTION = &HC00000
Const WS_CHILD = &H40000000
Const WS_CHILDWINDOW = &H40000000
Const WS_CLIPCHILDREN = &H2000000
Const WS_CLIPSIBLINGS = &H4000000
Const WS_DISABLED = &H8000000
Const WS_DLGFRAME = &H400000
Const WS_GROUP = &H20000
Const WS_HSCROLL = &H100000
Const WS_ICONIC = &H20000000
Const WS_MAXIMIZE = &H1000000
Const WS_MAXIMIZEBOX = &H10000
Const WS_MINIMIZE = &H20000000
Const WS_MINIMIZEBOX = &H20000
Const WS_OVERLAPPED = &H0
Const WS_OVERLAPPEDWINDOW = &HCF0000
Const WS_POPUP = &H80000000
Const WS_POPUPWINDOW = &H80880000
Const WS_SIZEBOX = &H40000
Const WS_SYSMENU = &H80000
Const WS_TABSTOP = &H10000
Const WS_THICKFRAME = &H40000
Const WS_TILED = &H0
Const WS_TILEDWINDOW = &HCF0000
Const WS_VISIBLE = &H10000000
Const WS_VSCROLL = &H200000
Extended Styles
WS_EX_ACCEPTFILES
The window accepts files via a drag-and-drop operation.
WS_EX_APPWINDOW
The window also appears on the taskbar whenever it is visible.
WS_EX_CLIENTEDGE
The window has a border with a sunken edge.
WS_EX_CONTEXTHELP
A context-help button appears on the title bar. This cannot be used with the
WS_MAXIMIZEBOX or WS_MINIMIZEBOX regular window styles.
WS_EX_CONTROLPARENT
The window contains children which can be TABbed through.
WS_EX_DLGMODALFRAME
The window has a modal dialog frame (a double border).
WS_EX_LAYERED
Windows 2000: The window is a layered window.
WS_EX_LAYOUTRTL
Windows 2000: The window's coordinate system places the horizontal origin on the right
side, with increasing x values to the left and decreasing x values to the right.
WS_EX_LEFT
The window has generic left-aligned properties.
WS_EX_LEFTSCROLLBAR
If the language supports reading order alignment, position the vertical scroll bar (if any) to
the left of the client area.
WS_EX_LTRREADING
Display window text using left-to-right reading.
WS_EX_MDICHILD
The window is an MDI child window.
WS_EX_NOACTIVATE
Windows 2000: The window is never brought to the foreground as a result of direct user
action.
WS_EX_NOINHERITLAYOUT
Windows 2000: Do not pass the window's layout to its child windows.
WS_EX_NOPARENTNOTRIFY
If the window is a child window, do not notify the parent window when the child window is
created or destroyed.
WS_EX_OVERLAPPEDWINDOW
The window has the border of a typical overlapped window.
WS_EX_PALETTEWINDOW
The window is a topmost toolbar window with a raised edge, normally used for a floating
palette.
WS_EX_RIGHT
The window has generic right-aligned properties, if the language supports reading order
alignment.
WS_EX_RIGHTSCROLLBAR
Display the vertical scroll bar (if any) to the right of the client area.
WS_EX_RTLREADING
Display window text using right-to-left reading, if the language supports reading order
alignment.
WS_EX_STATICEDGE
The window has a three-dimensional border intended for items which do not accept user
input.
WS_EX_TOOLWINDOW
The window is designed to be a floating toolbar window, having a small title bar area.
WS_EX_TOPMOST
The window appears above all non-topmost windows, even if it is not active.
WS_EX_TRANSPARENT
The window appears transparent because its sibling windows below it are drawn first.
WS_EX_WINDOWEDGE
The window has a border with a raised edge.
Constant Definitions
Note: Some of the values of the extended window style flags are not listed. If you know their
values, please e-mail me about them.
Static control
The static control displays text and graphics. The static control cannot be selected. It cannot
have keyboard focus.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Static control"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 330, 270, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
static char *lyrics = TEXT("Bring what I said it dont mean shit
now\n\
Bring the presents might as well throw em out\n\
Bring all those kisses, they didn't mean jack\n\
Bring you, you hoe, I dont want you back\n\
\n\
You thought, you could\n\
Keep this shit from me, yeah\n\
Ya burnt bitch, I heard the story\n\
Ya played me, ya even gave him head\n\
Now ya askin for me back\n\
Ya just another act, look elsewhere\n\
Cuz ya done with me\n\
");
switch(msg)
{
case WM_CREATE:
{
CreateWindow(TEXT("STATIC"), lyrics,
WS_CHILD | WS_VISIBLE | SS_LEFT,
20, 20, 300, 230,
hwnd, (HMENU) 1, NULL, NULL);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
CreateWindow(TEXT("STATIC"), lyrics,
WS_CHILD | WS_VISIBLE | SS_LEFT,
20, 20, 300, 230,
hwnd, (HMENU) 1, NULL, NULL);
Here we create the static control. We display text. It is aligned to the
left.
Static control
Button
A button is a simple control. It has a text label. It is used to trigger an action.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Buttons"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
150, 150, 230, 150, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
switch(msg)
{
case WM_CREATE:
{
CreateWindow(TEXT("button"), TEXT("Beep"),
WS_VISIBLE | WS_CHILD ,
20, 50, 80, 25,
hwnd, (HMENU) 1, NULL, NULL);
CreateWindow(TEXT("button"), TEXT("Quit"),
WS_VISIBLE | WS_CHILD ,
120, 50, 80, 25,
hwnd, (HMENU) 2, NULL, NULL);
break;
}
case WM_COMMAND:
{
if (LOWORD(wParam) == 1) {
Beep(40, 50);
}
if (LOWORD(wParam) == 2) {
PostQuitMessage(0);
}
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example we have created two buttons. On button will beep. The
other one will close the window.
case WM_COMMAND:
{
if (LOWORD(wParam) == 1) {
Beep(40, 50);
}
if (LOWORD(wParam) == 2) {
PostQuitMessage(0);
}
break;
}
The control id is in the LOWORD of the wParam. Depending on the control id, we call the
Beep() function or the PostQuitMessage() function.
Button controls
Check box
A check box control is a box that you can click to turn an option on or off.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, title,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
150, 150, 230, 150, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
switch(msg)
{
case WM_CREATE:
{
CreateWindow(TEXT("button"), TEXT("Show Title"),
WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
20, 20, 185, 35,
hwnd, (HMENU) 1, ((LPCREATESTRUCT)lParam)-
>hInstance, NULL);
CheckDlgButton(hwnd, 1, BST_CHECKED);
break;
}
case WM_COMMAND:
{
BOOL checked = IsDlgButtonChecked(hwnd, 1);
if (checked) {
CheckDlgButton(hwnd, 1, BST_UNCHECKED);
SetWindowText(hwnd, TEXT(""));
} else {
CheckDlgButton(hwnd, 1, BST_CHECKED);
SetWindowText(hwnd, title);
}
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we show or hide the window title depending on the state of the check box.
On windows, check box is a special kind of a button. We create a check box, if we set a
specific style to the button class. In our example, it is BS_CHECKBOX.
BOOL checked = IsDlgButtonChecked(hwnd, 1);
We determine the state of the check box using the IsDlgButtonChecked() function.
CheckDlgButton(hwnd, 1, BST_UNCHECKED);
We check and uncheck the check box using the CheckDlgButton() function.
SetWindowText(hwnd, TEXT(""));
The SetWindowText() function sets the title of the window.
Edit Control
Edit control is a rectangular child window. Edit control is used to enter and edit text. It can
be single line or multiline.
#include <windows.h>
#define ID_EDIT 1
#define ID_BUTTON 2
switch(msg)
{
case WM_CREATE:
hwndEdit = CreateWindow(TEXT("Edit"), NULL, WS_CHILD | WS_VISIBLE |
WS_BORDER,
50, 50, 150, 20, hwnd, (HMENU) ID_EDIT,
NULL, NULL);
hwndButton = CreateWindow(
TEXT("button"), TEXT("Set Title"),
WS_VISIBLE | WS_CHILD,
50, 100, 80, 25,
hwnd, (HMENU) ID_BUTTON, NULL, NULL);
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
len = GetWindowTextLength(hwndEdit) + 1;
GetWindowText(hwndEdit, text, len);
SetWindowText(hwnd, text);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Edit control"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
220, 220, 280, 200, 0, 0, hInstance, 0);
In our example, we have an edit control and a button. We can put some text into the edit
control. If we click on the button, the entered text will be displayed in the titlebar of the
main window.
if (HIWORD(wParam) == BN_CLICKED) {
len = GetWindowTextLength(hwndEdit) + 1;
GetWindowText(hwndEdit, text, len);
SetWindowText(hwnd, text);
}
The GetWindowTextLength() returns the text length entered. Notice, that we add 1 to the
length. This is to include the zero terminator. Try to omit it and see, what happens. The
GetWindowText() receives the text from the edit control. We use hwndEdit as an id. The
SetWindowText() sets the text for the window. In this context, it is a title of the main
window.
#include <windows.h>
#define ID_BLUE 1
#define ID_YELLOW 2
#define ID_ORANGE 3
HINSTANCE g_hinst;
COLORREF g_color;
g_hinst = hInstance;
RegisterClass(&wc);
hwnd = CreateWindow(wc.lpszClassName, TEXT("GroupBox"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 170, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
HDC hdc;
PAINTSTRUCT ps;
HBRUSH hBrush, holdBrush;
HPEN hPen, holdPen;
switch(msg)
{
case WM_CREATE:
CreateWindow(TEXT("button"), TEXT("Choose Color"),
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
10, 10, 120, 110, hwnd, (HMENU) 0, g_hinst, NULL);
CreateWindow(TEXT("button"), TEXT("Blue"),
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 30, 100, 30, hwnd, (HMENU)ID_BLUE , g_hinst, NULL);
CreateWindow(TEXT("button"), TEXT("Yellow"),
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 55, 100, 30, hwnd, (HMENU)ID_YELLOW , g_hinst,
NULL);
CreateWindow(TEXT("button"), TEXT("Orange"),
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 80, 100, 30, hwnd, (HMENU)ID_ORANGE , g_hinst,
NULL);
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
switch (LOWORD(wParam)) {
case ID_BLUE:
g_color = RGB(0, 76, 255);
break;
case ID_YELLOW:
g_color = RGB(255, 255, 0);
break;
case ID_ORANGE:
g_color = RGB(255, 123, 0);
break;
}
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hBrush = CreateSolidBrush(g_color);
hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
holdPen = SelectObject(hdc, hPen);
holdBrush = (HBRUSH) SelectObject(hdc, hBrush);
SelectObject(hdc, holdBrush);
SelectObject(hdc, holdPen);
DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we have a group box with three radio buttons. By clicking on the radio
button, we select a background color for the rectangle on the right.
CreateWindow(TEXT("button"), TEXT("Blue"),
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
20, 30, 100, 30, hwnd, (HMENU)ID_BLUE , g_hinst, NULL);
case ID_BLUE:
g_color = RGB(0, 76, 255);
break;
If we click on the radio button, we fill a global variable with a selected color. This variable
will be used to create a brush, that will fill the rectangle.
We invalidate the rectangle (in this case whole window), which will cause the client area to
be redrawn. This will launch a WM_PAINT message. During the WM_PAINT message, we
draw the rectangle. Drawing is explained in GDI chapter in more detail.
Figure: GroupBox, Radio boxes
ComboBox
A combo box is a combination of an edit box or static text and a list. A combo box is used
when we need to select an item from a list of available options.
#include <windows.h>
HINSTANCE g_hinst;
g_hinst = hInstance;
RegisterClass(&wc);
hwnd = CreateWindow(wc.lpszClassName, TEXT("Combo Box"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 270, 170, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
static HWND hwndCombo, hwndStatic;
const TCHAR *items[] = { TEXT("FreeBSD"), TEXT("OpenBSD"),
TEXT("Ubuntu"), TEXT("Solaris") };
int i;
LRESULT sel = 0;
switch(msg)
{
case WM_CREATE:
hwndCombo = CreateWindow(TEXT("combobox"), NULL,
WS_CHILD | WS_VISIBLE | CBS_DROPDOWN,
10, 10, 120, 110, hwnd, NULL, g_hinst, NULL);
break;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
SendMessage(hwndCombo, CB_SHOWDROPDOWN, (WPARAM) TRUE,
0);
}
if ( HIWORD(wParam) == CBN_SELCHANGE) {
sel = SendMessage(hwndCombo, CB_GETCURSEL, 0, 0);
SetWindowText(hwndStatic, items[sel]);
SetFocus(hwnd);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we put three controls on the window. A combo box, a button and a static text.
The static text displays the currently selected item from the combo box. It is used to
demonstrate the CBN_SELCHANGE combo box message. The button programatically opens
the combo box.
We fill the combo box with items. To add a string to the combo box, we send a
CB_ADDSTRING message.
If we select an item from the combo box, the window procedure receives the
WM_COMMAND message with the notification message CBN_SELCHANGE in the high-
order word of the wParam parameter.
We figure out the currently selected item. We send a CB_GETCURSEL message to the
combo box. The function returns the index of the currently selected item. We set the static
text to the currently selected string. Finally, we set focus to the main window. By default the
combo box has the focus. But it looks ugly, so I changed programatically the focus.
Progress bar
A progress bar is a control that is used, when we process lengthy tasks. It is animated so that
the user knows, that our task is progressing.
#include <windows.h>
#include <commctrl.h>
#define ID_BUTTON 1
#define ID_TIMER 2
HWND hwnd;
MSG msg ;
WNDCLASS wc = {0};
wc.lpszClassName = TEXT("Application");
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc ;
wc.hCursor = LoadCursor(0,IDC_ARROW);
g_hinst = hInstance;
RegisterClass(&wc);
hwnd = CreateWindow(wc.lpszClassName, TEXT("Progress bar"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 260, 170, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
INITCOMMONCONTROLSEX InitCtrlEx;
InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCtrlEx.dwICC = ICC_PROGRESS_CLASS;
InitCommonControlsEx(&InitCtrlEx);
switch(msg)
{
case WM_CREATE:
hwndPrgBar = CreateWindowEx(0, PROGRESS_CLASS, NULL,
WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
30, 20, 190, 25, hwnd, NULL, g_hinst, NULL);
CreateWindow(TEXT("button"), TEXT("Start"),
WS_CHILD | WS_VISIBLE,
85, 90, 80, 25, hwnd, (HMENU) 1, g_hinst, NULL);
case WM_TIMER:
SendMessage( hwndPrgBar, PBM_STEPIT, 0, 0 );
i++;
if ( i == 150 )
KillTimer(hwnd, ID_TIMER);
break;
case WM_COMMAND:
i = 1;
SendMessage( hwndPrgBar, PBM_SETPOS, 0, 0 );
SetTimer(hwnd, ID_TIMER, 5, NULL);
break;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we have a progress bar and a button. The button (re)starts to progress bar
control. We use a timer to update the progress bar.
We create a progress bar control with PROGRESS_CLASS class name and PBS_SMOOTH
style.
i = 1;
SendMessage( hwndPrgBar, PBM_SETPOS, 0, 0 );
SetTimer(hwnd, ID_TIMER, 5, NULL);
When we press the start button, we set the i value to 1, set the initial position of the progress
bar and start the timer. The timer will send periodically a WM_TIMER message to the
window procedure. Until it is killed.
case WM_TIMER:
SendMessage( hwndPrgBar, PBM_STEPIT, 0, 0 );
i++;
if ( i == 150 )
KillTimer(hwnd, ID_TIMER);
break;
During the WM_TIMER message, we update the progress bar by one step sending the
PBM_STEPIT message. We kill the timer, when the progress bar stops processing.
Figure: Progress bar
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
if(GetOpenFileName(&ofn))
{
// Do something usefull with the filename stored in szFileName
}
Note that we call ZeroMemory() on the struct in order to initialise it to 0. This is generally a
wise practice, as some APIs are very picky about members that you don't use being set to
NULL. This way you don't need to explicitely set each member that you don't use.
You can easily find out the meanings of the various members by looking them up in your
documentation. The lpstrFilter value points to a double-NULL terminated string, and you
can see from the example that there are several "\0" throughout it, including one at the end...
the compiler will add the second one at the end as it always does with string constants (that's
what you generally don't need to put them in yourself). The NULLs in this string break it up
into filters, each one is two parts. The first filter has the description "Text Files (*.txt)",
the wildcard isn't required here I just put it in because I felt like it. The next part is the actual
wildcard for the first filter, "*.txt". We do the same thing with the second filter except that
this is a generic filter for all files. You can add as many different filters as you'd like.
The lpstrFile points to the buffer we have allocated to store the name of the file, since
filenames can't be larger than MAX_PATH this is the value that I've chosen for the buffer size.
The flags indicate that the dialog should only allow the user to enter filenames that already
exist (since we want to open them, not create them) and to hide the option to open the file in
readonly mode, which we aren't going to support. Finally we provide a default extention, so if
the user types in "foo" and the file is not found, it will try to open "foo.txt" before finally
giving up.
To select a file for saving instead of opening, the code is nearly the same, except for calling
GetSaveFileName() we need only change the flags member to options more suitable for
saving.
In this case we no longer want to require the file exist, but we do want the directory to exist
since we aren't going to try and create it first. We'll also prompt the user if they select an
existing file to make sure they want to overwrite it.
lStructSize
Specifies the length, in bytes, of the structure.
Basically what this means is that as of Windows 2000 they added some members to this
struct, and so it's size changed. If the code above doesn't work for you it's possibly because
the size that your compiler used and the size that your operating system (ie. Windows 98,
Windows NT4) expected were different and so the call failed. If this happens, try using
OPENFILENAME_SIZE_VERSION_400 instead of sizeof(ofn). Thanks to people that pointed
this out to me.
However in windows all of these methods ultimately call the Win32 API functions, which are
what I will use here. If you are already comfortable using file IO with another method it
should be fairly easy to pick up, or if you want simply use your method of choice to access
files.
To open files, you can use OpenFile() or CreateFile(). MS recommends using only
CreateFile() as OpenFile() is now "obsolete". CreateFile() is a much more versatile
function and provides a great deal of control over the way you open files.
Reading
Say for example you have allowed the user to select a file using GetOpenFileName()...
There is a complete function to read a text file into an edit control. It takes as paramters the
handle to the edit control and the name of the file to read in. This perticular function has a fair
bit of error checking, file IO is one place where a lot of things can go wrong, and so you need
to be on the lookout for errors.
Note the variable dwRead. We don't use it except as a paramter in ReadFile(). This
parameter MUST be provided, the call will fail without it.
Once we've opened the file and chacked to see that CreateFile() succeeded, we check the
size of the file so we'll know how much memory we need to allocate in order to read the
entire thing. We then allocate the memory, check to make sure the allocation succeeded, and
then call ReadFile() to load the contents from disk into our memory buffer. The API file
functions have no concept of Text Files so they won't do things like read a single line of text,
or add NULL terminators to the end of our strings. This is why we've allocated an extra byte
and after we read in the file we add the NULL ourselves so that we can then pass the memory
buffer as a string to SetWindowText().
Once all that has succeeded we set out success variable to TRUE, and clean up as we reach the
end of the function, freeing the memory buffer and closing the file handle before finally
returning to the caller.
Writing
BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
HANDLE hFile;
BOOL bSuccess = FALSE;
dwTextLength = GetWindowTextLength(hEdit);
// No need to bother if there's no text.
if(dwTextLength > 0)
{
LPSTR pszText;
DWORD dwBufferSize = dwTextLength + 1;
Very similar to reading files, the function to write files has a few changes. First of all when
we call CreateFile() we specify that we want Read access, that the file should always be
created new (and if it exists it will be erased as it's opened) and that if it doesn't exist, it will
be created with the normal file attributes.
Next we get the length of the memory buffer needed from the edit control, since this is the
source of the data. Once we've allocated the memory, we request the string from the edit
control using GetWindowText() and then write it to the file with WriteFile(). Again, like
with ReadFile() the parameter that returns how much was actually written is required, even
though we don't use it.
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Display", ID_FILE_DISPLAY
MENUITEM "&Clear Display", ID_CLEAN_DISPLAY
MENUITEM "&Read", ID_FILE_READ
MENUITEM "&Write", ID_FILE_WRITE
MENUITEM "E&xit", ID_FILE_EXIT
END
POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END
END
//app_two.cpp
#include <windows.h>
#include "resource.h"
char g_szClassName[] = "myWindowClass";//Name of window class
char szFileName[MAX_PATH] = "";
char szFileNameWrite[MAX_PATH] = "";
DWORD dwFileSize;
HANDLE hFile;
//LPCTSTR pszFileNameWrite;
//LPCTSTR pszFileName;
LPSTR pszFileText;
OPENFILENAME ofn;
static HWND hwndEdit;
BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
// HANDLE hFile;
BOOL bSuccess = FALSE;
int error;
// GlobalFree((HGLOBAL)pszFileNameWrite);
ZeroMemory(&ofn, sizeof(ofn));
if(GetSaveFileName(&ofn))
{
hFile = CreateFile(szFileNameWrite, GENERIC_WRITE, 0, NULL,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwTextLength;
dwTextLength = GetWindowTextLength(hEdit);
// No need to bother if there's no text.
if(dwTextLength > 0)
{
LPSTR pszText;
DWORD dwBufferSize = dwTextLength + 1;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
// OPENFILENAME ofn;
switch(msg)
{
case WM_CREATE:
//This create the Edit Class Window
hwndEdit = CreateWindow("EDIT", // predefined class
NULL, // no window title
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
0, 0, 0, 0, // set size in WM_SIZE message
hwnd, // parent window
(HMENU) ID_EDITCHILD, // edit control ID
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
NULL); // pointer not needed
case WM_SIZE:
// Make the edit control the size of the window's client area.
MoveWindow(hwndEdit,
0, 0, // starting x- and y-
coordinates
LOWORD(lParam), // width of client area
HIWORD(lParam), // height of client area
TRUE); // repaint window
return 0;
case WM_COMMAND:
switch(wParam)
{
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
case ID_STUFF_GO:
MessageBox(NULL,"Go Exit Selection"
,"ID_STUFF" ,MB_ICONEXCLAMATION | MB_OK);
break;
case ID_FILE_DISPLAY:
//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) //Entry of program
{
WNDCLASSEX wc; //Create WNDCLASSEX object
In this section of the Winapi C tutorial, we will talk about more advanced windows controls.
ListBox
A list box is a simple list control, from which user can select one or more items. Selected
items are marked.
#include <windows.h>
#include <strsafe.h>
#define IDC_LIST 1
#define IDC_STATIC 2
HINSTANCE g_hinst;
typedef struct
{
TCHAR name[30];
TCHAR job[20];
int age;
} Friends;
Friends friends[] =
{
{TEXT("Erika"), TEXT("waitress"), 18},
{TEXT("Thomas"), TEXT("programmer"), 25},
{TEXT("George"), TEXT("police officer"), 26},
{TEXT("Michael"), TEXT("producer"), 38},
{TEXT("Jane"), TEXT("steward"), 28},
};
g_hinst = hInstance;
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("List Box"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 340, 200, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
case WM_CREATE:
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_LIST) {
if (HIWORD(wParam) == LBN_SELCHANGE) {
sel = (int) SendMessage(hwndList, LB_GETCURSEL, 0, 0);
StringCbPrintf(buff, ARRAYSIZE(buff), TEXT("Job:
%s\nAge: %d"),
friends[sel].job, friends[sel].age);
SetWindowText(hwndStatic, buff);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
In this example, we display a list box control and a static text control. By selecting a person
from a list box, we display his job and age in the static control.
hwndList = CreateWindow(TEXT("listbox") , NULL, WS_CHILD | WS_VISIBLE |
LBS_NOTIFY,
10, 10, 150, 120, hwnd,(HMENU) IDC_LIST, g_hinst, NULL);
A basic list box is created with listbox window class and LBS_NOTIFY message.
for (i = 0; i < ARRAYSIZE(friends); i++) {
SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM) friends[i].name);
}
We fill the list box. To do this, we send multiple LB_ADDSTRING messages to the list box
control.
if (HIWORD(wParam) == LBN_SELCHANGE) {
sel = (int) SendMessage(hwndList, LB_GETCURSEL, 0, 0);
StringCbPrintf(buff, ARRAYSIZE(buff), TEXT("Job: %s\nAge: %d"),
friends[sel].job, friends[sel].age);
SetWindowText(hwndStatic, buff);
}
If we select an item from a list box, the window procedure receives a LBN_SELCHANGE
message. First we determine the currently selected item by sending a LB_GETCURSEL
message to the list box. Then we copy the job name and age from the friends structure to the
buff variable. Finally, we set the static text with SetWindowText() function call.
HINSTANCE g_hinst;
LRESULT g_pos = 150;
HWND hwnd;
MSG msg ;
WNDCLASS wc = {0};
wc.lpszClassName = TEXT("Application");
wc.hInstance = hInstance ;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc ;
wc.hCursor = LoadCursor(0, IDC_ARROW);
g_hinst = hInstance;
RegisterClass(&wc);
hwnd = CreateWindow(wc.lpszClassName, TEXT("Burning control"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
100, 100, 400, 250, 0, 0, hInstance, 0);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam
)
{
InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCtrlEx.dwICC = ICC_BAR_CLASSES;
InitCommonControlsEx(&InitCtrlEx);
switch(msg)
{
case WM_CREATE:
rwc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
rwc.style = CS_HREDRAW;
rwc.lpfnWndProc = PanelProc;
rwc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClass(&rwc);
hwndBurn = CreateWindowEx(WS_EX_STATICEDGE ,
TEXT("BurningControl"), NULL,
WS_CHILD | WS_VISIBLE, 0, 330, 490, 30, hwnd, (HMENU)1, NULL,
NULL);
case WM_SIZE:
SetWindowPos(hwndBurn, NULL, 0, HIWORD(lParam)-30,
LOWORD(lParam), 30, SWP_NOZORDER);
break;
case WM_HSCROLL:
g_pos = SendMessage(hwndTrack, TBM_GETPOS, 0, 0);
InvalidateRect(hwndBurn, NULL, TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK PanelProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam )
{
HBRUSH hBrushYellow, hBrushRed, holdBrush;
HPEN hPen, holdPen;
HFONT hFont, holdFont;
PAINTSTRUCT ps;
RECT rect, rect2;
TCHAR *cap[] = { TEXT("75"), TEXT("150"), TEXT("225"), TEXT("300"),
TEXT("375"), TEXT("450"), TEXT("525"), TEXT("600"), TEXT("675")};
HDC hdc;
int till;
int step, full;
int i;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
SelectObject(hdc, holdPen);
rect2.bottom = 28;
rect2.top = 8;
rect2.left = i*step-10;
rect2.right = i*step+10;
SetBkMode(hdc, TRANSPARENT) ;
DrawText(hdc, cap[i-1], strlen(cap[i-1]), &rect2, DT_CENTER);
}
SelectObject(hdc, holdBrush);
DeleteObject(hBrushYellow);
DeleteObject(hBrushRed);
DeleteObject(hPen);
SelectObject(hdc, holdFont);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we display a trackbar control and our custom burning control. The trackbar
control is used to control the state of the burning control.
The burning control is a simple window. It is placed on the bottom of the parent window. It is
completely drawn during the WM_PAINT message. The lines, text and background is drawn
using the GDI function calls.
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Simple"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 250, 150, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static int w=250, h=150;
int x, y;
int i;
switch(msg)
{
case WM_SIZE:
GetClientRect(hwnd, &rect);
w = LOWORD(lParam) + 1;
h = HIWORD(lParam) + 1;
InvalidateRect(hwnd, &rect, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
for (i = 0; i<1000; i++) {
x = (rand() % w) + 1;
y = (rand() % h) + 1;
SetPixel(hdc, x, y, RGB(255, 0, 0));
}
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example we display randomly 1000 red pixels on the client area of the window. If we
resize the window, the pixels are redrawn.
Figure: pixels
Rectangle
To draw a rectangle, we use the Rectangle() function.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Rectangle"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 250, 200, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, 50, 50, 200, 100);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
The outline of the rectangle is drawn using the current pen. The background is drawn using
the current brush.
BOOL Rectangle( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect );
The rectangle is drawn using the Rectangle function. We draw the rectangle using two points.
Top left point and bottom right point.
Figure: rectangle
Pen
Pen is an elementary graphics object. It is used to draw lines, curves and outlines of
rectangles, ellipses, polygons or other shapes.
HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);
The CreatePen() function creates a logical pen with a specified style, width and color.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Lines"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 250, 180, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
HPEN holdPen;
HPEN hPen1;
HPEN hPen2;
HPEN hPen3;
HPEN hPen4;
HPEN hPen5;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hPen1 = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
hPen2 = CreatePen(PS_DASH, 1, RGB(0, 0, 0));
hPen3 = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
hPen4 = CreatePen(PS_DASHDOT, 1, RGB(0, 0, 0));
hPen5 = CreatePen(PS_DASHDOTDOT, 1, RGB(0, 0, 0));
SelectObject(hdc, hPen2);
MoveToEx(hdc, 50, 50, NULL);
LineTo(hdc, 200, 50);
SelectObject(hdc, hPen2);
MoveToEx(hdc, 50, 70, NULL);
LineTo(hdc, 200, 70);
SelectObject(hdc, hPen3);
MoveToEx(hdc, 50, 90, NULL);
LineTo(hdc, 200, 90);
SelectObject(hdc, hPen4);
MoveToEx(hdc, 50, 110, NULL);
LineTo(hdc, 200, 110);
SelectObject(hdc, holdPen);
DeleteObject(hPen1);
DeleteObject(hPen2);
DeleteObject(hPen3);
DeleteObject(hPen4);
DeleteObject(hPen5);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
In our example, we draw 5 different lines. Using 5 different pen styles.
SelectObject(hdc, hPen1);
To activate a pen, we call the SelectObject() function.
MoveToEx(hdc, 50, 30, NULL);
LineTo(hdc, 200, 30);
To draw lines, we use MoveToEx() and LineTo() functions.
DeleteObject(hPen1);
DeleteObject(hPen2);
DeleteObject(hPen3);
DeleteObject(hPen4);
DeleteObject(hPen5);
Figure: lines
Brush
Brush is an elementary graphics object. It is used to paint the background of graphics shapes,
such as rectangles, ellipses or polygons. A brush can be a solid colour a hatch or a custom
bitmap pattern.
In the following example, we create 4 rectangles filled with 4 different solid colors.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Solid Brush"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 220, 240, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
HDC hdc;
PAINTSTRUCT ps;
HPEN holdPen, hPen;
HBRUSH holdBrush, hBrush1, hBrush2, hBrush3, hBrush4;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
holdPen = SelectObject(hdc, hPen);
SelectObject(hdc, holdPen);
SelectObject(hdc, holdBrush);
DeleteObject(hPen);
DeleteObject(hBrush1);
DeleteObject(hBrush2);
DeleteObject(hBrush3);
DeleteObject(hBrush4);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
hBrush1 = CreateSolidBrush(RGB(121, 90, 0));
Here we create a solid color brush.
Hatch brushes
There are six predefined hatch brushes availabel. In our example, we show all of them.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Hatch brush"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 300, 220, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
HDC hdc;
PAINTSTRUCT ps;
HPEN holdPen, hPen;
HBRUSH holdBrush, hBrush1, hBrush2, hBrush3, hBrush4, hBrush5, hBrush6;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hPen = CreatePen(PS_NULL, 1, RGB(0, 0, 0));
holdPen = SelectObject(hdc, hPen);
SelectObject(hdc, holdPen);
SelectObject(hdc, holdBrush);
DeleteObject(hPen);
DeleteObject(hBrush1);
DeleteObject(hBrush2);
DeleteObject(hBrush3);
DeleteObject(hBrush4);
DeleteObject(hBrush5);
DeleteObject(hBrush6);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
This example is very similar to the previous one. We only use a new function call
CreateHatchBrush()
Shapes
Shapes are more sophisticated geometrical objects. We will draw various geometrical shapes
in the following example.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Shapes"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 390, 230, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
const POINT polygon[10] = { 30, 145, 85, 165, 105, 110, 65, 125, 30, 105 };
const POINT bezier[4] = {280, 160, 320, 160, 325, 110, 350, 110};
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
Figure: Shapes
Fonts
Text can be drawn on the window using various fonts. A font is a set of type characters of a
particular typeface design and size. (answers.com) Various typefaces include Helvetica,
Georgia, Times or Verdana.
#include <windows.h>
RegisterClass(&wc);
CreateWindow( wc.lpszClassName, TEXT("Sonnet 55"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
100, 100, 390, 350, NULL, NULL, hInstance, NULL);
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
HDC hdc;
PAINTSTRUCT ps;
DWORD color;
switch(msg)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
color = GetSysColor(COLOR_BTNFACE);
SetBkColor(hdc, color);
SelectObject(hdc, holdFont);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
color = GetSysColor(COLOR_BTNFACE);
SetBkColor(hdc, color);
By default, if we draw some text on the client area of the window, the background is set to
white color. We can change this by setting the backround color using the SetBkColor()
function. I used the typical Windows greyish color. The GetSysColor function is used to get
the system colors used in buttons, title or backround of window controls.
hFont = CreateFont(15, 0, 0, 0, FW_MEDIUM, 0, 0, 0, 0, 0, 0, 0, 0,
TEXT("Georgia"));
Here we create a font object. There are 14 parameters. We don't have to specify all of them. I
used only the font size, font weight and fontface parameters.
TextOut(hdc, 50, 20, verse1, lstrlen(verse1));
The text is drawn onto the window using the TextOut() function.
Figure: Fonts
Setting up
Before we get things animated, we need to set up a
structure to store the position of the ball between updates.
This struct will store the current position and size of the
ball, as well as the delta values, how much we want it to
move each frame.
int dx;
int dy;
}BALLINFO;
BALLINFO g_ballInfo;
We've also defined a constant BALL_MOVE_DELTA which is how far we want the ball to move
on each update. The reason we store deltas in the BALLINFO structure as well is that we want
to be able to move the ball left or right and up and down independantly, BALL_MOVE_DELTA is
just a handy name to give the value so we can change it later if we want.
BITMAP bm;
GetObject(g_hbmBall, sizeof(bm), &bm);
ZeroMemory(&g_ballInfo, sizeof(g_ballInfo));
g_ballInfo.width = bm.bmWidth;
g_ballInfo.height = bm.bmHeight;
g_ballInfo.dx = BALL_MOVE_DELTA;
g_ballInfo.dy = BALL_MOVE_DELTA;
The ball starts off in the top left corner, moving to the right and down according to the dx and
dy members of BALLINFO.
We've set the timer to elapse every 50 milliseconds, which results in approximately 20 frames
per second. Approximately because like I said, SetTimer() is a little inaccurate, but this isn't
critical code, and a few milliseconds here or there won't kill us.
Animating in WM_TIMER
Now when we get WM_TIMER we want to calculate the new position for the ball and draw it's
updated position.
case WM_TIMER:
{
RECT rcClient;
HDC hdc = GetDC(hwnd);
GetClientRect(hwnd, &rcClient);
UpdateBall(&rcClient);
DrawBall(hdc, &rcClient);
ReleaseDC(hwnd, hdc);
}
break;
I've put the code for updating and drawing the ball in their own functions. This is good
practice, and it lets us draw the ball from either WM_TIMER or WM_PAINT without duplicating
code, note that the method we use to get the HDC in each case is different, so it's best to leave
this code in the message handlers and pass the result into the DrawBall() function.
if(g_ballInfo.x < 0)
{
g_ballInfo.x = 0;
g_ballInfo.dx = BALL_MOVE_DELTA;
}
else if(g_ballInfo.x + g_ballInfo.width > prc->right)
{
g_ballInfo.x = prc->right - g_ballInfo.width;
g_ballInfo.dx = -BALL_MOVE_DELTA;
}
if(g_ballInfo.y < 0)
{
g_ballInfo.y = 0;
g_ballInfo.dy = BALL_MOVE_DELTA;
}
else if(g_ballInfo.y + g_ballInfo.height > prc->bottom)
{
g_ballInfo.y = prc->bottom - g_ballInfo.height;
g_ballInfo.dy = -BALL_MOVE_DELTA;
}
}
All this does is some basic math, we add the delta value to the x position to move the ball. If
the ball goes outside the client area, move it back in range and change the delta value to the
opposite direction so that the ball "bounces" off the sides.
void DrawBall(HDC hdc, RECT* prc)
{
HDC hdcBuffer = CreateCompatibleDC(hdc);
HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc-
>bottom);
HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);
SelectObject(hdcMem, g_hbmBall);
BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width,
g_ballInfo.height, hdcMem, 0, 0, SRCPAINT);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
SelectObject(hdcBuffer, hbmOldBuffer);
DeleteDC(hdcBuffer);
DeleteObject(hbmBuffer);
}
This is essentially the same drawing code as the past few examples, with the exception that it
gets the position and dimentions of the ball from the BALLINFO structure. There is however
one important difference...
Double Buffering
When doing your drawing directly to the HDC of the window, it's entirely possible that the
screen will get updated before you're done... for example after you draw the mask and before
you draw the colour image over top, the user might see a flicker of the back background
before your program has a chance to draw over it in colour. The slower your computer and
the more drawing operations that you do, the more flicker will be apparent and eventually it
will look like a big jumbled mess.
This is terribly distracting, and we can solve it simply by doing all the drawing in memory
first, and then copying the completed masterpiece to the screen in a single BitBlt() so that
the screen is updated directly from the old image, to the complete new image with none of the
individual operations visible.
To do this, we create a temporary HBITMAP in memory that is the exact size of the area we are
going to draw to on the screen. We also need an HDC so that we can BitBlt() to the bitmap.
Now that we have a place to draw to in memory, all of the drawing operations use hdcBuffer
instead of hdc (the window) and the results are stored on the bitmap in memory untill we are
complete. We can now copy the whole thing over to the window in one shot.
BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);
In this example I am creating and destroying the bitmap used for double buffering each
frame, I did this basically because I wanted to be able to size the window so it's easier to just
always create a new buffer than to track when the window position changes and resize the
buffer. It would be more efficient to create a global double buffer bitmap and either not allow
the window to resize or only resize the bitmap when the window resized, instead of creating
it and destroying it all the time. It's up to you to implement this if you want to optimize the
drawing for a game or something.
Loading Fonts
The Win32 GDI has some remarkable capabilites for dealing with vastly different typefaces,
styles, languages and characters sets. One of the drawbacks of this is that dealing with fonts
can look rather intimidating to the newcomer. CreateFont(), the primary API when it comes
to fonts, has 14 parameters for specifying height, style, weight, family, and various other
attributes.
Fortunately, it's not really has hard as it might appear, and a large portion of the work
involved is taken care of my sensible default values. All but 2 of the parameters to
CreateFont() can be set to 0 or NULL, and the system will simply use a default value giving
you a plain ordinary font.
CreateFont() creates an HFONT, a handle to a Logical Font in memory. The data held by this
handle can be retreived into a LOGFONT structure using GetObject() just as a BITMAP struct
can be filled from an HBITMAP.
The members of the LOGFONT are identical to the parameters to CreateFont() and for
convenience you can create a font directly from an existing LOGFONT structure using
CreateFontIndirect(). This is very handy, since it makes it simple to create a new font
from an existing font handle when you only want to alter certain aspects of it. Use
GetObject() to fill a LOGFONT, alter the members that you wish, and create a new font with
CreateFontIndirect().
HFONT hf;
HDC hdc;
long lfHeight;
hdc = GetDC(NULL);
lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(NULL, hdc);
hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0,
"Times New Roman");
if(hf)
{
DeleteObject(g_hfFont);
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK |
MB_ICONEXCLAMATION);
}
This is the code used to create the font in the example image. This is Times New Roman at 12
Point with the Italics style set. The italics flag is the 6th parameter to CreateFont() which
you can see we have set to TRUE. The name of the font we want to use is the last parameter.
The one bit of trickery in this code is the value used for the size of the font, the lfHeight
parameter to CreateFont(). Usually people are used to working with Point sizes, Size 10,
Size 12, etc... when dealing with fonts. CreateFont() however doesn't accept point sizes, it
wants Logical Units which are different on your screen than they are on your Printer, and
even between Printers and screens.
The reason this situation exists is because the resolution of different devices is so vastly
different... Printers can easily display 600 to 1200 pixels per inch, while a screen is lucky to
get 200... if you used the same sized font on a printer as on a screen, you likely wouldn't even
be able to see individual letters.
All we have to do is convert from the point size we want, into the appropriate logical size for
the device. In this case the device is the screen, so we get the HDC to the screen, and get the
number of logical pixels per inch using GetDeviceCaps() and slap this into the formula so
generously provided in MSDN which uses MulDiv() to convert from our pointsize of 12 to
the correct logical size that CreateFont() expects. We store this in lfHeight and pass it as
the first parameter to CreateFont().
Default Fonts
When you first call GetDC() to get the HDC to your window, the default font that is selected
into it is System, which to be honest isn't all that attractive. The simplest way to get a
reasonable looking font to work with (without going through the CreateFont() hassle) is to
call GetStockObject() and ask for the DEFAULT_GUI_FONT.
This is a system object and you can get it as many times as you want without leaking
memory, and you can call DeleteObject() on it which won't do anything, which is good
because now you don't need to keep track of whether your font is one from CreateFont() or
GetStockObject() before trying to free it.
Drawing Text
Now that we have a handy-dandy font, how do we get some text on the screen? This is
assuming that we don't just want to use an Edit or Static control.
Your basic options are TextOut() and DrawText(). TextOut() is simpler, but has less
options and doesn't do word wrapping or alignment for you.
char szSize[100];
char szTitle[] = "These are the dimensions of your client area:";
HFONT hfOld = SelectObject(hdc, hf);
SetBkColor(hdc, g_rgbBackground);
SetTextColor(hdc, g_rgbText);
if(g_bOpaque)
{
SetBkMode(hdc, OPAQUE);
}
else
{
SetBkMode(hdc, TRANSPARENT);
}
SelectObject(hdc, hfOld);
First thing we do is use SelectObject() to get the font we want to use into our HDC and
ready for drawing. All future text operations will use this font untill another one is selected
in.
Next we set the Text and Background colours. Setting the background colour doesn't actually
make the whole background this colour, it only affects certain operations (text being one of
them) that use the background colour to draw with. This is also dependant on the current
Background Mode. If it is set to OPAQUE (the default) then any text drawn is filled in behing
with the background colour. If it is set to TRANSPARENT then text is drawn without a
background and whatever is behind will show through and in this case the background colour
has no effect.
Now we actually draw the text using DrawText(), we pass in the HDC to use and the string to
draw. The 3rd parameter is the length of the string, but we've passed -1 because DrawText()
is smart enough that it will figure out how long the text is itself. In the 4th parameter we pass
in prc, the pointer to the client RECT. DrawText() will draw inside this rectangle based on the
other flags that you give it.
In the first call, we specify DT_WORDBREAK, which defaults to aligned to the top left, and will
wrap the text it draws automatically at the edge of the rectangle... very useful.
For the second call, we're only printing a single line without wrapping, and we want it to be
centered horizontally as well as vertically (which DrawText() will do only when drawing a
single line).
Client Redraw
Just a note about the example program... when the WNDCLASS is registered I have set the
CS_VREDRAW and CS_HREDRAW class styles. This causes the entire client area to be redrawn if
the window is resized, whereas the default is to only redraw the parts that have changed. That
looks really bad since the centered text moves around when you resize and it doesn't update
like you'd expect.
Choosing Fonts
In general, any program that deals with fonts will want to let the user choose their own font,
as well as the colour and style attribute to use when displaying it.
Like the common dialogs for getting open and save file names, there is a common dialog for
choosing a font. This is, oddly enough, called ChooseFont() and it works with the
CHOOSEFONT structure for you to set the defaults it should start with as well as returning the
final result of the users selection.
if(ChooseFont(&cf))
{
HFONT hf = CreateFontIndirect(&lf);
if(hf)
{
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK |
MB_ICONEXCLAMATION);
}
g_rgbText = cf.rgbColors;
}
}
The hwnd in this call is simply the window you want to use as the parent for the font dialog.
The easiest way to use this dialog is in conjunction with an existing LOGFONT structure, which
is most likely from whichever HFONT you are currently using. We set the lpLogFont member
of the structure to point to the LOGFONT that we just filled with our current information and
also added the CF_INITTOLOGFONTSTRUCT flag so that ChooseFont() knows to use this
member. The flag CF_EFFECTS tells ChooseFont() to allow the user to select a colour, as
well as Underline and Strikeout attributes.
Oddly enough, the Bold and Italics styles don't count as effects, they are considered part of
the font itself and in fact some fonts only come in Bold or Italics. If you want to check or
prevent the user from selecting a bold or italic font you can check the lfWeight and
lfItalic members of the LOGFONT respectively, after the user has made their selection. You
can then prompt the user to make another selection or something change the members before
calling CreateFontIndirect().
The colour of a font is not associated with an HFONT, and therefor must be stored seperately,
the rgbColors member of the CHOOSEFONT struct is used both to pass in the initial colour and
retreive the new colour afterward.
CF_SCREENFONTS indicates that we want fonts designed to work on the screen, as opposed to
fonts that are designed for printers. Some support both, some only one or the other.
Depending on what you're going to be using the font for, this and many other flags can be
found in MSDN to limit exactly which fonts you want the user to be able to select.
Choosing Colours
In order to allow the user to change just the colour of the font, or to let them pick a new
colour for anything at all, there is the ChooseColor() common dialog. This is the code used
to allow the user to select the background colour in the example program.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};
void DoSelectColour(HWND hwnd)
{
CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};
if(ChooseColor(&cc))
{
g_rgbBackground = cc.rgbResult;
}
}
This is fairly straightforward, again we're using the hwnd parameter as the parent to the
dialog. The CC_RGBINIT parameter says to start off with the colour we pass in through the
rgbResult member, which is also where we get the colour the user selected when the dialog
closes.
The g_rgbCustom array of 16 COLORREFs is required to store any values the user decides to
put into the custom colour table on the dialog. You could potentially store these values
somewhere like the registry, otherwise they will simply be lost when your program is closed.
This parameter is not optional.
Control Fonts
Something else you might want to do at some point is change the font on the controls on your
dialog or window. This is usually the case when using CreateWindow() to create controls as
we've done in previous examples. Controls like windows use System by default, so we used
WM_SETFONT to set a new font handle (from GetStockObject()) for the control to use. You
can use this method with fonts you create from CreateFont() as well. Simply pass the font
handle as wParam and set lParam to TRUE to make the control redraw.
I've done this in previous examples, but it makes sense to mention it here because it's relevant
and very short:
Where hfFont is of course the HFONT you want to use, and IDC_OF_YOUR_CONTROL is the ID
of whichever control you want to change the font of.
Books
If you expect anyone online to treat you with respect while you are learning, you NEED to
get a good book to learn from. We're here to provide direction and explain things that need
explaining, not to be your librarian or teach you step by step.
You can find more recommended books and links to buy at the #Winprog Store.
Programming Windows
by Charles Petzold. The book to get on Win32 API. If you want to write programs
using just the API (which is what this tutorial covers), you need this book.
Programming Windows with MFC
by Jeff Prosise. If you want to venture into MFC (AFTER becoming fully
accustomed to using the Win32 API), this is the book for you. If you don't like MFC
but intend on getting a job doing windows developement, get this anyway, it's better
to know than not.
Programming Applications for Windows
by Jeffrey Richter. Not for newbies, if you want to be up on managing processes and
threads, dlls, windows memory management, exception handling, and hooking into
the system, then this is the book for you.
Visual C++ Windows Shell Programming
by Dino Esposito. For anyone interested in the visual and user-friendly aspects of
windows, this book covers writing extentions to the windows shell, working
efficiently with files and drag and drop, customizing the taskbar and windows
explorer, and numerous other tricks. Well worthwhile for anyone writing GUI apps in
windows.
Network Programming for Microsoft Windows
Up to date information on network programming, including NetBIOS, mailslots and
pipes, and of course the ever important windows sockets, complete with winsock2 and
raw sockets. Also contains specific information on the various windows platforms
including 2000 and CE.
Links
MSDN Online
This site has references for all imaginable Microsoft technologies, including full
Win32 API and MFC documentation. If this didn't come with your compiler (ie.
VC++) then the completely free online site will provide you with the required
information. People will get really pissed off if you ask questions you could answer
by doing a simple search on MSDN.
#winprog homepage
See FAQ and Store
Getting It
After version 2.0 of the tutorial was written, Microsoft has subsequently made it so easy to
download their C++ development environment that it's no longer even worth writing
instructions for.
This should far and away be your first choice for a Win32 development environment. It's
free, it's complete, it is superbly documented by Microsoft and third party authors, and is
widely supported by the Win32 community.
From the Visual Studio Express FAQ: You are even allowed to use it for commercial
purposes.
In this specific case, it means one of two things. Either you are trying to write a Win32 GUI
application (or non-console application) and accidently compiled it as a Console application...
or you really are trying to compile a console application and didn't write or properly compile
in a main() function.
Generally the first is the most common, if you specify Win32 Console as the project type in
VC++ when you create your project you will get this error. You will also likely get it if you
try to compile from the command line using BC++ but you neglect to specify the correct
parameters to tell it to make a Win32 GUI application instead of a console app which is the
default.
Fixing
If you're using VC++ re-create your project and select the Win32 Application project type
(NOT "Console").
If you're using BC++ command line compiler, use -tW to specify a windows application.
VC++ (and most compilers) will automatically compile a file with a .cpp extension as C++
code, and a file with a .c extension as C code. If you have added the tutorial code to a .cpp
file, this is the most likely reason of getting this error.
If you're compiling code not from this tutorial, I can't guarantee that it's correct and therefor it
may actually be an error that needs resolving. You'll have to use your own judgement to
determine if it's safe to cast the value and remove the error, or if you are actually trying to
make a variable be something it's not.
Fixing
If you want to use C, simply rename your file from .cpp to .c. Otherwise, simply add a cast,
all of the code in the tutorial will work without any other changes when compiled as C++.
If you add a Common Control to a dialog and it fails to display, you most likely failed to call
InitCommonControls() before running your dialog, or perhaps at all. The best place to call
it is first thing in WinMain(). Calling it in WM_INITDIALOG is too late, since the dialog will
fail before it reaches this point and it will never get called.
Some people and documentation may tell you that InitCommonControls() is deprecated and
you should use InitCommonControlsEx(). Feel free to do this if you want,
InitCommonControls() is just simpler and there's nothing wrong with using it.
25. Why you should learn the API
before MFC
The Controversy
Too many people come on to IRC and ask "What is better, MFC or API?" and too many
people are willing to say "MFC sucks" or "API sucks" either because of traumatic events
involving one or the other in early childhood, or because everyone else is saying it.
And so on...
My Answer
My opinion, although by no means the only one, is that you should use the right framework
for the right job.
First of all a clarification on what the API and MFC are. API is a generic term meaning
Application Programming Interface, however in the context of Windows programming, it
means specifically the Windows API, which is the lowest level of interaction between
applications and the windows operating system. Drivers of course have even lower levels,
and different sets of function calls to work with, but for the vast majority of windows
development this is not an issue. MFC is a Class Library, it's a bunch of C++ classes that
have been written to reduce the amount of work it takes to do certain things with the API. It
also introduces an (arguably) Object Oriented framework into the application that you can
either take advantage of or ignore, which is what most beginners do since the framework isn't
really aimed at writing MP3 players, IRC clients or games.
Every program, whether it is written with MFC, Delphi, Visual Basic, perl, or any other
wacked out language or framework you can think of, is eventually built upon the API. In
many cases this interaction is hidden, so you don't deal directly with the API, the runtime and
support libraries do it for you. Some people ask, "MFC can do Blah Blah Blah, can the API?"
The answer is that MFC can only do what the API can do, because it's built on top of it.
However doing things yourself with the API may take considerably more code than using the
pre-written MFC classes.
So what is the right framework? For starters, for people that are just learning to program, I
strongly believe that you should work with the API untill you are comfortable with the way
windows applications work and you understand all of the basic mechanics behind things like
the message loop, GDI, controls, and maybe even multithreading and sockets. This way you
will understand the fundamental building blocks of all windows applications, and can apply
this common knowledge to MFC, Visual Basic, or whatever other framework you choose to
work with later. It's also important because these other frameworks don't support everything
that the API does, simply because it does a whole lot and they can't necessarily support all of
the arcane little things that most people won't use. So when you finally do need to use them
you need to add it yourself, you can't rely on the framework to do it for you and if you don't
understand the API this could be quite the chore.
But isn't MFC easier? In a certain sense it's easier in that many common tasks are done for
you, thus reducing the amount of code that you need to actually type. However, less code
does not mean "easier" when you don't understand the code you DO need to write, or how all
of the code that is there to support you actually works. Generally beginners who use the
wizards to start there applications have no idea what most of the generated code does, and
spend a great deal of time trying to figure out where to add things, or what changes to make
to acheive a certain result. If you start your programs from scratch, either in the API or with
MFC, then you know where everything is because you put it there, and you will only use
features that you understand.
Another important factor is that most people that are learing the Win32 API for the first time
don't already have a strong base in C++. To try and comprehend windows programming with
MFC and learn C++ at the same time can be a monumental task. Although it's not impossible,
it will take you considerably longer to become productive than if you already knew either
C++ or the API.
So basically...
What it comes down to is that I think you should learn the API untill you feel comfortable
with it, and then try out MFC. If it seems like it's making sense to you and saving you time,
then by all means use it.
However, and this is important... if you work with MFC without understanding the API and
then ask for help with something, and the answer you get is stated using the api (such as "Use
the HDC provided in the WM_CTLCOLORSTATIC message") and you say "huh?" because
you don't know how to translate an API subject into MFC on your own, then you are in
trouble and people will get frustrated with you for not learning what you need to know before
you try and use MFC.
I personally prefer to work with the API, it just suits me better, but if I were to write a
database frontend, or a host for a set of ActiveX controls I would seriously consider using
MFC, as it would eliminate a lot of code that I would need to reinvent otherwise.