我的目标是编写一个类(我们称其为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;
}
但是,当我运行这段代码时,我的无模式对话框显示了片刻,然后消失了。我知道我可能没有做任何事情来从工作线程中正确显示它。
知道我想念什么吗?
答案 0 :(得分:1)
要使一个线程显示一个窗口,必须有一个消息循环,以便窗口接收消息。工作线程通常没有消息循环,因此无法显示任何窗口。否则,您需要定期致电GetMessage(),这是一个不好的做法,但是仍然可以。收到消息后,请使用TranslateMessage()和DispatchMessage()。
另请参见Worker thread doesn't have message loop (MFC, windows). Can we make it to receive messages?
答案 1 :(得分:1)
正如其他人指出的那样,您不能简单地从非GUI线程创建窗口。即使能够,您仍然会遇到您所说的主线程“挂起”的问题。
您在这里有2个解决方案: 1)使用消息泵技术 2)将工作移至线程中,并在显示进度窗口时等待GUI
不幸的是,这两种解决方案都要求您逐案处理。您需要在GUI上手动识别所有可能很长的操作,然后修改其代码。
在这两种情况下,我都喜欢使用模式对话框进行进度条控制,因为模式对话框会阻止对主UI的访问。这样可以防止用户在完成当前功能之前与其他功能进行交互。
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()
初始化。
要显示进度条和阻止UI,您需要编写一个进度对话框类,该对话框一旦创建便会调用冗长的函数。您可以在函数之前实例化此对话框,并通过DoModal
调用来阻止UI。
void CMyGUIWnd::LengthyCallWrapper()
{
CProgressDlg dlg (&LengthyCall);
dlg.DoModal();
}
void CMyGUIWnd::LengthyCall()
{
// Long process with lots of PumpMessages calls to keep UI alive
}
您将再次使用进度对话框类,该类使用冗长的函数指针并在工作线程中执行该指针。线程完成后,应在对话框中发布一条消息。响应此消息,对话框将关闭自己的解锁UI。
对于两种情况下的实际进度报告,您的LengthyCall
应该使用一个指向进度对话框的指针,以便它可以更新适当的控件。在第二种方法中,设置任何进度变量时,您需要添加CCriticalSection
之类的同步对象,但是您不会直接修改任何控件。相反,您应该设置一个计时器,并根据提供的变量定期检查和更新进度控件。
希望这会有所帮助。
答案 2 :(得分:0)
if(arr3[k]<arr3[k+1]) { j=arr3[k+1]; arr3[k+1] = arr3[k]; arr3[k]=j; }
const getPerson = (state, props) =>
state.entities.people.byId[props.id];
const getPersonDetails = createSelector(
getPerson,
state => state.entities.classes,
(person, classes) => {
// loop through classes, etc..
// return whatever
}
);
立即返回,线程在CreateDialogIndirectParam(...)
ShowWindow(...)
之后退出,因此无模式对话框因线程完成而关闭。
这不同于CreateDialog
,ShowWindow
,后者具有自己的消息循环,并且直到用户或其他消息关闭对话框后才返回。
DialogBox
,DialogBoxIndirect
...的用法如下:
CreateDialog
CreateDialogIndirectParam