繁体   English   中英

当主GUI线程被阻止时,如何从工作线程创建无模式对话框?

[英]How to create a modeless dialog from a worker thread when main GUI thread is blocked?

我的目标是编写一个类(我们将其CProgressDlg ), CProgressDlg可用于在主UI线程中的某些操作完成时间超过例如1秒时显示带有进度条的对话框窗口。 所以以前写的方法:

if(do_work)
{
    for(int i = 0; i < a_lot; i++)
    {
        //Do work...
        ::Sleep(100);     //Use sleep to simulate work
    }
}

可以很容易地调整为以下形式(伪代码):

if(do_work)
{
    CProgressDlg m_progDlg;

    for(int i = 0; i < a_lot; i++)
    {
        //Do work...
        ::Sleep(100);     //Use sleep to simulate work

        if(m_progDlg.UpdateWithProgress(i))
        {
            //User canceled it
            break;
        }
    }
}

因此,要实现它,我将从CProgressDlg构造函数启动一个工作线程:

::CreateThread(0, 0, ThreadProcProgressDlg, (LPVOID)0, 0, 0);

然后从工作线程中创建一个无模式对话框,该对话框将为用户显示进度栏和取消按钮:

DWORD WINAPI ThreadProcProgressDlg(
  _In_ LPVOID lpParameter
)
{
    //Wait a little
    ::Sleep(1000);

    HMODULE hModule = AfxGetResourceHandle();
    ASSERT(hModule);

    //Get parent window
    //(Can't use main window, as its UI thread is blocked)
    HWND hParentWnd = NULL;

    const static BYTE dlgTemplate[224] = {
        0x1, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc8, 0x0, 0xc8, 0x90, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdb, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x90, 0x1, 0x0, 0x1, 0x4d, 0x0, 0x53, 0x0, 0x20, 0x0, 0x53, 0x0, 0x68, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, 0x0, 0x20, 0x0, 0x44, 0x0, 0x6c, 0x0, 0x67, 0x0, 0x0, 0x0, 
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x50, 0x92, 0x0, 0x36, 0x0, 0x42, 0x0, 0xe, 0x0, 0x2, 0x0, 0x0, 0x0, 0xff, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x81, 0x0, 0x2, 0x50, 0x7, 0x0, 0x7, 0x0, 0xcd, 0x0, 0x19, 0x0, 0xed, 0x3, 0x0, 0x0, 0xff, 0xff, 0x82, 0x0, 0x0, 0x0, 0x0, 0x0, 
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x50, 0x7, 0x0, 0x21, 0x0, 0xcd, 0x0, 0x7, 0x0, 0xec, 0x3, 0x0, 0x0, 0x6d, 0x0, 0x73, 0x0, 0x63, 0x0, 0x74, 0x0, 0x6c, 0x0, 0x73, 0x0, 0x5f, 0x0, 0x70, 0x0, 0x72, 0x0, 0x6f, 0x0, 0x67, 0x0, 0x72, 0x0, 0x65, 0x0, 0x73, 0x0, 0x73, 0x0, 0x33, 0x0, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x81, 0x0, 0x2, 0x50, 0x7, 0x0, 0x29, 0x0, 0xcd, 0x0, 0x8, 0x0, 0xee, 0x3, 0x0, 0x0, 0xff, 0xff, 0x82, 0x0, 0x0, 0x0, 0x0, 0x0, };

    //Show dialog
    HWND hDlgWnd = ::CreateDialogIndirectParam(hModule, (LPCDLGTEMPLATE)dlgTemplate, hParentWnd, DlgWndProc, (LPARAM)0);
    ASSERT(hDlgWnd);
    if(hDlgWnd)
    {
        ::ShowWindow(hDlgWnd, SW_SHOW);
    }

    return 0;
}

最小对话过程(仅用于显示)如下所示:

INT_PTR CALLBACK DlgWndProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
        }
        return TRUE;

        case WM_COMMAND:
        {
            UINT uCmd = LOWORD(wParam);

            if (uCmd == IDOK || 
                uCmd == IDCANCEL)
            {
                ::DestroyWindow(hDlg);

                return (INT_PTR)TRUE;
            }
        }
        break;
    }

    return (INT_PTR)FALSE;
}

但是,当我运行这段代码时,我的无模式对话框显示了片刻,然后消失了。 我知道我可能没有做任何事情来从工作线程中正确显示它。

知道我想念什么吗?

