Skip to content

Modeless Dialog

A Win32 example of running a dialog non-modally with CreateDialog plus a message loop, instead of the blocking DialogBox call.

Published Dec 30, 1997

Most sample code shows DialogBox, which blocks until the user closes the dialog. This project shows the other half of the API: CreateDialog returns immediately and hands you the dialog's HWND, so your main window stays alive and responsive while the dialog sits on top - necessary when the dialog must coexist with other windows, timers, or background work. The key piece is the message loop in WinMain: pump messages with GetMessage, route them through IsDialogMessage (which gives the dialog keyboard navigation like Tab), and only translate/dispatch what the dialog did not handle. The dialog procedure itself is the standard shape - WM_INITDIALOG to wire up controls, WM_COMMAND for buttons, EndDialog to close.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevious, LPSTR CmdLine, int CmdShow)
{
	AppInstance = hInstance;

	HANDLE xMutex = NULL;

    if(!InitializeApp())
    {
        return 1;
    }

	if( (xMutex = OpenMutex(SYNCHRONIZE, FALSE, gsTitleCaption)) )
	{
		MessageBox (NULL, "Only one instance can run at a time!" , gsTitleCaption, MB_ICONASTERISK + MB_SYSTEMMODAL);
		ReleaseMutex(xMutex);
		CloseHandle(xMutex);
		return 0;
	}
	else xMutex = CreateMutex(0, 0, gsTitleCaption);

	HWND MyDialog_hWnd = CreateDialog(AppInstance, MAKEINTRESOURCE(IDD_MAIN), 0, MainDialog);

    BOOL bRet;
    MSG msg;

    while ( (bRet = GetMessage(&msg, MyDialog_hWnd, 0, 0)) != 0 )
    {
        if(bRet == -1)
        {
    		MessageBox(NULL, "GetMessage Error."  , gsTitleCaption, MB_SYSTEMMODAL);
            return 0;
            // handle the error and possibly exit
        }
        else if(!IsWindow(MyDialog_hWnd) || !IsDialogMessage(MyDialog_hWnd, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    if(!UninitializeApp())
    {
        return 1;
    }

	ReleaseMutex(xMutex);
	CloseHandle(xMutex);

	return 0;
}

The dialog procedure handles the usual messages; note that unlike a modal dialog it must keep returning control to the pump, and closing it with EndDialog makes IsWindow(MyDialog_hWnd) go false so the loop exits cleanly:

BOOL CALLBACK MainDialog(HWND xHandle, UINT xMessage, WPARAM wParam, LPARAM lParam)
{
    static HWND TextEdit_hWnd   = NULL;
    static HWND TextStatic_hWnd = NULL;

    static HMENU SystemMenu     = NULL;

    if(xMessage == WM_INITDIALOG)
    {
        MainDialog_hWnd = xHandle;

        SystemMenu = GetSystemMenu(xHandle, FALSE);
        AppendMenu(SystemMenu, MF_SEPARATOR, 0, 0);
        AppendMenu(SystemMenu, MF_STRING, 1000, "About");

        SendMessage(xHandle, (UINT)WM_SETTEXT, (WPARAM)0, (LPARAM)gsTitleCaption);
        SendMessage(xHandle, WM_SETICON, TRUE, (LPARAM) LoadIcon(AppInstance, MAKEINTRESOURCE(IDI_MAIN)) );

        TextEdit_hWnd   = GetDlgItem(xHandle, IDC_TEXTBOX);
        TextStatic_hWnd = GetDlgItem(xHandle, IDC_STATICTEXT);

        Set_Text(TextEdit_hWnd, "Text here");
        Set_Text(TextStatic_hWnd, "will appear here.");

        CenterWindow(xHandle);

        return TRUE; // Return TRUE to set the keyboard focus, Otherwise return FALSE
    }

    if(xMessage == WM_COMMAND)
    {
        if(wParam == IDC_OK) //- Ok (Dialog Command)
        {
            char TempText[256];
            Get_TextEx(TextEdit_hWnd, TempText, 255);
            Set_Text(TextStatic_hWnd, TempText);
            return TRUE;
        }

        if(wParam == IDC_CANCEL) //- Cancel (Dialog Command)
        {
            EndDialog(xHandle, 0);
            return TRUE;
        }

        return FALSE;
    }

	if(xMessage == WM_PAINT)
    {
        HDC ThisHDC;
        PAINTSTRUCT ThisPS;

        ThisHDC = BeginPaint(xHandle, &ThisPS);

        // Any painting should be done here

        EndPaint(xHandle, &ThisPS);
        return TRUE;
    }

    if(xMessage == WM_CLOSE)
    {
        EndDialog(xHandle,0);
        return TRUE;
    }

    return FALSE;
}

The single-instance mutex check around the whole thing is the same pattern as the modal examples - open first, create only if nobody else holds it.

An unhandled error has occurred. Reload 🗙