Click here to Skip to main content
14,665,497 members
Rate this:
Please Sign up or sign in to vote.
See more:
INTRODUCTION AND RELEVANT INFORMATION:

I have made a simple dialog in the resource editor.

It has a button.

When pressed, button activates a thread function that populates Word document using OLE automation.

I need to handle WM_CLOSE properly, because user can close dialog box during the population of Word.

I have designed a custom message ( WM_THREAD_OK ), which thread sends when finishes, or when finishes with cleaning up of the COM objects after it receives a signal to abort.

Dialog box is modeless one.

The concept of my code:

In my dialog, I have a flag that indicates that user clicked on close button.

It is a bool variable defined just bellow the dialog procedure's header.

In my WM_INITDIALOG I set it to false.

When user presses close button or ESC key, this flag is set to true.

Now is the time to check if thread has finished execution.

In both handlers ( close button/ESC key ) thread handle is checked, and if thread handle is NULL then we do a proper cleanup and we destroy the window.

If thread is still working, abortion signal is sent to the thread and we return from those handlers, waiting for handler to threads custom message to close the dialog.

Also, dialog box is programatically hidden during the abortion of the thread, so the user can get the illusion of the expected behavior.

Here are relevant code snippets:

// static bool closeDlg;

case WM_INITDIALOG:
     {
          closeDlg = false;

          // other initialization ...

     }
     return TRUE;

case IDCANCEL:
     {

        ShowWindow( hwnd, SW_HIDE ); // hide window

        if( threadHandle ) // thread is running, abort it
        {

           data.bContinue = false; // set abortion flag for thread

           closeDlg = true; // set auto exit flag

           break; // just return, thread message will close dialog

        }

        DestroyWindow(hwnd);

        hDlgTSO = NULL; // set dialogs global handle to NULL

        break;
     }

  // other WM_COMMAND messages...

  case WM_THREAD_OK: // custom message sent by a thread just before it exits

        if( threadHandle )
        {		
           WaitForSingleObject( threadHandle, INFINITE );
   
           CloseHandle( threadHandle );

           threadHandle = NULL;
        }

        ShowWindow( GetDlgItem( hwnd, IDOK ), SW_SHOW );

        if( closeDlg )
           PostMessage( hwnd, WM_CLOSE, 0, 0 );

        return TRUE;
        break;

  case WM_CLOSE:

       ShowWindow( hwnd, SW_HIDE );

       if( threadHandle ) // thread is running, abort it
       {

           data.bContinue = false; // set abortion flag for thread

           closeDlg = true; // set auto exit flag

           return TRUE; // just return, thread's custom message handler
                        // will take care of closing
       }

       DestroyWindow(hwnd);

       hDlgTSO = NULL; // set dialog's global handle to NULL

       return TRUE;

       break;

   // other code for dialog ... 


Dialog box is created as a response to the click like this:

case 4001:

  if( !IsWindow( hDlgTSO ) )
 
     hDlgTSO = CreateDialog( hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, Print );
        
  else
            
     MessageBox( hWnd, L"Already open!", L"Info", MB_ICONINFORMATION );

  break;


And this is main window's message loop :

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    if( ( !IsWindow( hDlgTSO ) ) || ( !IsDialogMessage( hDlgTSO, &Msg ) ) )
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}


PROBLEM:

The problem occurs when I try to close dialog box during the population of the Word document:

When I click on the close button ( X in the top right ) the dialog box doesn't disappear from the screen, but stays there ( remember that I have hidden it via ShowWindow ).

As soon as I take ANY action after that( clicking on desktop, or on my applications main window, or if I switch to a different window ... ), it disappears.

Thread function exits properly.

It seems as if never gets WM_CLOSE message sent from the WM_THREAD_OK handler.

However, my IDCANCEL handler works well, yet it follows the same algorithm.

MY EFFORTS:

At this moment, I am reading the MSDN documentation for WM_CLOSE and for message loops.

I really can't figure out the problem, so I do not know where to search.

QUESTION:

