[英]Windows preview handlers don't respect drawing area set with SetRect
我一直在嘗試在我的應用程序中使用 Windows 預覽處理程序,並注意到一些預覽處理程序不遵守使用SetRect(&rect)
設置的繪圖矩形。
例如,對於 Edge PDF 預覽處理程序 (CLSID {3A84F9C2-6164-485C-A7D9-4B27F8AC009E}
),調用SetRect
僅在第一次調用時正常工作。 在連續的調用中,只有矩形尺寸被更新,而不是 position:
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}
) 的行為類似,只是它根本不遵守 position,它始終位於 (0,0)。
Word 預覽處理程序 (CLSID {84F66100-FF7C-4fb4-B0C0-02CD7FB668FE}
) 在第一次獲得焦點時自動將繪圖矩形擴展到父 Window 的整個客戶區域(例如,當您單擊“Word”區域時,加載預覽后)。 以后的SetRect
調用行為正常。
根據 MSDN, SetRect
指示預覽處理程序更改其繪制到的父 hwnd 中的區域
和
...僅呈現在此方法的 prc 所描述的區域中...
我已經檢查了文件資源管理器的作用。 它似乎以預期的大小創建了一個子 window 和 position,然后將預覽放在該 window 中。在這種情況下,上面列出的問題是無關緊要的,因為預覽填充了整個 window。我現在的問題是預覽處理程序是否只是行為不當,或者我遺漏了什么。
以下是演示該問題的示例應用程序,它需要一個文件路徑作為第一個參數,創建一個新的 window 並預覽該文件。 它首先將繪圖矩形在 position (0,0) 處設置為 500x500,然后在 (100,100) 處設置為 600x600。 大多數錯誤處理被省略。
用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);
}
大多數開發人員可能只在資源管理器中進行測試,如果資源管理器使用子 window 來托管處理程序,則預覽處理程序中不會注意到這些錯誤 go。
您最好的選擇是對您自己的應用程序執行相同的操作。
眾所周知,托管 shell 擴展是一項艱巨的任務,一些擴展甚至會搞砸QueryInterface
,因此Explorer 必須同時檢查 HRESULT 和指針!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.