我正在尝试在 PROGDLG_MODAL 模式下使用 IProgressDialog 并且遇到了两个障碍。
首先,根据我从 MSDN 收集到的信息和 <shlobj.h>
中的 block 评论描述 IProgressDialog 的头文件,您应该直接从您正在工作的线程中使用它,并且 IProgressDialog 将从另一个线程执行其 UI 内容;那是,
while (there_is_still_work_to_do()) {
if (pd->HasUserCancelled())
break;
do_more_work();
completed++;
pd->SetProgress64(completed, total);
}
足以启动并运行响应式进度对话框。
但是,在实践中,我仍然需要在
do_more_work()
中泵送消息。 ;如果我不这样做,直到循环结束后才会显示进度对话框!那么,当它说“对象然后在后台线程上处理更新”时,我是否误解了 MSDN?好的,所以我确实需要发送消息,但现在我仍然对 MSDN 所说的内容感到困惑:我可以从我的工作线程运行 IProgressDialog 的哪些方法?知道了这一点,我可以正确地重构我的代码。然而,一个更可怕的问题是
StopProgressDialog()
不会立即拆除 window 。事实上,Release()
也没有。 !在非模态情况下,进度对话框会在屏幕上停留一段时间。这个特定的问题之前已经解决过;我会解决的。然而,这一次,我将进度对话框用作 UI 模式对话框。如果我碰巧调用了另一个对话框函数,比如MessageBox()
,在释放 IProgressDialog 之后,我们会立即得到两个具有相同所有者窗口的模式对话框:进度对话框会自行关闭并在消息框仍在运行时重新启用主窗口。非模态的情况被别人解决了by merely hiding the progress dialog after calling
StopProgressDialog()
.虽然这确实隐藏了窗口,但它对模态没有任何作用。根据链接问题末尾提问者的假设,我还尝试发布和发送 WM_NULL
在 StopProgressDialog()
之后.那也没用。最后,我尝试设置一个 WinEvents Hook ,等待窗口被销毁并在它被销毁时触发一个事件对象。这确实有效;
MessageBox()
在进度对话框被销毁之前不会发生。然而,这并不完美:主窗口不会立即再次激活,MessageBox()
在我单击主窗口的任务栏图标之前,它甚至不会出现在后台。(即便如此,因为我无法将 LPARAM 传递给我的 WinEvents 钩子(Hook),如果我想跨不同线程处理多个 IProgressDialogs,我需要有一个全局的窗口句柄列表来监视(及其关联的事件对象),即同步。幸运的是,出于我的目的,我不需要它。此外,以上所有内容都假设 CLSID_ProgressDialog 也是一个 IOleWindow;如果情况发生变化,那么......)
我用这种 WinEvents 方式做错了吗?我怀疑我没有调用
MsgWaitForMultipleObjectsEx()
正确,但如果事实证明 IProgressDialog 没有正确实现模态,那么我想我不走运:S下面的示例程序完成了上述所有操作。我已经在 Windows 7 64 位(构建为 64 位)上使用 Visual Studio 2013 对其进行了测试。
// 14 december 2015
#define _UNICODE
#define UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
void chk(HRESULT hr) { if (hr != S_OK) DebugBreak(); }
void doWork(bool pumpMessages)
{
UINT_PTR timer;
MSG msg;
if (!pumpMessages) {
Sleep(2000);
return;
}
timer = SetTimer(NULL, 20, 2000, NULL);
while (GetMessageW(&msg, NULL, 0, 0)) {
if (msg.message == WM_TIMER && msg.hwnd == NULL)
break;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
KillTimer(NULL, timer);
}
HWINEVENTHOOK hook;
HWND dialogWindow;
HANDLE dialogEvent;
void CALLBACK onDialogClosed(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
{
if (hwnd == dialogWindow)
SetEvent(dialogEvent);
}
void waitEvent(void)
{
MSG msg;
DWORD ret;
for (;;) {
ret = MsgWaitForMultipleObjectsEx(1, &dialogEvent,
INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
if (ret == WAIT_OBJECT_0) // event
break;
if (GetMessage(&msg, NULL, 0, 0) == 0)
break;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
void rundialogs(HWND parent, bool pumpMessages, bool tryHide, int tryHideWhat, bool abort)
{
IProgressDialog *pd;
IOleWindow *olewin;
HWND hwnd;
DWORD process;
DWORD thread;
chk(CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pd)));
chk(pd->SetTitle(L"Test"));
chk(pd->StartProgressDialog(parent, NULL,
PROGDLG_NORMAL | PROGDLG_MODAL | PROGDLG_AUTOTIME | PROGDLG_NOMINIMIZE,
NULL));
doWork(pumpMessages);
chk(pd->Timer(PDTIMER_RESET, NULL));
for (ULONGLONG i = 0; i < 10; i++) {
if (pd->HasUserCancelled())
break;
doWork(pumpMessages);
if (i == 5 && abort)
break;
chk(pd->SetProgress64(i + 1, 10));
}
chk(pd->QueryInterface(IID_PPV_ARGS(&olewin)));
chk(olewin->GetWindow(&hwnd));
olewin->Release();
// set up event hoook before stopping the progress dialog
// this way it won't get sdestroyed before the hook is set up
if (tryHide && tryHideWhat == 3) {
thread = GetWindowThreadProcessId(hwnd, &process);
dialogWindow = hwnd;
dialogEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
ResetEvent(dialogEvent);
hook = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY,
NULL, onDialogClosed,
process, thread,
WINEVENT_OUTOFCONTEXT);
}
chk(pd->StopProgressDialog());
if (tryHide)
switch (tryHideWhat) {
case 0: // hide
ShowWindow(hwnd, SW_HIDE);
break;
case 1: // send WM_NULL
SendMessageW(hwnd, WM_NULL, 0, 0);
break;
case 2: // post WM_NULL
PostMessageW(hwnd, WM_NULL, 0, 0);
break;
case 3: // winevents
waitEvent();
UnhookWinEvent(hook);
break;
}
pd->Release();
MessageBoxW(parent,
L"This should be MODAL to the main window!\n"
L"But you should see that in reality the main window\n"
L"is still enabled!",
L"mainwin",
MB_OK | MB_ICONINFORMATION);
}
HWND button;
HWND checkbox;
HWND checkbox2;
HWND combobox;
HWND checkbox3;
bool ischecked(HWND hwnd) { return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED; }
static LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_COMMAND && lParam == (LPARAM) button)
rundialogs(hwnd,
ischecked(checkbox),
ischecked(checkbox2),
(int) SendMessageW(combobox, CB_GETCURSEL, 0, 0),
ischecked(checkbox3));
if (uMsg == WM_CLOSE)
PostQuitMessage(0);
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSW wc;
HWND mainwin;
MSG msg;
CoInitialize(NULL);
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
RegisterClassW(&wc);
mainwin = CreateWindowExW(0,
L"mainwin", L"mainwin",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
200, 220,
NULL, NULL, hInstance, NULL);
button = CreateWindowExW(0,
L"button", L"Click Me",
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
10, 10, 150, 100,
mainwin, (HMENU) 100, hInstance, NULL);
checkbox = CreateWindowExW(0,
L"button", L"Pump Messages",
BS_CHECKBOX | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE,
10, 110, 150, 20,
mainwin, (HMENU) 101, hInstance, NULL);
checkbox2 = CreateWindowExW(0,
L"button", L"Try",
BS_CHECKBOX | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE,
10, 130, 50, 20,
mainwin, (HMENU) 101, hInstance, NULL);
combobox = CreateWindowExW(0,
L"combobox", L"",
CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE,
60, 130, 100, 100,
mainwin, (HMENU) 102, hInstance, NULL);
SendMessageW(combobox, CB_ADDSTRING, 0, (LPARAM) L"Hide");
SendMessageW(combobox, CB_ADDSTRING, 0, (LPARAM) L"Send");
SendMessageW(combobox, CB_ADDSTRING, 0, (LPARAM) L"Post");
SendMessageW(combobox, CB_ADDSTRING, 0, (LPARAM) L"WinEvents");
SendMessageW(combobox, CB_SETCURSEL, 0, 0);
checkbox3 = CreateWindowExW(0,
L"button", L"Abort Early",
BS_CHECKBOX | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE,
10, 150, 150, 20,
mainwin, (HMENU) 103, hInstance, NULL);
ShowWindow(mainwin, nCmdShow);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
CoUninitialize();
return 0;
}
更新 嗯,经过进一步检查,似乎当我停止进度对话框而不显示消息框时,所有者窗口确实 不是 重新获得焦点!我猜进度对话框根本无法正确处理模态;如果我删除
PROGDLG_MODAL
标记一切正常。哦,好吧:/我将不得不伪造这种方式或切换到其他方式。我可能只是在进度对话框本身上显示我的消息框,并希望 future 版本的 Windows 不会带走 IOleWindow。除非有更好的方法?或者除非在调用
StopProgressDialog()
之后手动执行模态并保持进度对话框无模式。已经足够好了(只有在StopProgressDialog()
开始之后才会报错)但是then again...
最佳答案
我不确定这是否是一个“干净”的解决方案,但既然你有那个进度对话框的窗口句柄,你为什么不在调用 MessageBox()
之前杀死它? ?
这对我有用(在您的测试应用程序中):
::SendMessage(hwnd, WM_DESTROY, 0, 0);
MessageBoxW(parent,
L"This should be MODAL to the main window!\n"
L"But you should see that in reality the main window\n"
L"is still enabled!",
L"mainwin",
MB_OK | MB_ICONINFORMATION);
关于winapi - IProgressDialog 问题 : Do I misunderstand its multithreading, 因为我似乎需要消息泵?我可以避免在它结束之前打开另一个对话框吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34273861/