2015-12-14 54 views
2

我試圖在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); 
} 

已足夠讓響應進度對話框啓動並運行。

然而,在實踐中,我仍然需要在do_more_work()泵信息;如果我不這樣做,直到循環結束後,進度對話框纔會顯示出來!所以我誤解MSDN時說:「該對象然後處理後臺線程更新」?好吧,所以我確實需要抽取消息,但是現在我仍然對MSDN所說的內容感到困惑,因爲:我可以從我的工作線程運行哪些IProgressDialog方法?知道這一點,我可以適當地重構我的代碼。

然而,更嚴重的問題是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...

+0

關鍵短語從文檔:「對象,然後處理更新**在後臺線程**,並允許用戶取消操作」 。你沒有創建一個線程。 UI線程必須保持可用來顯示進度,當它忙於執行代碼時不能這樣做。 –

+0

啊;我認爲當它說「對象」它是指IProgressDialog,而不是我自己的對象。這就說得通了;謝謝。我在猜測IProgressDialog的作者隱式使用消息泵的操作,因爲shlobj.h根本沒有提及它們:/ – andlabs

+0

實際上,這導致了後續的「是任何IProgressDialog的方法可以安全地在後臺線程上運行?「;我將這個問題添加到這個問題中,因爲它仍然是重要的,因爲它會影響我如何溝通這兩個線程。 – andlabs

回答

1

我不知道這是否是一個「乾淨」的解決方案,但由於你有一個窗口句柄進入該進程對話框,爲什麼不在調用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); 
相關問題