简体   繁体   English

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

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

My goal is to write a class (let's call it CProgressDlg ) that can be used to display a dialog window with a progress bar when some operation in the main UI thread takes longer than, say 1 second, to finish. 我的目标是编写一个类(我们将其CProgressDlg ), CProgressDlg可用于在主UI线程中的某些操作完成时间超过例如1秒时显示带有进度条的对话框窗口。 So a previously written method: 所以以前写的方法:

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

can be easily adjusted as something like this (pseudo-code): 可以很容易地调整为以下形式(伪代码):

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;
        }
    }
}

So to implement it I would start a worker thread from the CProgressDlg constructor: 因此,要实现它,我将从CProgressDlg构造函数启动一个工作线程:

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

And then from a worker thread I would create a modeless dialog that will display the progress bar and a cancel button for the user: 然后从工作线程中创建一个无模式对话框,该对话框将为用户显示进度栏和取消按钮:

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;
}

Where the minimal dialog procedure (just to display it) will be something like this: 最小对话过程(仅用于显示)如下所示:

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;
}

But when I run this code, my modeless dialog is shown for a split second and then disappears. 但是,当我运行这段代码时,我的无模式对话框显示了片刻,然后消失了。 I understand that I probably haven't done something to display it properly from a worker thread. 我知道我可能没有做任何事情来从工作线程中正确显示它。

Any idea what am I missing? 知道我想念什么吗?

For a thread to display a window, there must be a message loop so the window receives messages. 要使一个线程显示一个窗口,必须有一个消息循环,以便该窗口接收消息。 Worker threads typically do not have message loops, so no window can be displayed. 工作线程通常没有消息循环,因此无法显示任何窗口。 Otherwise, you need to call GetMessage() periodically, a bad practise but it will anyway work. 否则,您需要定期调用GetMessage() ,这是一种不好的做法,但是仍然可以正常工作。 After you get a message, Use TranslateMessage() and DispatchMessage(). 收到消息后,请使用TranslateMessage()和DispatchMessage()。

Also see Worker thread doesn't have message loop (MFC, windows). 另请参阅工作线程没有消息循环(MFC,Windows)。 Can we make it to receive messages? 我们可以使其接收消息吗?

As others pointed out you cannot simply create windows from non GUI thread. 正如其他人指出的那样,您不能简单地从非GUI线程创建窗口。 Even if you were able to, you would still have a problem of main thread 'hanging' as you said. 即使您能够做到,仍然会像您所说的那样存在主线程“挂起”的问题。

Your have 2 solutions here: 1) Use message pumping technique 2) Move the work into a thread and wait on GUI while showing progress window 您在这里有2个解决方案:1)使用消息泵技术2)将工作移到线程中并在显示进度窗口时等待GUI

Unfortunately both solutions require you to deal with this on case by case basis. 不幸的是,两种解决方案都需要您逐案处理。 You need to manually identify all potentially long operations on GUI and then modify their code. 您需要在GUI上手动识别所有可能很长的操作,然后修改其代码。

In both cases I like to use modal dialog for progress bar control because modal dialog blocks access to main UI. 在这两种情况下,我都喜欢使用模式对话框进行进度条控制,因为模式对话框会阻止对主UI的访问。 This prevents user from interacting with other features until current one is completed. 这样可以防止用户在完成当前功能之前与其他功能进行交互。

  1. The most basic way to prevent hanging is to add a function that peeks at message queue and pumps it: 防止挂起的最基本方法是添加一个监视消息队列并将其泵送的功能:
bool CMyGUIWnd::PumpAppMessages()
{
    MSG msg;
    while (::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
        if (!AfxGetApp ()->PumpMessage ()) {
            ::PostQuitMessage (0);
            return false;
        }
    }

    return true;
}

In any lengthy code (like your column sort) you would need to find places where to sprinkle this PumpAppMessages to keep program responsive. 在任何冗长的代码中(例如您的列排序),您都需要找到将PumpAppMessages洒到什么地方以保持程序响应。

Of course you don't want to call this all the time and you might want to make sure you only call it after certain number of milliseconds (250 in my example below): 当然,您不想一直调用它,并且可能要确保仅在一定的毫秒数(在下面的示例中为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;
}

where m_last_pump_message_time is initialized with ::GetTickCount() . 其中的m_last_pump_message_time::GetTickCount()初始化。

To show progress bar and block UI, you would need to write a progress dialog class which calls your lengthy function once it's created. 要显示进度条和阻止用户界面,您需要编写一个进度对话框类,该对话框一旦创建便会调用冗长的函数。 You would instantiate this dialog before the function and block UI with DoModal call. 您可以在函数之前实例化此对话框,并通过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. The second approach is a bit more work, but makes the UI more responsive because it doesn't depend on how often message pumping is done. 第二种方法需要做更多的工作,但是由于不依赖于消息泵送的频率,因此使UI更具响应性。

You would again use the progress dialog class which takes the lengthy function pointer and executes it in worker thread. 您将再次使用进度对话框类,该类使用冗长的函数指针并在工作线程中执行该指针。 Once the thread is finished it should post a message to dialog. 线程完成后,应在对话框中发布一条消息。 In response to this message the dialog would close itself unblocking UI. 响应此消息,对话框将关闭自己的解锁UI。

For actual progress reporting in both cases your LengthyCall should take a pointer to the progress dialog so that it could update proper controls. 对于这两种情况的实际进度报告,您的LengthyCall应该使用一个指向进度对话框的指针,以便可以更新适当的控件。 In the second approach you would need to add sync objects like CCriticalSection when setting any progress variables but you would not modify any controls directly. 在第二种方法中,设置任何进度变量时,您将需要添加CCriticalSection类的同步对象,但不会直接修改任何控件。 Instead, you would setup a timer and check and update progress controls in regular intervals based on provided variables. 相反,您可以设置一个计时器,并根据提供的变量定期检查和更新进度控件。

Hope this helps. 希望这可以帮助。

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

CreateDialog returns immediately, the thread exits after ShowWindow , and so the mode-less dialog closes because the thread is finished. CreateDialog立即返回,该线程在ShowWindow之后退出,因此无模式对话框关闭,因为线程已完成。

This is different than DialogBox , DialogBoxIndirect which have their own message loop, and do not return until the dialog is closed by the user or another message. 这与DialogBoxDialogBoxIndirect不同,后者具有自己的消息循环,只有在用户或其他消息关闭对话框后才返回。

The usage for CreateDialog , CreateDialogIndirectParam ... is as follows: CreateDialogCreateDialogIndirectParam ...的用法如下:

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

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


In this case you can just borrow a window from GUI thread. 在这种情况下,您可以仅从GUI线程借用一个窗口。 Example: 例:

 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