How can I modify the above code so dialog box gets properly closed ?

If additional code is required, ask for it. It is omitted to keep the question short.

Thank you.

Regards.

EDIT:

The problem in my code was the existence of multiple modeless dialog boxes. The main window message loop was not implemented properly which caused the malfunction. I have accepted the answer bellow because without studying that code I would not be able to find the cause of my problem.
Posted
Updated 12-Jan-14 5:52am
v2

1 solution

Rate this:
Please Sign up or sign in to vote.

Solution 1

Here is a super ugly testcode that works, you can cheat from it. Compile it as an ANSI (not UNICODE) console program.

You can debug both this and your program to compare what is different. You can also copy paste parts.

DialogTest.cpp:
#include "stdafx.h"
#include "resource.h"
#include <windows.h>

const UINT WM_THREAD_OK = WM_USER + 10;
const int SHOW_DLG_BUTTON_ID = 2;
const int SIMULATE_THREAD_MESSAGE_BUTTON_ID = 3;

HINSTANCE g_hInstance = (HINSTANCE)GetModuleHandle(NULL);
HWND g_hThreadFinishSimulateWnd = NULL;
HWND g_hMainWnd = NULL;
HWND g_hDialog = NULL;
HWND g_hShowDlgButton = NULL;
HWND g_hSimulateThreadFinishButton = NULL;

bool g_ThreadRunning = false;
bool g_CloseDialogRequest = false;
bool g_CloseMainWndRequest = false;

void StartThread()
{
    // TODO: actually create and start the thread...
    g_ThreadRunning = true;
    SetWindowText(GetDlgItem(g_hDialog, IDC_STATIC_THREAD_STATE), "yes");
    EnableWindow(GetDlgItem(g_hDialog, IDC_START_THREAD), FALSE);
    EnableWindow(g_hSimulateThreadFinishButton, TRUE);
}

void OnThreadFinished()
{
    // TODO: wait for thread handle and close it
    g_ThreadRunning = false;
    // The if is just cosmetics, we don't enable the disabled button
    // on the window/dialog if it is about to be closed.
    if (!g_CloseDialogRequest)
    {
        SetWindowText(GetDlgItem(g_hDialog, IDC_STATIC_THREAD_STATE), "no");
        EnableWindow(GetDlgItem(g_hDialog, IDC_START_THREAD), TRUE);
    }
    EnableWindow(g_hSimulateThreadFinishButton, FALSE);
}

void RequestThreadExit()
{
    // TODO: Send a cancel request to the thread somehow
}


void DisableAllChildControls(HWND wnd)
{
    HWND child = GetWindow(wnd, GW_CHILD);
    while (child)
    {
        EnableWindow(child, FALSE);
        child = GetWindow(child, GW_HWNDNEXT);
    }
}

void RequestThreadExitAndDialogClose()
{
    if (g_CloseDialogRequest)
        return;
    RequestThreadExit();
    g_CloseDialogRequest = true;
    SetWindowText(g_hDialog, "Dialog close requested...");
#if 0
    // Well, disabling the currently focused dialog is not a good practice...
    EnableWindow(g_hDialog, FALSE);
#elif 0
    // In some situations this may be correct, but often it isn't... If you want to
    // lie to the user that the cancel has finished then it may be OK but in this case
    // what do you lie to the user if she wants to start the thread again and the previous
    // thread is still in progress???
    ShowWindow(g_hDialog, SW_HIDE);
#elif 1
    // Disabling all child controls and informing the user that the close request
    // has been processed - we did the feedback by setting the dialog title but
    // we could use a label too with "cancelling..." text.
    DisableAllChildControls(g_hDialog);
#endif
}

