2015-12-14 1 views
2

Я пытаюсь использовать IProgressDialog в режиме PROGDLG_MODAL, и я нажимаю два коряга.Вопросы IProgressDialog: Не понимаю ли я его многопоточность, поскольку мне, похоже, нужен насос сообщений? Могу ли я избежать открытия другого диалога до его окончания?

Во-первых, из того, что я собираю из MSDN и комментария блока в файле заголовка <shlobj.h>, описывающего 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() не срывает окно сразу. На самом деле, ни Release()! В немодальных ситуациях, похоже, что диалог прогресса задерживается на экране еще некоторое время. Эта конкретная проблема была рассмотрена ранее; Я доберусь до этого. Однако на этот раз я использую диалог прогресса в виде диалогового окна с интерфейсом UI. Если мне удастся вызвать другую функцию диалогового окна, например MessageBox(), сразу после выпуска IProgressDialog, мы завершаем двумя модальными диалогами с тем же окном владельца: диалог прогресса отклоняется и снова включает главное окно, в то время как окно сообщения все еще работает.

Немодальный корпус был решен другими by merely hiding the progress dialog after calling StopProgressDialog(). Хотя это скрывает окно, оно ничего не делает о модальности. Основываясь на гипотезе респондента вопроса в конце связанного вопроса, я также пробовал отправлять и отправлять WM_NULL с после StopProgressDialog(). Это тоже не сработало.

Последнее, я попытался установить крюк WinEvents, ожидая, когда окно будет уничтожено и будет запущено объект события, когда он есть. Это работает; MessageBox() не произойдет, пока диалог прогресса не будет уничтожен. Однако это не работает отлично: главное окно не активируется снова сразу, а MessageBox() даже не отображается в фоновом режиме, пока я не щелкнул значок панели задач главного окна.

(Даже тогда, потому что я не могу передать LPARAM моему крюку WinEvents, если бы я хотел обрабатывать несколько IProgressDialogs для разных потоков, мне нужно было бы иметь глобальный список оконных ручек для наблюдения (и связанного с ними события объекты), которые синхронизированы, но, к счастью, я не нуждаюсь в этом для своих целей. Кроме того, все вышеперечисленное относится к предположению, что CLSID_ProgressDialog также является IOleWindow, если это когда-либо изменяется, то ...)

Am Я делаю что-то не так с этим способом WinEvents? Я подозреваю, что неправильно звонил MsgWaitForMultipleObjectsEx(), но если выяснится, что IProgressDialog не реализует модальность должным образом, то я думаю, мне не повезло: S

Пример программы ниже делает все вышеперечисленное. Я тестировал его с помощью Visual Studio 2013 на 64-разрядной версии Windows 7 (в качестве 64-разрядной версии).

// 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

ключевая фраза из документации: «Объект затем обрабатывает обновления ** на фоне потока ** и позволяет пользователю отменить операцию» , Вы не создали нить. Поток пользовательского интерфейса должен оставаться доступным для отображения прогресса, он не может этого сделать, когда он занят выполнением кода. –

+0

Ah; Я подумал, когда он сказал «Объект», он ссылался на 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); 

 Смежные вопросы

  • Нет связанных вопросов^_^