Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / C++
Article

Win32 Tips and Tricks

Rate me:
Please Sign up or sign in to vote.
4.68/5 (42 votes)
21 Oct 2007CPOL20 min read 149.3K   2.4K   135   40
Usability tricks, gotchas, and workarounds for Win32.

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:

Image 1

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.

Image 2

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:

  1. 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.
  2. 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.
  3. 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:

// undocumented message to get IShellBrowser from the file dialog

#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();
}

// experiments show that shBrowser doesn't need to be Released

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; // requires Win XP

  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:

Image 3

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;

// in the parent's message handler:

  if (uMsg==WM_NOTIFY && ((NMHRD*)lParam)->code==LVN_ITEMCHANGED 
                      && !g_bSelChanging) {
    g_bSelChanging=true;
    PostMessage(hWnd,WM_LVSELCHANGE,0,0);
  }

  if (uMsg==WM_LVSELCHANGE) {
    // process the new selection here

    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;

// get the version of the common controls DLL

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)) {
  // common controls version 6 (WinXP with visual styles)

  SIZE_PROPSHEETPAGE=sizeof(PROPSHEETPAGE);
  SIZE_TOOLINFO=sizeof(TOOLINFO);
}
else {
  // Win2000 or XP without visual styles

  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:

  1. 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.
  2. 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 )
{
  // default to the app language

  if (wLanguage==0xFFFF) wLanguage=g_AppLanguage;

  // first try wLanguage

  HRSRC hrsrc=FindResourceEx(hInstance,lpType,lpName,wLanguage);

  // try the default UI language

  WORD ui=GetUserDefaultLangID();
  if (!hrsrc && wLanguage!=ui)
      hrsrc=FindResourceEx(hInstance,lpType,lpName,ui);

  // fall back to default behavior

  if (!hrsrc) hrsrc=FindResource(hInstance,lpName,lpType);
  assert(hrsrc);
  if (!hrsrc) return NULL;

  // load the resource

  if (phrsrc) *phrsrc=hrsrc;
  HGLOBAL hglb=LoadResource(hInstance,hrsrc);
  assert(hglb);
  if (!hglb) return NULL;

  // finally lock the resource

  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:

// Somewhere globally:

CStringSet g_MainStringSet;

// In WinMain:

g_MainStringSet.Init(hInstance,GetUserDefaultLangID(),true);

// In your code:

_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:

//MyAssert.h:
#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); // true will continue, false will cause _CrtDbgBreak

  }
  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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Ivo started programming in 1985 on an Apple ][ clone. He graduated from Sofia University, Bulgaria with a MSCS degree. Ivo has been working as a professional programmer for over 12 years, and as a professional game programmer for over 10. He is currently employed in Pandemic Studios, a video game company in Los Angeles, California.

Comments and Discussions

 
QuestionWidth of window Pin
bokideclan21-Mar-14 2:23
bokideclan21-Mar-14 2:23 
GeneralLink error "IID_IFolderView" Pin
cschmidt3-Nov-09 9:37
cschmidt3-Nov-09 9:37 
GeneralRe: Link error "IID_IFolderView" Pin
Ivo Beltchev3-Nov-09 10:31
Ivo Beltchev3-Nov-09 10:31 
GeneralRe: Link error "IID_IFolderView" Pin
cschmidt3-Nov-09 12:15
cschmidt3-Nov-09 12:15 
QuestionCompile Error?????? Pin
Andraw1117-Oct-09 4:24
Andraw1117-Oct-09 4:24 
AnswerRe: Compile Error?????? Pin
Ivo Beltchev7-Oct-09 18:19
Ivo Beltchev7-Oct-09 18:19 
GeneralRe: Compile Error?????? Pin
Andraw1118-Oct-09 5:57
Andraw1118-Oct-09 5:57 
AnswerRe: Compile Error?????? Pin
ehsansad15-Jun-10 1:55
professionalehsansad15-Jun-10 1:55 
GeneralJust what I need. Pin
NotU11-Aug-09 6:32
NotU11-Aug-09 6:32 
GeneralRe: Just what I need. Pin
Ivo Beltchev11-Aug-09 6:57
Ivo Beltchev11-Aug-09 6:57 
GeneralGetOpenFileName type-ahead popup displays all files, not just folders Pin
PaulBart131-Oct-08 15:28
PaulBart131-Oct-08 15:28 
GeneralRe: GetOpenFileName type-ahead popup displays all files, not just folders Pin
Ivo Beltchev1-Nov-08 5:30
Ivo Beltchev1-Nov-08 5:30 
GeneralWonderful Article. Pin
kaviteshsingh14-May-08 19:38
kaviteshsingh14-May-08 19:38 
GeneralRe: Newbie Article. Pin
kilt18-Feb-09 12:11
kilt18-Feb-09 12:11 
GeneralNewbie post Pin
Ivo Beltchev18-Feb-09 14:46
Ivo Beltchev18-Feb-09 14:46 
JokeRe: Newbie post Pin
Ivo Beltchev18-Feb-09 14:49
Ivo Beltchev18-Feb-09 14:49 
GeneralSunclassing VB Pin
METAILLIER21-Apr-08 8:59
METAILLIER21-Apr-08 8:59 
QuestionGetOpenFileName positionning with VB Pin
METAILLIER21-Apr-08 4:28
METAILLIER21-Apr-08 4:28 
GeneralRe: GetOpenFileName positionning with VB Pin
Ivo Beltchev21-Apr-08 6:55
Ivo Beltchev21-Apr-08 6:55 
QuestionHow to get the selected file and its full path from IShellBrowser Pin
Nitheesh George24-Feb-08 20:16
Nitheesh George24-Feb-08 20:16 
AnswerRe: How to get the selected file and its full path from IShellBrowser Pin
Ivo Beltchev1-Mar-08 11:38
Ivo Beltchev1-Mar-08 11:38 
GeneralRe: How to get the selected file and its full path from IShellBrowser Pin
Nitheesh George5-Mar-08 22:50
Nitheesh George5-Mar-08 22:50 
GeneralUpdate: a bug fix Pin
Ivo Beltchev22-Oct-07 6:49
Ivo Beltchev22-Oct-07 6:49 
GeneralSelecting the VIEW mode Pin
AlexEvans17-Oct-07 11:53
AlexEvans17-Oct-07 11:53 
GeneralRe: Selecting the VIEW mode Pin
Ivo Beltchev17-Oct-07 19:13
Ivo Beltchev17-Oct-07 19:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.