void MyLog(const char* msg)
{
    printf("- %-50s thread_running:%d dlg_close_req:%d main_wnd_close_req:%d\n",
        msg,
        g_ThreadRunning ? 1 : 0,
        g_CloseDialogRequest ? 1 : 0,
        g_CloseMainWndRequest ? 1 : 0);
}

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        MyLog("Dialog: WM_INITDIALOG");
        g_CloseDialogRequest = false;
        EnableWindow(g_hShowDlgButton, FALSE);
        return TRUE;
    case WM_CLOSE:
        MyLog("Dialog: WM_CLOSE");
        if (g_ThreadRunning)
        {
            RequestThreadExitAndDialogClose();
        }
        else
        {
            // The if is just cosmetics, we don't enable the disabled button
            // on the window/dialog if it is about to be closed.
            if (!g_CloseMainWndRequest)
                EnableWindow(g_hShowDlgButton, TRUE);
            EndDialog(hwndDlg, 0);
            g_hDialog = NULL;
        }
        return TRUE;
    case WM_THREAD_OK:
        MyLog("Dialog: WM_THREAD_OK");
        OnThreadFinished();
        if (g_CloseDialogRequest)
            PostMessage(hwndDlg, WM_CLOSE, 0, 0);
        if (g_CloseMainWndRequest)
            PostMessage(g_hMainWnd, WM_CLOSE, 0, 0);
        return TRUE;
    case WM_COMMAND:
        {
            int ctrl_id = LOWORD(wParam);
            if (ctrl_id == IDC_START_THREAD)
            {
                MyLog("Dialog: WM_COMMAND/ID_START_THREAD");
                if (!g_ThreadRunning)
                    StartThread();
                return TRUE;
            }
            else if (ctrl_id == IDCANCEL)
            {
                MyLog("Dialog: WM_COMMAND/IDCANCEL");
                PostMessage(hwndDlg, WM_CLOSE, 0, 0);
                return TRUE;
            }
        }
        break;
    }
    return FALSE;
}


LRESULT CALLBACK MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        MyLog("MainWnd: WM_CREATE");
        g_hShowDlgButton = CreateWindowEx(0, "Button", "Create/show my test dialog", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
            0, 0, 200, 35, hWnd, (HMENU)SHOW_DLG_BUTTON_ID, g_hInstance, NULL);
        break;
    case WM_CLOSE:
        MyLog("MainWnd: WM_CLOSE");
        if (g_ThreadRunning)
        {
#if 1
            if (!g_CloseMainWndRequest)
            {
                g_CloseMainWndRequest = true;

                SetWindowText(hWnd, "Main window close requested...");
                DisableAllChildControls(hWnd);

                RequestThreadExitAndDialogClose();
            }
#else
            // A very simple variation to handle a running thread
            MessageBox(hWnd, "Sorry, can't exit while the thread is running!", "INFO", MB_OK|MB_ICONHAND);
#endif
            return 0;
        }

        // If we close the main window when the dialog is open but
        // the thread isn't running...
        if (g_hDialog)
            EndDialog(g_hDialog, 0);

        // Letting DefWindowProc to destroy the main window.
        break;
    case WM_DESTROY:
        MyLog("MainWnd: WM_DESTROY");
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
        {
            int ctrl_id = LOWORD(wParam);
            if (ctrl_id == SHOW_DLG_BUTTON_ID)
            {
                MyLog("MainWnd: WM_COMMAND/SHOW_DLG_BUTTON_ID");
                if (!g_hDialog)
                {
                    g_hDialog = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DialogProc);
                    ShowWindow(g_hDialog, SW_SHOW);
                }
                return 0;
            }
        }
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}


bool CreateMainWnd()
{
    static const char CLASS_NAME[] = "MainWndClass";
    WNDCLASS wc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = g_hInstance;
    wc.lpfnWndProc = &MainWndProc;
    wc.lpszClassName = CLASS_NAME;
    wc.lpszMenuName = NULL;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    if (!RegisterClass(&wc))
        return false;
    g_hMainWnd = CreateWindowEx(
        0,
        CLASS_NAME,
        "Main Window",
        WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
        0, 150, 250, 100,
        NULL,
        NULL,
        g_hInstance,
        NULL
        );
    return true;
}

