This document was originally prepared by Michael Janzen
<mjanzen@uoguelph.ca> during the 2004 Winter term.
This assumes you have downloaded the newskel code. For an explanation of
the newskel code you can see the heavily commented version.
Please note that there is more than one way to do things. This is only explaining on possible way.
To Add a Resource Script
- From the "File" menu select "New"
- Select "Resource Script" and give the script a name
- Click "OK"
To Add a Menu
- Click on the "Resource" tab to show your resources
- Right click on the resources and select "Insert"
- Select "Menu" and click "New"
- Design your menu.
- Use identifiers that start with "IDM_" to denote menu identifiers. This also avoid conflicts with some Windows reserved identifiers
- You can put an & in front of a letter to make it a short-cut

- Rename your menu such that your menu name is in quotes
- To rename your menu: right click the menu name and select properties.
- Add your menu name where the windows class is defined
-
wcl.lpfnWndProc = (WNDPROC) WndProc; // window function
wcl.style = CS_HREDRAW | CS_VREDRAW;
wcl.hIcon = LoadIcon( 0, IDI_APPLICATION ); // large icon
wcl.hIconSm = LoadIcon( 0, IDI_WINLOGO ); // small icon
wcl.hCursor = LoadCursor( 0, IDC_ARROW ); // cursor style
wcl.lpszMenuName = "MyMenu";
wcl.cbClsExtra = 0; // no extra
To Respond to Menu Selections
- Include "resource.h"
- In your CALLBACK function ("WndProc" if you haven't changed it) add a WM_COMMAND case
LRESULT CALLBACK
WndProc( HWND hWnd, // window handle
UINT nMessage, // type of message
WPARAM wParam, // additional information
LPARAM lParam ) // additional information
{
switch ( nMessage )
{
case WM_DESTROY: /* terminate the program */
PostQuitMessage( 0 );
break;
case WM_COMMAND:
break;
default:
/* Let Windows NT process any messages not specified in
the preceding switch statement. */
return DefWindowProc( hWnd, nMessage, wParam, lParam );
}
return LRESULT( 0 );
}
- In your case WM_COMMAND, switch on the LOWORD of wParam. This contains your menu identifier when the message is WM_COMMAND
case WM_COMMAND:
switch( LOWORD(wParam) )
{
case IDM_DKGRAY:
MessageBox( HWND_DESKTOP,
"Dark Gray was clicked",
"Menu Item Selected",
MB_OK );
break;
};
break;
- Now when the user selects the colour "Dark Gray" a message box will appear
Adding an Accelerator
- Add an accelerator to your resource script (similar to adding a menu - see above)
- Add an accelerator item using the same identifier as your menu

- Here the accelerator is ALT-G
- You may want to modify your menu so that the ALT-G appear with "Dark Gray"
- "&Dark Gray\tALT-G"
- The "\t" is a tab character and can be used for aligning
- Rename your accelerator so that its name is in quotes (eg "MyAccelerator")
- Load the accelerator in you .cpp file
HACCEL hAccel = LoadAccelerators( hThisInst, "MyAccelerator" );
//
// Enter the application message loop. Get and dispatch
// messages until a WM_QUIT message is received
//
MSG message; // window message
while ( GetMessage( & message, (HWND) 0, 0, 0 ) )
- To add the accelerator to the message loop, the handle to the window is required. Make some space in the global variable structure to hold the handle to the window
typedef
struct tagGlobal
{
HINSTANCE hInst; // handle to instance for resources
HWND hWndMain; // handle to main window
} GLOBAL, * LPGLOBAL;
LPGLOBAL
GetGlobalData( )
{
static GLOBAL data =
{
(HINSTANCE) 0, // hInst
(HWND) INVALID_HANDLE_VALUE // hWndMain
};
return ( & data );
}
- Now when the window is created, record the handle to the window in global data
//
// Return FALSE if window handle is NULL
// (window not could not be created)
//
if ( ! hWndMain )
{
MessageBox( HWND_DESKTOP,
"CreateWindow",
"InitInstance",
MB_OK );
return ( FALSE );
}
// Update the global variable
G->hWndMain = hWndMain;
//
// Make the window visible and update its client area
//
ShowWindow( hWndMain, nCmdShow ); // Show the window
UpdateWindow( hWndMain ); // Sends WM_PAINT message
- Now use the windows handle in your message loop
- First you will need to retrieve the global data so that it is in scope
-
- Add the windows handle to the GetMessage Function
- Add TranslateAccelerator() to your message loop. This will call your WndProc function if an accelerator is pressed. However, you don't also want to send a WM_CHAR message, so use an if statement when checking for an accelerator press.
HACCEL hAccel = LoadAccelerators( hThisInst, "MyAccelerator" );
LPGLOBAL G = GetGlobalData( );
//
// Enter the application message loop. Get and dispatch
// messages until a WM_QUIT message is received
//
MSG message; // window message
while ( GetMessage( & message, G->hWndMain, 0, 0 ) )
{
if ( ! TranslateAccelerator( G->hWndMain, hAccel, & message ) )
{
// Translate virtual key codes
TranslateMessage( & message );
// Dispatch message to window
DispatchMessage( & message );
}
}
- Note:The program does not seem to terminate correctly when an accelerator is used. To get around this, add an exit(0) after the PostQuitMessage(0).
Changing the icons
- Create two icons in your resource script (32x32)(similar to adding a menu, or accelerator)
- Change the names of your icons to be in quotes (eg: "MyIconSmall", "MyIconLarge")
- Modify the windows class part of your code in the same way you added your menu, but in the LoadIcon function. You wil need to add the handle to the application's instance so that the linker can find where your icons are stored.
wcl.hIcon = LoadIcon( hInstance, "MyIconLarge" ); // large icon
wcl.hIconSm = LoadIcon( hInstance, "MyIconSmall" ); // small icon
wcl.hCursor = LoadCursor( 0, IDC_ARROW ); // cursor style
wcl.lpszMenuName = "MyMenu";
- Note: Load Icon only works on 32x32 size icon

Changing the Cursor
- Basically the same as changing the icon. See above.
- The cursor has a "hot spot". This is the point where the cursor clicks when you press the mouse button. The hot spot can be set when drawing your cursor
- Add the cursor to your .cpp code in the same way you added an icon
wcl.hIcon = LoadIcon( hInstance, "MyIconLarge" ); // large icon
wcl.hIconSm = LoadIcon( hInstance, "MyIconSmall" ); // small icon
wcl.hCursor = LoadCursor( hInstance, "MyCursor" ); // cursor style
Adding a Bitmap
The two step process is a bit weird, but here it is
- Add and draw a bitmap using the similar technique to adding an icon
- Rename the bitmap so that the name is in quotes (eg: "MyBitmap")
- Some variables are needed to hold the bitmap, and other information for painting the bitmap
WindowFunc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
static HBITMAP hBit; /* handle of bitmap */
HDC hDC, hMemDC;
HGDIOBJ hOldObject;
PAINTSTRUCT ps;
LPGLOBAL G = GetGlobalData( );
- Notice that hBit is declared as static. This means that the variable will not be reset every time the WindowFunc (aka WndProc) is called. So once the bitmap is loaded, it is the same bitmap regardless of which time the WindowFunc is called
- G is used to hold the global data so that the handle to this instance can be used
- Load the bitmap when a WM_CREATE is received. WM_CREATE is called when the window is first created. Thus the bitmap will be ready when you need it
- Write all your painting code under the WM_PAINT case. This may mean that your program calls other functions, but you should not try to paint when other messages are received
- Start off with a BeginPaint call. This returns the handle to the device context. You can think of this as the pointer to your window on which you want to paint.
- Next create a compatible memory context from the device context you got from the begin paint function. There are reasons why you use a memory context (an efficient way for redrawing the window) which we will not be using in CTEC1638. There does not seem to be a way to paint the window directly, so we will copy the bitmap to a memory context and then copy the memory context to the window
- Create the compatible memory context using CreateCompatibleDC function
- Select the bitmap into the memory context using SelectObject. The old object that was in the memory context is returned, which can be stored in hOldObject. In our example it is not necessary to store the old object, but this is a good idea in general so you can return the context to the way you found it
case WM_PAINT:
hDC = BeginPaint( hWnd, & ps ); /* get device context */
hMemDC = CreateCompatibleDC( hDC ); /* create compatible DC */
hOldObject = SelectObject( hMemDC, hBit ); /* select bitmap */
BitBlt( hDC, 10, 10, 48, 48,
hMemDC, 0, 0, SRCCOPY ); /* display image */
BitBlt( hDC, 300, 100, 48, 48,
hMemDC, 0, 0, SRCCOPY ); /* display image (still on hMemDC) */
hOldObject = SelectObject( hMemDC, hOldObject ); /* deselect bitmap */
DeleteDC( hMemDC ); /* free the memory context */
EndPaint( hWnd, & ps ); /* release DC */
break;
- BitBlt copies the memory contect to the device context (window in this case). After specifying where to copy to (hDC) the x and y values appear of where to draw the image. Then the width and height are specified. The memory context to copy from is next. Then the x and y values of where to start in the memory context. The 0, 0 mean the upper left corner, so when the whole width and height are given earlier this copies the entire bitmap. SRCCOPY specified how Windows draws the bitmap. In this case the bitmap overwrites whatever is already on the window. You can press F1 on the BitBlt function for help on other copying methods.
- The next SelectObject restores hMemDC to the way it was before we changed it (again, not required in this example, but a good idea in general)
- DelectDC is used to return the system resources that were allocated to make the compatible memory context
- EndPaint releases the handle to the device context. This returns some resources that were allocated for painting (memory for example)
Things to Note
When modifying the WndProc function you probably want to only call a function, which actually accomplishes what you want to do. Otherwise the WndProc function becomes unweildly lengthy.
Some windows messages
WM_CREATE
- Called when the window is first created.
- lParam has no meaning
- wParam has no meaning
WM_DESTROY
- Called when the window is being destroyed (Right after it closes).
- lParam has no meaning
- wParam has no meaning
WM_COMMAND
- Called when a menu item is selected
- lParam has no meaning
- The LOWORD of wParam contains the identifier of the menu item that was selected
- The HIWORD of wParam contains information on whether the menu item was selected from clicking on the menu or from an accelerator
WM_PAINT
- Called when the window needs to be repainted
- The parameters contain the area that needs to be repainted. For small programs it is easier to repaint the entire window rather than figure out what has to be repainted.
WM_LBUTTONDOWN
- Called when the left mouse button is pressed down. (There is also a corresponding up if you need it)
- The LOWORD of lParam contains the x position (in pixels) of where the mouse was clicked
- The HIWORD of lParam contains the y position (in pixels) of where the mouse was clicked
WM_RBUTTONDOWN
- Called when the right mouse button is pressed down. (There is also a corresponding up if you need it)
- The LOWORD of lParam contains the x position (in pixels) of where the mouse was clicked
- The HIWORD of lParam contains the y position (in pixels) of where the mouse was clicked
Creating a Static Library
Right click on your project workspace and select "Add New Project to Workspace"
Select Win32 Static Library, name your project and click "OK"
- You may want to change the directory so you don't get projects in nested directories
Click "Finish" then "OK". You don't need to check off either of the checkboxes
Add a source file and header file to your new project (File->New->C++ Source file, etc ..)
Include windows.h in your header file and prototype any functions you want to have
In your source file, include your header file and define the body of the functions specified in the header file
Using a Static Library
Click your "Windows 32 Application" project (the first one) and then select "Dependencies" from the "Project" menu.
Add your static library as a dependency by clicking the checkbox


- Notice the test2 in test1 as a dependency
Alternatively you could include the .lib file. You may need to do this is you are using someone elses library and you cannot simply specify a dependency since you do not have access to the static library project.
Include the resource header in your application's .cpp file. Be careful to specify the correct directory (wherever your static library's header file is located
Call the function somewhere from your application's code
hOldObject = SelectObject( hMemDC, hBit ); /* select bitmap */
BitBlt( hDC, testingFunction(3)*10, 10, 48, 48,
hMemDC, 0, 0, SRCCOPY ); /* display image */
BitBlt( hDC, 300, 100, 48, 48,
hMemDC, 0, 0, SRCCOPY ); /* display image (still on hMemDC) */
- Okay, so it's not spectacular, but this is just a simple example
Creating a Dynamic Library
- Start the same way as with a static library, but select "Win32 Dynamic-Link Library". Select empty project when asked.
- Add a header and source file
- Include windows.h and prototype your function (like with a static library) and include definitions for DLLImport and DLLExport. You can either put these right in your file, or import them from another header file.
- Put your newly defined DllExport in front of both your function prototype and when you define the body of the function
Using a Dynamic Library
- Add your dynamic library as a dependency for your application the same way you added a static library as a dependency.
- Include the header file of the dll in your application's source file
- Call the function
BitBlt( hDC, testingFunction(3)*10, testingFunctionWithDLL(2)*10, 48, 48,
hMemDC, 0, 0, SRCCOPY ); /* display image */
- If you try to run the program you will likely get an error similar to the following error
- You need to copy the .dll file into the same directory as your application's source code. There are a number of other places you could copy it (like your windows directory), but the same directory as your applications source code should work.
Using a Timer
Sometimes it is handy to have a windows message come at regular intervals. To do this you can use a timer. There are three main parts to using a timer.
Preliminary
Okay. Prior to the first part you need to have an identifier for your timer. You can just use an integer or you can define a constant.
const INT TIMER_ID = 100; /* ID for timer control */
Starting the Timer
- To start the timer use the SetTimer function
- The first argument is the window the timer message will be sent to. Remember, you have to have a WndProc (or equivalent function) Callback function the the OS will call
- The second argument is an integer that is the timer ID. This is required because you could have more than one timer running at the same time
- The third timer is the approximate frequency of the timer in milliseconds. In this case the timer will send a message every second (1000 milliseconds).
- The last argument is a function to call when the timer sends a message. In this case the timer message will be handled in the Callback function, so this feature isn't needed. (Hence the NULL)
Handling the Timer Message
The timer will send a message at regular intervals. The message is a WM_TIMER message. To handle the message just as the case to your WndProc function.
Stopping the Timer
To shut off the timer, use the KillTimer function. You need to pass the handle of the window, and the ID of the timer. (Remember there could be more than one timer, so you need to specify which one to stop).
Something to Note
The timer is approximately correct. However, Windows could get busy so the interval between WM_TIMER messages might be a little longer than your specified. Also timer messages can build up in the message queue, so you may get a whole bunch of timer messages on after each other.
Dialogs
Modal versus Non-Modal
Dialogs come in two forms: modal and non-modal. When a modal dialog is activated you must close the dialog before returning to your main window. (You can do things in the dialog before returning to the main window.) A modeless dialog is slightly more complicated because both the dialog and the rest of the program can receive input.
- Add a dialog to your windows resource script
- Change the name of the dialog so that it is in quots
- Design your dialog. Add items to your dialog and lay out the dialog box the way you want it to look. If you have more than one radio button selection then you will need to place the radio buttons in a groupbox to separate the groups of radio buttons (otherwise the different groups of radio buttons are inconvieniently linked to each other).
- Add the code to your program to create a dialog. This is done by using the DialogBox(...) function.
-
DialogBox(hInst, "MyDB", hwnd, (DLGPROC) DialogFunc);
- hInst is the handle to the applications main instance. (Helps windows to find where you dialog box is stored in memeory).
- "MyDB" is the name of the dialog box as created in the resource editor
- hwnd is the handle of the window to which the dialog belongs. (ie. usually you only have one window in the programs we have been making).
- DialogFunc This is the procedure that will be called when a message is to be handled by your dialog. This is a call back function similar to your call back function for your main window. You will need to write this function.
BOOL CALLBACK DialogFunc(HWND hdwnd, UINT message, WPARAM wParam, LPARAM lParam)
- Remember to prototype this function at the top of your program
- (Note: In theory you could use just one CALLBACK function to handle both your main window and your dialog. However, this is probably not a good idea.)
- Write your DialogFunc function to handle different messages to be handled by your dialog box.
- When a button is pressed it will send a WM_COMMAND message with the id of the button in the LOWORD(wParam).
- The function DialogFunc should return TRUE if the message was handled
and FALSE otherwise. If your function returns TRUE by default then the
dialog box will not be properly displayed and will exhibit strange
behaviour
Writing back-end of a dialog
Windows will "handle" a lot of your controls with respect to appearance. When a radio button is clicked windows changes which radio button is selected. Windows will draw the checkbox automatically when the checkbox is toggled. However you will need to add the code of what should actually be done.
Buttons
As mentioned above when a button is clicked your dialog function is sent a WM_COMMAND message. The button ID is in the LOWORD of the wParam.
Some dialog items are actually buttons where you may not consider to be buttons. For example, push buttons, checkboxes and radio buttons are all buttons.
Edit Boxes
You can read from an edit box using the GetDlgItemText(...) function.
You can also set the text in a dialog box using the SetDlgItemText
It should be noted that you can use GetDlgItemText and SetDlgItemText with dialog items that are not edit boxes. For example, you can change the text (or read the text) on a button.
Check Boxes
As mentioned above the Check Box sends a WM_COMMAND message when clicked. You can also send the check box messages using SendDlgItemMessage
- If the message is BM_SETCHECK the checkbox can either be changed to a checked or unchecked state. The checkbox is checked if the wParam is BST_CHECKED. The checkbox is set to unchecked if the wParam is BST_UNCHECKED. lParam is set to 0 in both cases.
- If the message is BM_GETCHECK the SendDlgItemMessage will return either a BST_CHECK or BST_UNCHECKED to indicate whether or not the checkbox is checked. wParam and lParam are both zero.
Radio Buttons
Same as checkboxes. Note: Although possible, it is not a good idea to check more than one radio button at once. (A quick way to confuse a user).
WM_INITDIALOG
This message is sent to your dialog function when the dialog is started. (Like the WM_CREATE message sent to your main window).
Tackbar (a.k.a slider control)
Some messages that can be sent to your trackbar include:
- TBM_GETPOS SendDlgItemMessage returns the current position of your trackbar
- TBM_SETPOS lParam contains the new position and wParam is nonzero to redraw the trackbar or zero otherwise
- TBM_SETRANGE The minimum value is the low-word of lParam and the maximum value is the high-word of lParam. wParam determines whether or not to redraw the trackbar
Note: There are other messages that can be sent to trackbars.
Ending the Dialog Box
Use EndDialog(..) to end the dialog box
Modeless Dialog Boxes
To create a modeless dialog box use the CreateDialog(..) function instead of the DIalogBox(...) function
HWND CreateDialog(HINSTANCE hThisInst, LPCSTR lpName, HWND hwnd, DLGPROC lpDFunc);
- The parameters are similar to the DialogBox function
- Note that the handle to the dialog box is returned
- If you have not set the dialogbox to WS_VISIBLE you will need to call ShowWindow to display your dialog box
- Instead of using EndDialog(..) use DestroyWindow(...)
BOOL DestroyWindow(HWND hwnd);
- Change the message loop to recognise dialog messages by adding the IsDialogMessage(..)
BOOL IsDialogMessage(HWND hdwnd, LPMSG msg);
- Here hdwnd is the handle to the dialog box and msg is the message from the GetMessage(...) call
- So, the message loop could like like ...
while(GetMessage &msg, NULL, 0, 0))
{
if(!IsDialogMessage(hDlg, &msg))
{
if(!TranslateAcelerator(hwnd, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
};
};
};
One more thing to do to make a modeless dialog box
Your GetMessage(...) box in your message loop may take a handle to a
window as a parameter. This causes only messages for your window to be
received. However, you also want the messages for your modeless dialog
box. So change the handle to the window to NULL in the GetMessage(...)
function call. This will cause the message loop to receive all messages -
both the ones for your window and the ones for your modeless dialog box.
Creating a Thread
A thread allows you to have two parts of your program run concurrently.
This can be very useful. For example, you if one part of your program
freezes or deadlocks, the other part of your program can continue to run
(very useful for running a server, especially when the client can be
untrustworthy or unstable). Another example is that you can put
computationally expensive processes in their own thread so that your
program can continue responding to GUI processing.
One way to create a thread is to use the CreateThread function
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpSecAttr,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpThreadFunc,
LPVOID lpParam,
DWORD dwFlags,
LPDWORD lpdwThreadID);
- You can set the lpSecAttr to NULL to use the default security
descriptor
- You can set the dwStackSize to zero to use the default stack size
- The lpThreadFunc is the entry point to your thread. In other words
the function that the operating system should call when it has created the
new thread
- lpParam is a general pointer that you can use to pass parameters to
your thread function
- dwFlags instructs the operating system how to start the thread. Use
zero to start the thread immediately. Another options is CREATE_SUSPEND
which requires a call to ResumeThread()
to actually start the thread.
- Pass a reference ( use &) to receive the thread ID. This thread
ID should be defined as a DWORD (no pointe)
The lpThreadFunc should point to a function that has the following form:
Unfortunately, there is a bit of a memory leak with the CreateThread
function. Threre are two solutions to this problem
- Do not use any "dos" functions with your thread. Instead use the
windows functions. For example, use lstrlen instead of strlen.
- Use _beginthreadex instead of CreateThread. The parameters are the
same type and meaning.
Other Thread Functions
- Terminate Thread
- Stops the thread right away
- TerminateThread(HANDLE, INT)
- The first parameter is the handle of the thread. The second is the
exit status of the thread (you can use zero)
- Suspend Thread
- Causes the thread to pause until it is instructed to start
again
- SuspendThread(HANDLE)
- Resume Thread
- Causes a paused thread to continue
- ResumeThread(HANDLE)
- Change the importance of a thread
- SetThreadPriority( HANDLE, INT)
- Set the priority of the thread. This affects how much of the
processor time the thread receives in relation to other threads
- You may use the values 0, 1, 2, 3, and 4 for thread priorities
- The actual amount of CPU time the thread receives is a combination of
the thread's priority and the processor's priority
Note: Some people consider it problematic to pause and resume threads as
the code can become deadlock prone.
Semaphores
Sometimes it is required that only one process or thread use a resource or
section of code at a time. The solution is to have a semaphore.
Think of a bathroom that doesn't lock properly. Before someone enters
the bathroom they ask to doorman if it is okay. If the doorman says it is
alright, they enter and use the bathroom. After they are finished they
tell the doorman that they are done.
Now rather than say okay, the doorman may say to wait. The process
waits until one of two things happens. First the person ahead of them may
leave the bathroom and the doorman will then allow the waiting process
access to the controlled resource. Alternatively the process may become
"impatient" and leave. This corresponds to a timeout.
If you haven't guessed yet the doorman corresponds to a semaphore.
A semaphore works by decrementing a count when a thread requests access
and incrementing when the thread releases the semaphore. When the count
is zero the semaphore blocks access (the threads wait until another thread
releases).
To create a semaphore use the following API call
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSecAttr, LONG InitialCount,
LONG MaxCount, LPSTR lpszName);
- The security attributes can be set to NULL to use the default security
setting
- The maximum count is how many tasks can simultaneously be granted
access. Often this value is set to one
- The initial count is the initial count of the semaphore. For example,
if the initial count is zero, then all objects wait. Usually the max
count is one and the initial count is one
- The name is a string that names the semaphore. This allows more than
one program to use the semaphore. You may also use the value of NULL for
this parameter if you want an unnamed semaphore.
Using a semaphore
To request access use WaitForSingleObject()
To release a semaphore use use ReleaseSemaphore().
Back to CTEC1638