要使一个线程显示一个窗口,必须有一个消息循环,以便该窗口接收消息。 工作线程通常没有消息循环,因此无法显示任何窗口。 否则,您需要定期调用GetMessage() ,这是一种不好的做法,但是仍然可以正常工作。 收到消息后,请使用TranslateMessage()和DispatchMessage()。

另请参阅工作线程没有消息循环(MFC,Windows)。 我们可以使其接收消息吗?

正如其他人指出的那样,您不能简单地从非GUI线程创建窗口。 即使您能够做到,仍然会像您所说的那样存在主线程“挂起”的问题。

您在这里有2个解决方案:1)使用消息泵技术2)将工作移到线程中并在显示进度窗口时等待GUI

不幸的是,两种解决方案都需要您逐案处理。 您需要在GUI上手动识别所有可能很长的操作,然后修改其代码。

在这两种情况下,我都喜欢使用模式对话框进行进度条控制,因为模式对话框会阻止对主UI的访问。 这样可以防止用户在完成当前功能之前与其他功能进行交互。

  1. 防止挂起的最基本方法是添加一个监视消息队列并将其泵送的功能:
bool CMyGUIWnd::PumpAppMessages()
{
    MSG msg;
    while (::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
        if (!AfxGetApp ()->PumpMessage ()) {
            ::PostQuitMessage (0);
            return false;
        }
    }

    return true;
}

在任何冗长的代码中(例如您的列排序),您都需要找到将PumpAppMessages洒到什么地方以保持程序响应。

当然,您不想一直调用它,并且可能要确保仅在一定的毫秒数(在下面的示例中为250)之后才调用它:

bool CMyGUIWnd::PumpMessages()
{
    //
    // Retrieve and dispatch any waiting messages.
    //
    bool do_update = false;
    DWORD cur_time = ::GetTickCount ();

    if (cur_time < m_last_pump_message_time){
        do_update = true; // wrap around occurred
    }else{
        DWORD dt = cur_time - m_last_pump_message_time;
        if (dt > 250){
            do_update = true;
        }
    }

    if (do_update)
    {
        m_last_pump_message_time = cur_time;    
        return PumpAppMessages();
    }

    return true;
}

其中的m_last_pump_message_time::GetTickCount()初始化。

要显示进度条和阻止用户界面,您需要编写一个进度对话框类,该对话框一旦创建便会调用冗长的函数。 您可以在函数之前实例化此对话框,并通过DoModal调用阻止UI。

void CMyGUIWnd::LengthyCallWrapper()
{
    CProgressDlg dlg (&LengthyCall);
    dlg.DoModal();
}

void CMyGUIWnd::LengthyCall()
{
    // Long process with lots of PumpMessages calls to keep UI alive
}
  1. 第二种方法需要做更多的工作,但是由于不依赖于消息泵送的频率,因此使UI更具响应性。

您将再次使用进度对话框类,该类使用冗长的函数指针并在工作线程中执行该指针。 线程完成后,应在对话框中发布一条消息。 响应此消息,对话框将关闭自己的解锁UI。

对于这两种情况的实际进度报告,您的LengthyCall应该使用一个指向进度对话框的指针,以便可以更新适当的控件。 在第二种方法中,设置任何进度变量时,您将需要添加CCriticalSection类的同步对象,但不会直接修改任何控件。 相反,您可以设置一个计时器,并根据提供的变量定期检查和更新进度控件。

希望这可以帮助。

 CreateDialogIndirectParam(...) ShowWindow(...) 

CreateDialog立即返回,该线程在ShowWindow之后退出,因此无模式对话框关闭,因为线程已完成。

这与DialogBoxDialogBoxIndirect不同,后者具有自己的消息循环,只有在用户或其他消息关闭对话框后才返回。

CreateDialogCreateDialogIndirectParam ...的用法如下:

CreateDialogIndirectParam(...)
ShowWindow(...)

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


在这种情况下,您可以仅从GUI线程借用一个窗口。 例:

 class CProgressDlg : public CDialog { public: bool stop; CProgressDlg() { stop = false; } void OnCancel() { stop = true; CDialog::OnCancel(); } }; UINT WorkerThread(LPVOID ptr) { CProgressDlg* dlg = (CProgressDlg*)ptr; for(int i = 0; i < 10; i++) { Sleep(1000); if(dlg->stop) { dlg->MessageBox(L"stopped"); break; } } dlg->SendMessage(WM_COMMAND, IDCANCEL); return 0; } void CMyWindow::foo() { m_progress.Create(IDD_PROGRESS, this); m_progress.ShowWindow(SW_SHOW); AfxBeginThread(WorkerThread, (LPVOID)&m_progress); } 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM