[英]How to pass WM_KEYDOWN message to IWebBrowser2 instance?
我正在使用IWebBrowser2
接口在父應用程序中加載嵌入式瀏覽器。 我的代碼被編譯為 dll,即瀏覽器組件在運行時通過插件接口動態加載。
我遇到的問題是加載我的 dll 的應用程序正在捕獲某些 keydown 消息,因此它們沒有到達我的IWebBrowser2
實例。
因此,我在我的 dll 中使用SetWindowsHookEx()
API 捕獲這些消息。
然后我如何將WM_KEYDOWN
或WM_CHAR
消息轉發到我的IWebBrowser2
實例,以便它們可以例如用於在瀏覽器的焦點文本框中輸入文本?
聽起來根本問題是您的窗口與主機應用程序的窗口位於不同的線程上,這可能會混淆焦點狀態。 您可以輕松進入宿主窗口和托管窗口都認為它們具有焦點的情況。
解決方案是在與父窗口相同的線程上創建窗口,如果這不可能(例如,因為插件模型或因為插件在單獨的進程中運行),請使用AttachThreadInput 。
我已經很多年沒有使用 Web 瀏覽器控件了,但我記得很久以前的一個項目,當我們在另一個進程中將 Web 瀏覽器控件添加為窗口的子項時,我們遇到了類似的問題。 使用 AttachThreadInput 解決了很多錯誤。 缺點是任一線程中的錯誤(如掛起)有效地掛起兩個線程。 在拆卸過程中,我們還必須小心分離線程。
我相信問題出在主機應用程序的消息隊列實現中,其中一些消息被處理而不是傳遞,例如實現熱鍵。 由於您無法更改他們的代碼,因此掛鈎消息隊列聽起來是一種合理的方法。
以下代碼片段演示了問題和解決方案:
#define WINDOW_CLASS _T("StackOverflow_41911104")
HINSTANCE g_Instance = 0;
HHOOK g_Hook = 0;
HWND g_TargetWindow = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
HWND CreateMainWindow()
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = g_Instance;
wcex.hIcon = nullptr;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = WINDOW_CLASS;
wcex.hIconSm = nullptr;
ATOM windowClass = RegisterClassExW(&wcex);
HWND mainWindow = CreateWindowW(WINDOW_CLASS, WINDOW_CLASS, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 400, nullptr, nullptr, g_Instance, nullptr);
g_TargetWindow = CreateWindow(_T("Edit"), nullptr, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE, 0, 0, 300, 300, mainWindow, (HMENU)1000, g_Instance, nullptr);
return mainWindow;
}
HACCEL CreateAccelerators()
{
ACCEL acceleratorsList[] =
{
{FVIRTKEY, 'R', 1000},
{FVIRTKEY, 'T', 1001},
};
return CreateAcceleratorTable(acceleratorsList, _countof(acceleratorsList));
}
void ProcessHookMessage(MSG* a_Message)
{
// Only affect our window and its children
if ((g_TargetWindow != a_Message->hwnd) && !IsChild(g_TargetWindow, a_Message->hwnd))
return;
// Deliver the message directly
TranslateMessage(a_Message);
DispatchMessage(a_Message);
// Do not allow to process this message the second time
a_Message->message = WM_NULL;
}
LRESULT CALLBACK Hook_GetMsgProc(int a_Code, WPARAM a_WParam, LPARAM a_LParam)
{
if ((HC_ACTION == a_Code) && (PM_REMOVE == a_WParam))
ProcessHookMessage((MSG*)a_LParam);
return CallNextHookEx(g_Hook, a_Code, a_WParam, a_LParam);
}
void InstallHook()
{
g_Hook = SetWindowsHookEx(WH_GETMESSAGE, Hook_GetMsgProc, g_Instance, GetCurrentThreadId());
}
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
{
g_Instance = hInstance;
HWND mainWindow = CreateMainWindow();
HACCEL hAccelTable = CreateAccelerators();
InstallHook();
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
// The problem lurks here: some messages are handled directly and never reach the target window
if (TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
在此代碼片段中,如果您注釋掉InstallHook()
調用,您將無法打印R和T ,因為這些鍵用於加速器表。 但是,使用InstallHook()
,鈎子會強制執行正常的消息隊列行為,並且一切正常。
建議的鈎子代碼有以下興趣點:
SendMessage
/ PostMessage
混淆看起來,這比通常發送消息要棘手一些:
首先,您需要獲取 Web 瀏覽器的就地活動對象 ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms691299(v=vs.85).aspx ),然后在其上調用TranslateAccelerator
( https://msdn.microsoft.com/en-us/library/windows/desktop/ms693360(v=vs.85).aspx )。
一些非常高級的偽代碼如下所示:
HRESULT hr;
IOleInPlaceActiveObject* pIOIPAO;
hr = webBrowser2->QueryInterface(webBrowser2,
&IID_IOleInPlaceActiveObject, (LPVOID*)&pIOIPAO);
if (SUCCEEDED(hr))
{
result = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, msg);
}
其中msg
是您應該相應填充的消息( MSG
),而webBrowser2
是您的IWebBrowser2
。
PS:沒試過這個代碼,使用風險自負:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.