简体   繁体   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?

I'm trying to use IProgressDialog in PROGDLG_MODAL mode and I'm hitting two snags.我正在尝试在 PROGDLG_MODAL 模式下使用 IProgressDialog,但遇到了两个障碍。

First, from what I gather from MSDN and the block comment in the <shlobj.h> header file describing IProgressDialog, you are supposed to use it directly from the thread you are doing work and that the IProgressDialog will do its UI stuff from another thread;首先,根据我从 MSDN 收集到的内容以及描述 IProgressDialog 的<shlobj.h>头文件中的块注释,您应该直接从您正在工作的线程中使用它,并且 IProgressDialog 将从另一个线程执行其 UI 内容; that is,那是,

while (there_is_still_work_to_do()) {
    if (pd->HasUserCancelled())
        break;
    do_more_work();
    completed++;
    pd->SetProgress64(completed, total);
}

is sufficient to get a responsive progress dialog up and running.足以启动并运行响应式进度对话框。

However, in practice, I still need to pump messages in do_more_work() ;但是,在实践中,我仍然需要在do_more_work()抽取消息; if I don't, the progress dialog won't show up until after the loop ends!如果我不这样做,直到循环结束后才会显示进度对话框! So am I misunderstanding MSDN when it says "The object then handles updating on a background thread"?那么当 MSDN 说“该对象然后在后台线程上处理更新”时,我是否误解了它? Okay so I do need to pump messages, but now I'm still confused by what MSDN says insofar as: which methods of IProgressDialog can I run from my worker thread?好的,所以我确实需要发送消息,但是现在我仍然对 MSDN 所说的内容感到困惑:我可以从我的工作线程运行 IProgressDialog 的哪些方法? Knowing this, I can restructure my code properly.知道了这一点,我可以正确地重组我的代码。

A more dire issue, however, is that StopProgressDialog() doesn't tear down the window immediately.然而,一个更可怕的问题是StopProgressDialog()不会立即拆除窗口。 In fact, neither does Release() !事实上,也没有Release() Under non-modal situations, it'll appear that the progress dialog lingers on screen for some more time.在非模态情况下,进度对话框会在屏幕上停留一段时间。 This particular issue has been addressed before;这个特殊问题之前已经解决过; I'll get to that.我会做到这一点。 This time, however, I'm using the progress dialog as a UI-modal dialog.但是,这一次,我将进度对话框用作 UI 模式对话框。 If I happen to call another dialog box function, such as MessageBox() , immediately after releasing the IProgressDialog, then we wind up with two modal dialogs with the same owner window: the progress dialog dismisses itself and re-enables the main window while the message box is still running.如果我碰巧在释放 IProgressDialog 后立即调用另一个对话框函数,例如MessageBox() ,那么我们最终会得到两个具有相同所有者窗口的模式对话框:进度对话框自行关闭并重新启用主窗口,而消息框仍在运行。

The non-modal case was solved by others by merely hiding the progress dialog after calling StopProgressDialog() .其他人通过在调用StopProgressDialog()后仅隐藏进度对话框来解决非模态情况。 While this does hide the window, it doesn't do anything about modality.虽然这确实隐藏了窗口,但它对模态没有任何作用。 Based on the question asker's hypothesis at the end of the linked question, I also tried posting and sending WM_NULL s after StopProgressDialog() .根据链接问题末尾的提问者的假设,我还尝试在StopProgressDialog()之后发布和发送WM_NULL s。 That also didn't work.那也没有用。

Last, I tried to set up a WinEvents hook, waiting for the window to be destroyed and firing an event object when it is.最后,我尝试设置一个 WinEvents 钩子,等待窗口被销毁并在它被销毁时触发一个事件对象。 This does work;这确实有效; the MessageBox() will not happen until the progress dialog is destroyed. MessageBox()在进度对话框被销毁之前不会发生。 However, this does not work perfectly: the main window doesn't become active again immediately, and the MessageBox() doesn't even show up in the background until I click the main window's taskbar icon.但是,这并不完美:主窗口不会立即再次变为活动状态,并且MessageBox()在我单击主窗口的任务栏图标之前甚至不会出现在后台。