LRESULT CALLBACK SimulateThreadFinishWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        g_hSimulateThreadFinishButton = CreateWindowEx(0, "Button", "Simulate thread finish", WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|WS_DISABLED,
            0, 0, 200, 35, hWnd, (HMENU)SIMULATE_THREAD_MESSAGE_BUTTON_ID, g_hInstance, NULL);
        break;
    case WM_CLOSE:
        PostMessage(g_hMainWnd, WM_CLOSE, 0, 0);
        return 0;
    case WM_COMMAND:
        {
            int ctrl_id = LOWORD(wParam);
            if (ctrl_id == SIMULATE_THREAD_MESSAGE_BUTTON_ID)
            {
                MyLog("MainWnd: WM_COMMAND/SIMULATE_THREAD_MESSAGE_BUTTON_ID");
                if (g_hDialog && g_ThreadRunning)
                    PostMessage(g_hDialog, WM_THREAD_OK, 0, 0);
            }
        }
        break;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

// Creates a utility window for us. We need this "debug" window because
// in case of main window close request both the main window and the
// dialog is disabled so we wouldn't be able to press a button on them.
bool CreateSimulateThreadFinishWindow()
{
    static const char CLASS_NAME[] = "SimulateWndClass";
    WNDCLASS wc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = g_hInstance;
    wc.lpfnWndProc = &SimulateThreadFinishWndProc;
    wc.lpszClassName = CLASS_NAME;
    wc.lpszMenuName = NULL;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    if (!RegisterClass(&wc))
        return false;
    g_hThreadFinishSimulateWnd = CreateWindowEx(
        0,
        CLASS_NAME,
        "Thread finish simulate window",
        WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
        0, 0, 250, 100,
        NULL,
        NULL,
        g_hInstance,
        NULL
        );
    return true;
}

int main()
{
    if (!CreateMainWnd() || !CreateSimulateThreadFinishWindow())
        return -1;

    ShowWindow(g_hThreadFinishSimulateWnd, SW_SHOW);
    UpdateWindow(g_hThreadFinishSimulateWnd);

    ShowWindow(g_hMainWnd, SW_SHOW);
    UpdateWindow(g_hMainWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!g_hDialog || !IsDialogMessage(g_hDialog, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    DestroyWindow(g_hThreadFinishSimulateWnd);

    printf("Program finished, press ENTER to exit.\n");
    getchar();
    return (int)msg.wParam;
}


resource.h:
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Test.rc
//
#define IDD_DIALOG1                     101
#define IDC_START_THREAD                1001
#define IDC_STATIC_THREAD_STATE         1002

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1003
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


Test.rc:
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// Hungarian (Hungary) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_HUN)
LANGUAGE LANG_HUNGARIAN, SUBLANG_DEFAULT

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIALOG1 DIALOGEX 0, 0, 191, 48
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    PUSHBUTTON      "Start thread",IDC_START_THREAD,7,27,50,14
    PUSHBUTTON      "IDCANCEL: simulating ESC",IDCANCEL,63,27,119,14
    LTEXT           "Thread running:",IDC_STATIC,7,7,52,8
    LTEXT           "no",IDC_STATIC_THREAD_STATE,63,7,76,8
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_DIALOG1, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 182
        TOPMARGIN, 7
        BOTTOMMARGIN, 41
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // Hungarian (Hungary) resources
/////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////////////
// English (United States) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

#endif    // English (United States) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
   
v4
Comments
AlwaysLearningNewStuff 12-Jan-14 11:49am
   
My +5 for the effort!

Sorry for answering this late, but I am working on a huge project all by myself and got distracted with other obligations.

After carefully studying your example, I have found the problem in my code:

I have multiple modeless dialog boxes, and the reason why my code malfunctioned was in improper implementation of the main window's message loop.

After that was fixed, everything works fine.

Thank you so much for your efforts and for teaching me how to use threads properly!

I wish you all best in the New Year!

Best regards.
xenotron 12-Jan-14 11:55am
   
You are welcome! :-) Happy New Year!

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




CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100