繁体   English   中英

IProgressDialog 问题:我是否误解了它的多线程,因为我似乎需要一个消息泵? 我可以避免在结束之前打开另一个对话框吗?

[英]IProgressDialog questions: Do I misunderstand its multithreading, as I seem to need a message pump? Can I avoid opening another dialog before it ends?

我正在尝试在 PROGDLG_MODAL 模式下使用 IProgressDialog,但遇到了两个障碍。

首先,根据我从 MSDN 收集到的内容以及描述 IProgressDialog 的<shlobj.h>头文件中的块注释,您应该直接从您正在工作的线程中使用它,并且 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 模式对话框。 如果我碰巧在释放 IProgressDialog 后立即调用另一个对话框函数,例如MessageBox() ,那么我们最终会得到两个具有相同所有者窗口的模式对话框:进度对话框自行关闭并重新启用主窗口,而消息框仍在运行。

其他人通过在调用StopProgressDialog()后仅隐藏进度对话框来解决非模态情况。 虽然这确实隐藏了窗口,但它对模态没有任何作用。 根据链接问题末尾的提问者的假设,我还尝试在StopProgressDialog()之后发布和发送WM_NULL s。 那也没有用。

最后,我尝试设置一个 WinEvents 钩子,等待窗口被销毁并在它被销毁时触发一个事件对象。 这确实有效; MessageBox()在进度对话框被销毁之前不会发生。 但是,这并不完美:主窗口不会立即再次变为活动状态,并且MessageBox()在我单击主窗口的任务栏图标之前甚至不会出现在后台。

(即使那样,因为我无法将 LPARAM 传递给我的 WinEvents 钩子,如果我想跨不同线程处理多个 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标志一切正常。 哦,好吧:/我将不得不伪造模态或切换到其他东西。

我可能会在进度对话框本身上显示我的消息框,并希望未来版本的 Windows 不会带走 IOleWindow。 除非有更好的方法? 或者除非手动执行模态并在调用StopProgressDialog()后保持进度对话框无模式已经足够好(错误只会在StopProgressDialog()开始后报告)但又...

我不确定这是否是一个“干净”的解决方案,但是既然您有该进度对话框的窗口句柄,为什么不在调用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);

无论出于何种原因,从操作的开始到最后和结束获得流畅动画的关键是“泵送计时器消息”(我相信这就是发生的情况)。

在 Delphi 中,它可以像这样完成,作为 IsCancelled 方法的一部分:

function TProgressDialog.IsCancelled: boolean;
begin
  self.PumpTimerMessages;
  Result := FDlgIntf.HasUserCancelled;
end;


procedure TProgressDialog.PumpTimerMessages;
var
  Msg: TMsg;
  i: integer;
begin
  for i := 1 to 32 do begin
    while Windows.PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
      Windows.TranslateMessage(Msg);
      Windows.DispatchMessage(Msg);
    end;
  end;
end;

我观察到 PeekMessage 没有检索到任何消息,但尽管如此,它使进度对话框更新更加一致并正确显示文本行和选取框模式(在此措施之前非常不稳定/随机)。

部分回答

[参见更新] 我使用一个孩子作为对话的父级,而不是实际的父级,并且该模式在此设置下运行良好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM