[英]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. 这样可以防止用户在完成当前功能之前与其他功能进行交互。
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
}
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. 这与DialogBox
和DialogBoxIndirect
不同,后者具有自己的消息循环,只有在用户或其他消息关闭对话框后才返回。
The usage for CreateDialog
, CreateDialogIndirectParam
... is as follows: CreateDialog
, CreateDialogIndirectParam
...的用法如下:
CreateDialogIndirectParam(...)
ShowWindow(...)
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
if(hDlgWnd == 0 || !IsDialogMessage(hDlgWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
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.