我試圖在PROGDLG_MODAL模式下使用IProgressDialog,並且遇到了兩個障礙。IProgressDialog問題:我誤解了它的多線程,因爲我似乎需要消息泵?我可以避免在結束之前打開另一個對話框嗎?
首先,從我從MSDN收集的和描述IProgressDialog的<shlobj.h>
頭文件中的塊註釋中,您應該直接從您正在工作的線程使用它,並且IProgressDialog將從另一個線程;也就是,
while (there_is_still_work_to_do()) {
if (pd->HasUserCancelled())
break;
do_more_work();
completed++;
pd->SetProgress64(completed, total);
}
已足夠讓響應進度對話框啓動並運行。
然而,在實踐中,我仍然需要在好吧,所以我確實需要抽取消息,但是現在我仍然對MSDN所說的內容感到困惑,因爲:我可以從我的工作線程運行哪些IProgressDialog方法?知道這一點,我可以適當地重構我的代碼。do_more_work()
泵信息;如果我不這樣做,直到循環結束後,進度對話框纔會顯示出來!所以我誤解MSDN時說:「該對象然後處理後臺線程更新」?
然而,更嚴重的問題是StopProgressDialog()
不能立即拆除窗口。實際上,Release()
也沒有!在非模態情況下,顯示進度對話框會在屏幕上停留更長時間。這個特殊的問題已經解決過了;我會做到這一點。但是,這一次我將進度對話框用作UI模式對話框。如果我碰巧在釋放IProgressDialog後立即調用另一個對話框函數(例如MessageBox()
),那麼我們最終會看到具有相同所有者窗口的兩個模態對話框:進度對話框自行關閉,並在消息框重新啓用主窗口仍在運行。
非模態情況已被其他人解決by merely hiding the progress dialog after calling StopProgressDialog()
。雖然這確實隱藏了窗口,但它並沒有對模態做任何事情。根據鏈接問題末尾提問者的假設,我也嘗試在StopProgressDialog()
之後張貼併發送WM_NULL
。這也沒有奏效。
最後,我嘗試設置一個WinEvents鉤子,等待窗口被銷燬,並在事件對象發出時觸發。這確實起作用;在進度對話框銷燬之前,MessageBox()
不會發生。但是,這並不完美:主窗口不會立即再次激活,並且MessageBox()
甚至不會在後臺顯示,直到我單擊主窗口的任務欄圖標。 (即使這樣,因爲我不能將LPARAM傳遞給我的WinEvents鉤子,如果我想要跨不同線程處理多個IProgressDialog,我需要有一個全局的窗口句柄列表來監視(以及它們相關的事件對象),這是同步的,幸運的是,我不需要這個用於我的目的,而且,所有上述都假設CLSID_ProgressDialog也是一個IOleWindow;如果有變化,那麼......)
Am我用這個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;
}
UPDATE咦,經進一步檢查,似乎當我停止進度對話框不顯示在MessageBox,所有者窗口確實不得到可以關注回來了!我猜測進度對話框根本無法正確處理模態;如果我刪除PROGDLG_MODAL
標誌一切工作正常。哦,好的:/我將不得不僞造模態或切換到其他模式。
我可能只是在進度對話框中顯示我的消息框,並希望未來的Windows版本不會帶走IOleWindow。除非有更好的方法?或者除非調用StopProgressDialog()
後手動做的方式,保持進度對話框無模式足夠好(錯誤只會StopProgressDialog()
後報道開始),但then again...
關鍵短語從文檔:「對象,然後處理更新**在後臺線程**,並允許用戶取消操作」 。你沒有創建一個線程。 UI線程必須保持可用來顯示進度,當它忙於執行代碼時不能這樣做。 –
啊;我認爲當它說「對象」它是指IProgressDialog,而不是我自己的對象。這就說得通了;謝謝。我在猜測IProgressDialog的作者隱式使用消息泵的操作,因爲shlobj.h根本沒有提及它們:/ – andlabs
實際上,這導致了後續的「是任何IProgressDialog的方法可以安全地在後臺線程上運行?「;我將這個問題添加到這個問題中,因爲它仍然是重要的,因爲它會影響我如何溝通這兩個線程。 – andlabs