Обработчики предварительного просмотра Windows не учитывают область рисования, установленную с помощью SetRect

Я пытался использовать обработчики предварительного просмотра Windows в своем приложении и заметил, что некоторые из обработчиков предварительного просмотра не учитывают прямоугольник рисования, установленный с помощьюSetRect(&rect).

Например, с обработчиком предварительного просмотра Edge PDF (CLSID{3A84F9C2-6164-485C-A7D9-4B27F8AC009E}), вызов работает правильно только при первом вызове. При последовательных вызовах обновляются только размеры прямоугольника, но не позиция:

      RECT rect{0,0,500,500}; // 500x500 rectangle at position (0,0)
preview_handler->SetWindow(hwnd, &rect); // Sets parent window and initial drawing rectangle correctly
rect = {100, 100, 700, 700}; // 600x600 rect at position (100,100)
preview_handler->SetRect(&rect); // Updates rectangle size to 600x600, but stays at position (0,0)

Обработчик предварительного просмотра Powerpoint (CLSID{65235197-874B-4A07-BDC5-E65EA825B718}) ведет себя аналогично, за исключением того, что он вообще не учитывает положение, он всегда будет в (0,0).

Обработчик предварительного просмотра Word (CLSID{84F66100-FF7C-4fb4-B0C0-02CD7FB668FE}) автоматически расширяет прямоугольник рисования на всю клиентскую область родительского окна при первом получении фокуса (например, когда вы нажимаете на область «Word» после загрузки предварительного просмотра). Более поздние вызовы ведут себя правильно.

Согласно MSDN,SetRect

Указывает обработчику предварительного просмотра изменить область в родительском hwnd, в которую он рисует.

и

...отрисовывается только в области, описанной параметром этого метода...

Я проверил, что делает проводник. Кажется, что он создает дочернее окно с предполагаемым размером и положением, а затем помещает предварительный просмотр в это окно. В этом случае перечисленные выше проблемы не имеют значения, поскольку предварительный просмотр заполняет все окно. Теперь мой вопрос заключается в том, что обработчики предварительного просмотра просто не работают должным образом или я что-то упускаю.

Ниже приведен пример приложения для демонстрации проблемы, он ожидает путь к файлу в качестве первого аргумента, создает новое окно и выполняет предварительный просмотр файла. Сначала он устанавливает размер прямоугольника рисования 500x500 в позиции (0,0), затем 600x600 в позиции (100,100). Большая часть обработки ошибок опущена.

Компилировать сcl /std:c++20 /EHsc main.cpp

      #include <Windows.h>
#include <shlwapi.h>
#include <objbase.h>
#include <Shobjidl.h>
#include <thread>
#include <string>
#include <array>
#include <filesystem>
#include <iostream>

#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Ole32.lib")

void WindowThreadProc(bool& ready, HWND& hwnd) {
    SetProcessDPIAware();
    HINSTANCE hinst = GetModuleHandleW(nullptr);

    auto wnd_proc = [](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ->LRESULT {
        switch (message) {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        }
        return DefWindowProc(hWnd, message, wParam, lParam);
    };

    WNDCLASS wc{};
    wc.lpfnWndProc = wnd_proc;
    wc.hInstance = hinst;
    wc.lpszClassName = "Test Window";

    RegisterClass(&wc);

    HWND handle = CreateWindowExW(
        0, L"Test Window", L"Test Window", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr, nullptr, hinst, nullptr);

    ShowWindow(handle, true);

    hwnd = handle;
    ready = true;

    MSG msg{};
    while (GetMessage(&msg, nullptr, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
}

//return clsid of preview handler for the passed extension, throw if no suitable preview handler is installed
CLSID getShellExClsidForType(
    const std::wstring& extension,
    const GUID& interfaceClsid) {
    std::array<wchar_t, 39> interfaceClsidWstr;
    StringFromGUID2(
        interfaceClsid,
        interfaceClsidWstr.data(),
        static_cast<DWORD>(interfaceClsidWstr.size()));

    std::array<wchar_t, 39> extensionClsidWstr;
    DWORD extensionClsidWstrSize = static_cast<DWORD>(extensionClsidWstr.size());
    HRESULT res;
    res = AssocQueryStringW(
        ASSOCF_INIT_DEFAULTTOSTAR,
        ASSOCSTR_SHELLEXTENSION,
        extension.c_str(),
        interfaceClsidWstr.data(),
        extensionClsidWstr.data(),
        &extensionClsidWstrSize);
    if (res != S_OK) {
        throw "no preview handler found";
    };

    CLSID extensionClsid;
    IIDFromString(extensionClsidWstr.data(), &extensionClsid);

    std::wcout << L"Extension: " << extension << L" - Preview Handler CLSID: " << extensionClsidWstr.data() << std::endl;

    return(extensionClsid);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        return 0;
    }

    //initialize as STA
    CoInitialize(nullptr);

    bool ready = false;
    HWND hwnd;
    //create and run message pump in different thread
    std::thread window_thread(WindowThreadProc, std::ref(ready), std::ref(hwnd));

    //wait for window to be ready
    while (!ready) {}

    //create preview handler, use first argument as path
    std::filesystem::path path(argv[1]);
    CLSID clsid = getShellExClsidForType(path.extension(), __uuidof(IPreviewHandler));
    IPreviewHandler *preview_handler;
    CoCreateInstance(
        clsid, 
        nullptr, 
        CLSCTX_LOCAL_SERVER, 
        __uuidof(IPreviewHandler), 
        reinterpret_cast<void**>(&preview_handler));

    IInitializeWithStream* init_with_stream;
    HRESULT res;
    //initialize previewhandler with stream or with file path
    IInitializeWithFile* init_with_file;
    res = preview_handler->QueryInterface(&init_with_file);
    if (res == S_OK) {
        init_with_file->Initialize(path.c_str(), STGM_READ);
        init_with_file->Release();
    }
    else {
        IInitializeWithStream* init_with_stream;
        res = preview_handler->QueryInterface(&init_with_stream);
        if (res == S_OK) {
            IStream* stream;
            SHCreateStreamOnFileEx(
                path.c_str(),
                STGM_READ | STGM_SHARE_DENY_WRITE,
                0, false, nullptr,
                &stream);
            init_with_stream->Initialize(stream, STGM_READ);
            stream->Release();
            init_with_stream->Release();
        }
        else {
            throw "neither InitializeWithFile nor InitializeWithStream supported";
        }
    }

    auto print_rect = [](RECT& rect) {
        std::wcout << L"Setting Rect to: (" << rect.left << L", " << rect.top << ", " << rect.right << ", " << rect.bottom << ")" << std::endl;
    };

    //initial rect
    RECT rect{ 0,0,500,500 };
    print_rect(rect);
    preview_handler->SetWindow(hwnd, &rect);
    preview_handler->DoPreview();
    preview_handler->SetRect(&rect);

    //new rect
    rect = { 200, 200, 800, 800 };
    print_rect(rect);
    preview_handler->SetRect(&rect);


    window_thread.join();

    preview_handler->Release();
    
    return(0);
}

1 ответ

Большинство разработчиков, вероятно, тестируют только в проводнике, и если проводник использует дочернее окно для размещения обработчика, эти ошибки остаются незамеченными в обработчиках предварительного просмотра.

Лучше всего сделать то же самое со своим приложением.

Хостинг расширений оболочки, как известно, является сложной задачей, некоторые расширения даже умудряются испортитьQueryInterfaceпоэтому Explorer должен проверять как HRESULT, так и указатель !

Другие вопросы по тегам