Introduction
In this article, I'm going to present a number of small tips and tricks when using the Win32 API. None of them are interesting enough to warrant a separate article, but together they make an interesting collection. Some are workarounds for shortcomings in Windows, some aim to improve usability for you as a developer and for your users, and some describe common gotchas in Win32.
Note: The code in this article is tested on Windows 2000 and Windows XP. Some of it may also apply to older or newer versions of the OS.
Contents
Common Dialogs
How to set the initial folder in SHBrowseForFolder
The standard way to let the user select a folder is the SHBrowseForFolder
function. It is very clunky, and hasn't been updated much since Windows 95. One problem is that, by default, it starts from the root of the file system. Imagine you have a path setting with a browse button [...] to pick a folder:

It would be nice if the browser dialog opens with the current path already selected. Here's how you do it:
int CALLBACK BrowseCallbackProc( HWND hWnd, UINT uMsg, LPARAM lParam,
LPARAM lpData )
{
if (uMsg == BFFM_INITIALIZED)
SendMessage(hWnd, BFFM_SETSELECTION,TRUE, lpData);
return 0;
}
g_SHBF_Folder=_T("C:\\Program Files");
TCHAR path[_MAX_PATH];
BROWSEINFO info={NULL,NULL,path,_T("title"),BIF_USENEWUI,BrowseCallbackProc,
(LPARAM)g_SHBF_Folder};
SHBrowseForFolder(&info);
Don't forget to call CoInitialize
when using SHBrowseForFolder
. See the CommonDialogs project for a complete example.
Browse for folder using GetOpenFileName
Even with the ability to set the initial folder, SHBrowseForFolder
leaves much to be desired. If we can use GetOpenFileName
instead, we will get many usability benefits:
- The navigation is much easier - for example, you get "Back" and "Up" buttons, and the Places bar on the right.
- You can type a folder name with auto-complete.
- You can click on a folder shortcut to jump to another folder.
- The dialog is easily customizable with a dialog template - for example, you can add a "History" combo box to quickly select the last 10 used folders.
- You only see one folder at a time, instead of the huge tree containing all drives and all their folders. That means much less scrolling to do. Also, you can sort the folders by name or time.
- Because of the above, the dialog only needs to enumerate the current folder, which is very fast.
SHBrowseForFolder
, on the other hand, shows all drives and file systems, so it takes much longer to enumerate them and determine their icons. - It just makes sense to use consistent UI when selecting files and folders.