(Even then, because I can't pass an LPARAM to my WinEvents hook, if I wanted to handle multiple IProgressDialogs across different threads I would need to have a global list of window handles to watch for (and their associated event objects) which is synchronized. Fortunately I don't need this for my purposes. And furthermore, all of the above goes under the assumption that CLSID_ProgressDialog is also an IOleWindow; if that ever changes, then...) (即使那样,因为我无法将 LPARAM 传递给我的 WinEvents 钩子,如果我想跨不同线程处理多个 IProgressDialogs,我需要有一个全局的窗口句柄列表来监视(及其关联的事件对象)这是同步。幸运的是,我不需要这个来达到我的目的。此外,以上所有内容都假设 CLSID_ProgressDialog 也是一个 IOleWindow;如果发生变化,那么......)

Am I doing something wrong with this WinEvents way?我用这种 WinEvents 方式做错了吗? I suspect I'm not calling MsgWaitForMultipleObjectsEx() correctly, but if it turns out that IProgressDialog doesn't implement modality properly, then I guess I'm out of luck :S我怀疑我没有正确调用MsgWaitForMultipleObjectsEx() ,但如果结果证明 IProgressDialog 没有正确实现模式,那么我想我运气不好:S

The sample program below does all of the above.下面的示例程序完成了上述所有工作。 I've tested it with Visual Studio 2013 on Windows 7 64-bit (building as 64-bit).我已经在 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 Huh, upon further examination it appears that when I stop the progress dialog without showing the messagebox, the owner window does NOT get focus back either!更新嗯,经过进一步检查,似乎当我停止进度对话框而不显示消息框时,所有者窗口也不会重新获得焦点! I'm guessing the progress dialog simply does not handle modality correctly;我猜进度对话框根本无法正确处理模态; if I remove the PROGDLG_MODAL flag everything works fine.如果我删除PROGDLG_MODAL标志一切正常。 Oh well :/ I'm going to have to either fake the modality or switch to something else.哦,好吧:/我将不得不伪造模态或切换到其他东西。

I might just wind up showing my message box over the progress dialog itself and hope that a future version of Windows doesn't take away IOleWindow.我可能会在进度对话框本身上显示我的消息框,并希望未来版本的 Windows 不会带走 IOleWindow。 Unless there is a better way?除非有更好的方法? Or unless doing modality manually and keeping the progress dialog modeless after the call to StopProgressDialog() is good enough (an error will only be reported after StopProgressDialog() to begin with) but then again...或者除非手动执行模态并在调用StopProgressDialog()后保持进度对话框无模式已经足够好(错误只会在StopProgressDialog()开始后报告)但又...

I am not sure if this is a "clean" solution, but since you have a window handle to that progress dialog, why don't you just kill it before calling MessageBox() ?我不确定这是否是一个“干净”的解决方案,但是既然您有该进度对话框的窗口句柄,为什么不在调用MessageBox()之前将其杀死?

This worked for me (in your test app):这对我有用(在您的测试应用程序中):

::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);

For whatever reason, the key to get smooth animation from the start to the very and end of the operation is to "pump timer messages" (I believe that this is what happens).无论出于何种原因,从操作的开始到最后和结束获得流畅动画的关键是“泵送计时器消息”(我相信这就是发生的情况)。

In Delphi, it can be done like this, as part of the IsCancelled method:在 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;

I observerd no messages being ever retrieved by PeekMessage, but nevertheless, it makes the progress dialog to update much more consistent and to display the text lines and the marquee mode properly (which was very erratic/random before this measure).我观察到 PeekMessage 没有检索到任何消息,但尽管如此,它使进度对话框更新更加一致并正确显示文本行和选取框模式(在此措施之前非常不稳定/随机)。

Partial answer部分回答

[cf. [参见UPDATE ] I use a child as the dialog parent, rather than the actual parent and the modality works well with this setting.更新] 我使用一个孩子作为对话的父级,而不是实际的父级,并且该模式在此设置下运行良好。

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

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