No wonder many applications that take usability seriously do this (for example, Visual Studio, Office, 3D Studio MAX). So how is it done?
As a first step, we need to remove all files from the list and leave only the folders. The easiest way to do this is by using a weird filter (for example, like this: "qqqqqqqqqqqqqqq.qqqqqqqqq"). Next, we need to remove all extra controls from the dialog. This is done by sending the CDM_HIDECONTROL
message for the stc2
(the "Files of type" label) and cmb1
(the filter combo box) controls. Also, a good idea is to rename the IDOK
button to "Select" and the stc3
label to "Folder Name:" using the CDM_SETCONTROLTEXT
message. Next, we subclass the dialog, and wait for the IDOK
button to be pressed. When the button is pressed, there are three courses of action:
- If the user has typed in some text in the file name box, we want to jump to that folder. This is default behavior, so we let the dialog do its thing. After that, we clear the name box.
- If an item in the listbox is selected, we want to get the full name of the item. Getting the full name can be tricky, and involves some tinkering with the Shell API. It is described in detail here: MSDN. If the selected item is a folder, we close the dialog, otherwise fall back to the default behavior.
- If no item is selected, then the user wants to pick the current folder - just get the path with the
CDM_GETFOLDERPATH
message and close the dialog.
And that's it. See the CommonDialogs project for full sources.
How to restore the size of the file dialog
When you use the file browser dialog, Windows is trying to be helpful and restores the size and position of the dialog the next time you run it. This is nice, but has some limitations:
- You may have multiple dialogs in your application. For example, one for opening a document, one for saving a document, one for picking a working folder, etc. You may need the position of each one to be saved independently.
- The above problem gets much worse if you customize some of the dialogs using a template. You will want the dialogs with more custom controls to be bigger, but instead, you'll get the same size stored for all.
- The first time you open the dialog, it is positioned at the top-left corner. It will be better if it is centered instead.
- The position is not stored between runs of the application. So, every time you start the application, you will get the dialog at the top-left.
The solution? Handle the storing and restoring the size manually.
You can do that by subclassing the dialog, storing the size on WM_DESTROY
, and restoring it on WM_SHOWWINDOW
.
See the CommonDialogs project for full sources. The OFN_RESTORESIZE
macro enables and disables this feature.
How to restore the view settings of the file dialog
If the user switches to the Details or another view, it would be nice to remember that the next time the file dialog is opened. It would be even better if we can restore the width of the columns (at least the Name column) and keep them from resetting (the dialog has the annoying habit of resetting the widths every time you switch to a new folder).
First, we need to get the current settings. Getting the view type is easy if you know the undocumented message WM_GETISHELLBROWSER
:
#define WM_GETISHELLBROWSER (WM_USER+7)
IShellBrowser *shBrowser=(IShellBrowser*)SendMessage(hWnd,WM_GETISHELLBROWSER,0,0);
IShellView *shView=NULL;
if (shBrowser->QueryActiveShellView(&shView)==S_OK) {
FOLDERSETTINGS settings;
shView->GetCurrentInfo(&settings);
g_ViewMode=settings.ViewMode;
shView->Release();
}
A note on GetCurrentInfo
- On Windows 2000, it doesn't support the Thumbnails view. When the browser is in Thumbnails view, the function returns the previous view type - List, Details, or something else. I haven't found a workaround for that yet.
To get the width of the Name column, you need the header control of the list view:
HWND hwndHeader=ListView_GetHeader(hwndList);
if (hwndHeader) {
HDITEM item;
item.mask=HDI_WIDTH;
if (Header_GetItem(hwndHeader,0,&item))
g_NameWidth=item.cxy;
}
The tricky part is when exactly to retrieve these settings. Doing it when the dialog is destroyed is too late. By that time, the list view is gone and shBrowser->QueryActiveShellView
fails. I have found that a good way to do it is to subclass the list view and handle its WM_DESTROY
message. Having the list subclassed will also come in handy later when we try to restore the settings. So, how do we get our hands on the list view? If you do some Spying++, you'll see that the view is a sneaky and elusive beast. It lives inside another window with the class name "SHELLDLL_DefView
". The SHELLDLL_DefView
is recreated every time the user changes the current folder, and the list view is recreated with it, and needs to be subclassed again. So, first we do the subclassing in the top window on WM_SHOWWINDOW
(already handled in the previous section), and when the list view is destroyed, we post a custom message to the top window to subclass the next incarnation.
Now that we have the settings, we can restore them for the next dialog. The view type is restored on WM_SHOWWINDOW
. There are two ways of doing that. If you are running on Windows XP, you can get the IFolderView
interface and call SetCurrentViewMode
:
IShellBrowser *shBrowser=(IShellBrowser*)SendMessage(hWnd,WM_GETISHELLBROWSER,0,0);
IShellView *shView=NULL;
if (shBrowser->QueryActiveShellView(&shView)==S_OK) {
IFolderView *folder=NULL;
if (shView->QueryInterface(IID_IFolderView,(void **)&folder)==S_OK) {
folder->SetCurrentViewMode(g_ViewMode);
folder->Release();
}
shView->Release();
}
Since Windows 2000 doesn't support IFolderView
, we use another undocumented trick. We can send one of these commands to the SHELLDLL_DefView
window:
28713 - Large icons
28714 - Small icons
28715 - List
28716 - Details
28717 - Thumbnails
28718 - Tiles
The last two do not work on Windows 2000, just on XP.
To restore the width of the name column, you have to do two things - if the list view is already in Details mode (you can check that by testing the WS_VISIBLE
style of the header control), just call the ListView_SetColumnWidth
macro. Otherwise, set the width in the header control directly:
HWND hwndHeader=ListView_GetHeader(hwndList);
if (hwndHeader) {
if ((GetWindowLong(hwndHeader,GWL_STYLE)&WS_VISIBLE)!=0)
ListView_SetColumnWidth(hwndList,0,g_NameWidth);
else {
HDITEM item;
item.mask=HDI_WIDTH;
item.cxy=g_NameWidth;
Header_SetItem(hwndHeader,0,&item);
}
}
The width has to be restored in two cases. First is when the list view is subclassed. The second is when the header control is created. This can happen if the control starts in List mode and is later switched to Details mode. When the header is created, it sends the WM_PARENTNOTIFY
message to the list control and that's where we catch it.
See the CommonDialogs project for full sources. The interesting parts are the OFNListProc
function that handles the WM_PARENTNOTIFY
and WM_DESTROY
messages, and the OFNFileProc
function that handles WM_SHOWWINDOW
and WM_RESETLIST
. WM_RESETLIST
is a custom message that is posted when the list view needs to be subclassed or the width needs to be reset. The OFN_RESTORESETTINGS
macro enables and disables this feature.
Common Controls
Reducing flicker in a group box
Of all the common controls, the group box is probably the one that flickers the most when resized. There is a good reason for that. The group box is used to draw a boundary around other controls in the window. As such, it must be used with the "transparent" style; otherwise, it will draw on top of the controls inside it. The transparent controls are drawn after all other controls are drawn. This introduces a big delay between erasing the background and drawing the frame, which appears as flickering. The more controls you have in the window, the stronger the flickering. If the Windows Visual Styles are enabled, the problem is even worse. The borders are drawn much slower because they use bitmap elements, thus increasing the effect.
Here's one possible solution to the problem. We make the group box non-transparent, and move it to the bottom of the Z order to be drawn first. That is not enough though. Since the group box control is intended to be used as transparent, it doesn't erase its own background and counts on the underlying windows to do that. This is fixed by adding a WM_ERASEBKGND
handler that erases the background with COLOR_BTNFACE
. As an even further improvement, we can split the drawing of the background in two parts - on WM_ERASEBKGND
, only the inside area will be erased. The background behind the border and the caption will be erased as part of the WM_PAINT
handling. For that, we need to know the size of the border and the caption. Seems like, ten DLUs (dialog box units) for the top side and five DLUs for the sides and bottom gives a good result.
See the ResizeControls project for full sources. The code is intended more as a proof of concept than a drop-in solution. You would probably want to encapsulate the group box control in a class using your framework of choice - MFC, WTL, etc.
How to correctly resize a static control
What can be simpler than a static control? What can possibly go wrong with it? Well, here's one problem: Imagine you have a multi-line static control with word-wrapping enabled. If you resize or move the control, Windows optimizes the redrawing by copying as much of the old image as possible to the new place. This speeds up the redraw and reduces flickering. But when the control's width changes, the word-wrapping requires the text in the whole area to be rearranged. Just redrawing the newly exposed area doesn't work.
You can avoid this problem by specifying SWP_NOCOPYBITS
when calling SetWindowPos
. This will prevent Windows from trying to preserve the old image. Note that you shouldn't use SWP_NOCOPYBITS
for all resizable controls. Doing so can cause excessive redraw and flickering on controls that already handle resizing correctly (pretty much all other controls). Single-line static controls, and multi-line without word wrapping also work fine without SWP_NOCOPYBITS
.
The sample project ResizeControls shows the wrong and the right way to resize the control:

The two static controls contain identical text. The first one is resized without SWP_NOCOPYBITS
, and the second with SWP_NOCOPYBITS
. Notice how the text in the first control is garbled. The same dialog also compares a regular group box with the one described in the previous section.
Another solution is to call InvalidateRect
for the static control after you resize it. That's what WTL's CDialogResize
does when you use the DLSZ_REPAINT
flag.
Handling selection in a list view
The old list box control sends LBN_SELCHANGE
to its parent when the selection changes. The notification is sent only once no matter how complex the selection change is.
The newer list view control doesn't have such notification. Instead, it sends LVN_ITEMCHANGED
for every state change of every item. If you have 1000 items in the control and select them all, you'll get 1000 notifications. Often, you want to do some processing when the selection changes. For example, if you have a list of files, you may want to update the selection count and the total size of the selection. Doing that 1000 times can be expensive.
There is a way to get a single notification instead. On the first LVN_ITEMCHANGED
, post a custom message to the parent and set a flag. Ignore the rest of the notifications if the flag is set. By the time the custom message is received, all items should be updated and you can use LVM_GETNEXTITEM
to find the selection. When you are done handling the selection, just clear the flag:
const int WM_LVSELCHANGE=RegisterWindowMessage(_T("WM_LVSELCHANGE"));
static g_bSelChanging=false;
if (uMsg==WM_NOTIFY && ((NMHRD*)lParam)->code==LVN_ITEMCHANGED
&& !g_bSelChanging) {
g_bSelChanging=true;
PostMessage(hWnd,WM_LVSELCHANGE,0,0);
}
if (uMsg==WM_LVSELCHANGE) {
g_bSelChanging=false;
}
If you use a framework such as MFC or WTL, you may want to handle the notifications in your list view class using reflection. The class will also hold the g_bSelChanging
flag per control instead of using a global variable.
Structure sizes
The Win32 API contains many structures. Some of them have a member that must be set to the structure's size. For example, OPENFILENAME
, TBBUTTONINFO
, TOOLINFO
, etc. It is important to use the correct size for the current version of Windows. If you use the size from an old version, some of the new functionality will be disabled as the OS will try to emulate the old behavior. If you use the size from a future version (for example, running Windows XP code on Windows 95), you will get undefined behavior, ranging from error codes to crashes.
On Windows XP, you have to be extra careful, as you may have to use different sizes if you run with common controls version 5 or 6. Here's an example of how to handle the size of PROPSHEETPAGE
and TOOLINFO
on Windows XP:
int SIZE_PROPSHEETPAGE;
int SIZE_TOOLINFO;
ULONG COMCTL_VERSION=0;
HMODULE dll=GetModuleHandle(_T("comctl32.dll"));
if (dll) {
DLLGETVERSIONPROC DllGetVersion=
(DLLGETVERSIONPROC)GetProcAddress(dll,"DllGetVersion");
if (DllGetVersion) {
DLLVERSIONINFO vinfo;
memset(&vinfo,0,sizeof(vinfo));
vinfo.cbSize=sizeof(vinfo);
DllGetVersion(&vinfo);
COMCTL_VERSION=MAKEVERSION(vinfo.dwMajorVersion,vinfo.dwMinorVersion);
}
}
if (COMCTL_VERSION>=MAKEVERSION(6,0)) {
SIZE_PROPSHEETPAGE=sizeof(PROPSHEETPAGE);
SIZE_TOOLINFO=sizeof(TOOLINFO);
}
else {
SIZE_PROPSHEETPAGE=PROPSHEETPAGE_V2_SIZE;
#ifdef UNICODE
SIZE_TOOLINFO=TTTOOLINFOW_V2_SIZE;
#else
SIZE_TOOLINFO=TTTOOLINFOA_V2_SIZE;
#endif
}
Other structures to watch for if you want to support different versions of Windows: OPENFILENAME
, SCROLLINFO
, WINDOWPLACEMENT
, MENUITEMINFO
, NONCLIENTMETRICS
, TBBUTTONINFO
, TRACKMOUSEEVENT
, TPMPARAMS
, MONITORINFO
, MONITORINFOEX
, PAGESETUPDLG
, PRINTDLGEX
, PRINTDLG
, ICONMETRICS
, DOCINFO
.
Resources
Support for multiple languages in one RC file
Translating an application in multiple languages usually involves more than just replacing the plain text. More often, you need to replace dialog templates, menus, accelerators, and other resources. There are two ways to provide separate resource sets for each language:
- Your application should not contain any resources. Instead, you build the resources in a separate DLL. You make one DLL for each language. At run-time, the application explicitly loads the DLL containing the current language, and loads the resources from it.
- You put all languages together. The RC files natively support multiple languages for each resource. You can have separate versions of
IDD_DIALOG1
for each language you want to support. The resource editor in Visual Studio handles that pretty well. Visual Studio 2005 adds support for editing Unicode RC files (older versions could compile such files, but not edit them), fixing, once and for all, the code page problems. The Win32 API, on the other hand, does not make it easy to pick which version to use at run time. For example, the DialogBox
function may pick different resources based on the current OS language setting, the thread locale, and even the Windows version.
I am not going to discuss the merits of the two approaches. I believe both have their strengths and weaknesses. Most likely, other people will chime in their opinions in the comments section. Instead, I'm going to show here how to reliably select which resource to use from an RC file containing multiple languages.
At the core of the system is the LoadResourceEx
function. It finds and loads a resource for a given language. When the wLanguage
parameter is 0xFFFF
(the default), the system uses the g_AppLanguage
global setting. If the resource is not found, it tries the default OS language, GetUserDefaultLangID()
. If the resource is still not found, it falls back to the default behavior.
WORD g_AppLanguage;
LPVOID LoadResourceEx( HINSTANCE hInstance, LPCTSTR lpType,
LPCTSTR lpName, WORD wLanguage=0xFFFF, HRSRC *phrsrc=NULL )
{
if (wLanguage==0xFFFF) wLanguage=g_AppLanguage;
HRSRC hrsrc=FindResourceEx(hInstance,lpType,lpName,wLanguage);
WORD ui=GetUserDefaultLangID();
if (!hrsrc && wLanguage!=ui)
hrsrc=FindResourceEx(hInstance,lpType,lpName,ui);
if (!hrsrc) hrsrc=FindResource(hInstance,lpName,lpType);
assert(hrsrc);
if (!hrsrc) return NULL;
if (phrsrc) *phrsrc=hrsrc;
HGLOBAL hglb=LoadResource(hInstance,hrsrc);
assert(hglb);
if (!hglb) return NULL;
LPVOID res=LockResource(hglb);
assert(res);
return res;
}
The rest of the functions use LoadResourceEx
to convert the resource into a dialog, accelerator table, etc.
HACCEL LoadAcceleratorsEx( HINSTANCE hInstance,
LPCTSTR lpTableName, WORD wLanguage=0xFFFF );
HMENU LoadMenuEx( HINSTANCE hInstance, LPCTSTR lpMenuName, WORD wLanguage=0xFFFF );
INT_PTR DialogBoxParamEx( HINSTANCE hInstance, LPCTSTR lpTemplateName,HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam, WORD wLanguage=0xFFFF );
HWND CreateDialogParamEx( HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam, WORD wLanguage=0xFFFF );
INT_PTR DialogBoxEx( HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent,
DLGPROC lpDialogFunc, WORD wLanguage=0xFFFF );
HWND CreateDialogEx( HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent,
DLGPROC lpDialogFunc, WORD wLanguage=0xFFFF );
int LoadStringEx( HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax,
WORD wLanguage=0xFFFF );
Since the language setting is optional, these functions are a drop-in replacement for LoadAccelerators
, DialogBox
, etc. Just set g_AppLanguage
at startup to the language of choice. Also, through the beauty of C++, we can have another set of functions that accept numeric IDs directly, without the dreaded MAKEINTRESOURCE
:
HACCEL LoadAcceleratorsEx( HINSTANCE hInstance, UINT uTableID, WORD wLanguage=0xFFFF );
HMENU LoadMenuEx( HINSTANCE hInstance, UINT uMenuID, WORD wLanguage=0xFFFF );
INT_PTR DialogBoxParamEx( HINSTANCE hInstance, UINT uTemplateID, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam, WORD wLanguage=0xFFFF );
HWND CreateDialogParamEx( HINSTANCE hInstance, UINT uTemplateID, HWND hWndParent,
DLGPROC lpDialogFunc, LPARAM dwInitParam, WORD wLanguage=0xFFFF );
INT_PTR DialogBoxEx( HINSTANCE hInstance, UINT uTemplateID, HWND hWndParent,
DLGPROC lpDialogFunc, WORD wLanguage=0xFFFF );
HWND CreateDialogEx( HINSTANCE hInstance, UINT uTemplateID, HWND hWndParent,
DLGPROC lpDialogFunc, WORD wLanguage=0xFFFF );
See the Resources project for full sources. You can grab the ResourceEx.h and ResourceEx.cpp files and drop them in your project directly.
Database for all string resources
You can use the LoadString
function (or LoadStringEx
above) to load a string from the application resources. It is not very user-friendly. You are required to provide your own buffer and to guess the length of the string in advance:
TCHAR buff[256];
LoadString(hInstance,IDS_STRING1,buff,256);
_tprintf(_T("%s\n"),buff);
If your buffer is not big enough, the function will fail and you must retry with a bigger buffer.
I have found it much more convenient to create a database that takes care of the allocations and deallocations for me. It doesn't try to guess the length of the strings, but gets it by parsing the string resources directly. At run time, you can get a const TCHAR*
from a string ID and not worry about buffers and sizes:
CStringSet g_MainStringSet;
g_MainStringSet.Init(hInstance,GetUserDefaultLangID(),true);
_tprintf(_T("%s\n"),g_MainStringSet.GetString(IDS_STRING1));
The first parameter of the Init
function is the module handle. You will need a separate string database for each module you have in your program - the main EXE, plug-ins, other DLLs containing resources. That is because the string IDs are guaranteed to be unique only within the same module.
The second parameter is the language you want to use.
The last parameter is true
if you want to scan the resources and preload all strings, or false
if you want to load each string the first time it is used.
The current implementation uses the standard heap functions malloc
and free
to allocate memory. Depending on the API you are using, you can modify the database to use std::string
, or ATL::CString
, or something else internally.
See the Resources project for full sources. You can grab the StringSet.h and StringSet.cpp files and drop them in your project directly.
Wait, There's More
Control the tab order of MDI windows
In MDI applications, you can use Ctrl+Tab to go to the next document in the list. In the default MDI implementation, the order of the documents is static. It would be more user-friendly if the list is rearranged in the most-recently-used order, so your recent documents are accessible with less keystrokes. Windows does something similar in its Alt+Tab order.
One solution is to maintain a separate MRU (most recently used) list of documents. First, when a document is activated but the Ctrl key is not pressed, it is moved to the top of the list. Second, you need to subclass the MDI client window and handle the WM_MDINEXT
message. If the Ctrl key is pressed, override the default behavior by activating the next (or previous) document in the list. And last, when the Ctrl key is released, you must put the current document at the top of the list. This is best handled in the main message loop.
Also, a nice improvement is to override the update of the Window menu and display the documents in MRU order, so the current document is at the top of the menu. This is done by handling WM_INITMENUPOPUP
and the AFX_IDM_FIRST_MDICHILD
... AFX_IDM_LAST_MDICHILD
commands by the main window.
Check out the MDITest project for full sources. The important parts are the MDIChildProc
which updates the document list, the MDIClientProc
which handles WM_MDINEXT
, the MDIFrameProc
which updates the Window menu and handles the commands, and the message loop which detects the releasing of the Ctrl key.
Asserts in a GUI application
By default, an assert in your program will pop up a message box showing you the failure condition, other useful information, and a choice to break or continue. While the message box is up, your application continues to receive and process messages. If you choose to break in the debugger, the state of the application will be different from the one that triggered the assert, making it harder to debug. What's even worse, leaving the application running behind the message box may trigger another assert, and another, and so on. This can be especially annoying in a WM_PAINT
handler: repainting triggers the assert, which shows a message box, which triggers repainting, and so on until the stack blows up.
Another problem is that if you choose to abort the program, it is done by calling raise(SIGABRT)
. This doesn't immediately kill the process. Before that, it performs some system cleanup. In Visual Studio, it also includes a call to _CrtDumpMemoryLeaks
(if you are running with the leak detection turned on), which will dump the contents of the heap to the Output window. As the program is aborted unexpectedly, the heap can contain many thousands of items. The Output window is not exceptionally fast, and Visual Studio may freeze for a few minutes. In versions prior to Visual Studio 2005, you could press Shift+F5 and stop the heap dump. In 2005, you can't do that any more - it insists on printing the whole heap.
Here is a better solution. MyAssert
blocks the main GUI thread, and spawns a new thread to display the message box. It also terminates the program using TerminateProcess
, if you choose to abort:
#include <crtdbg.h>
#if !defined(NDEBUG)
bool my_assert( const char *exp, const char *file, unsigned line );
#define MyAssert(exp) do { if (!(exp) && !my_assert(#exp,__FILE__,__LINE__)) \
_CrtDbgBreak(); } while (0)
#else
#define MyAssert(exp) ((void)0)
#endif
MyAssert.cpp:
#if !defined(NDEBUG)
static DWORD _stdcall AssertThreadProc( void *param )
{
return MessageBoxA(NULL,(const char *)param,"Assertion Failed",
MB_ABORTRETRYIGNORE|MB_TASKMODAL|MB_ICONERROR);
}
bool my_assert( const char *exp, const char *file, unsigned line )
{
char buf[2048];
sprintf(buf,"Expression: %s\r\nFile: %s\r\nLine: %d\n",exp,file,line);
HANDLE h=CreateThread(NULL,0,AssertThreadProc,buf,0,NULL);
if (h) {
WaitForSingleObject(h,INFINITE);
DWORD res=IDRETRY;
GetExitCodeThread(h,&res);
if (res==IDABORT)
TerminateProcess(GetCurrentProcess(),3);
return (res==IDIGNORE);
}
return true;
}
#endif
The GUIAssert project shows what happens if WM_PAINT
triggers the default assert
, _ASSERT
, and MyAssert
. The default assert
keeps popping up recursively, blowing the stack. The _ASSERT
is a little smarter - if the second assert is triggered while the first one is up, it directly breaks into the debugger, no message box. But still, by that time, the state of the application is altered. Also, the debugger breaks somewhere inside the assert implementation instead of your code. MyAssert
blocks the calling thread immediately, and lets you examine the exact failure condition. It also breaks in the correct line inside your code.
Sample Projects
The sample projects are compatible with Visual C++ 6, VS 2003, VS 2005, and VS 2008 beta2. They have been tested on Windows 2000 and Windows XP with and without visual styles. The source code is pure Win32 but can be easily converted to MFC, WTL, or maybe even .NET.
- CommonDialogs - shows how to pick folders with
SHBrowseForFolder
and with GetOpenFileName
, and how to restore the settings of a file dialog. - ResizeControls - shows how to resize a group box and a static control.
- Resources - shows how to use multiple languages in the same program, and how to preload all string resources.
- MDITest - shows how to better handle Ctrl+Tab in MDI applications.
- GUIAssert - compares the default
assert
, the CrtDbg's _ASSERT
, and MyAssert
.
History
- Oct 12th, 2007 - First version
- Oct 21st, 2007 - Few updates based on viewer suggestions:
- removed the unnecessary global variable from the SHBrowseForFolder example
- added support for restoring the view settings in the file browser
- added option in the string database to load the strings on demand
- Oct 22nd, 2007 - Fixed a problem in the CommonDialogs sample that breaks the renaming in the file list