如何将 WM_KEYDOWN 消息传递给 IWebBrowser2 实例?
How to pass WM_KEYDOWN message to IWebBrowser2 instance?
我正在使用 IWebBrowser2
界面在父应用程序中加载嵌入式浏览器。我的代码被编译为 dll,即浏览器组件在运行时通过插件接口动态加载。
我遇到的问题是加载我的 dll 的应用程序捕获了某些按键消息,因此它们没有到达我的 IWebBrowser2
实例。
因此,我在我的 dll 中使用 SetWindowsHookEx()
API 捕获这些消息。
然后我如何将 WM_KEYDOWN
或 WM_CHAR
消息转发到我的 IWebBrowser2
实例,以便它们可以,例如用于在浏览器的聚焦文本框中输入文本?
看来,这比平时发消息要麻烦一点:
首先,您需要在其上获得In place活动对象(https://msdn.microsoft.com/en-us/library/windows/desktop/ms691299(v=vs.85).aspx) of your web browser and then call the 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:没有尝试此代码,使用风险自负:)
听起来根本问题在于您的 window 与宿主应用程序的 window 位于不同的线程上,这可能会混淆焦点状态。您很容易陷入主持人 window 和主持人 window 都认为自己有焦点的情况。
解决方案是在与父 window 相同的线程上创建您的 window,并且,如果这不可能(例如,由于插件模型或插件是运行 在单独的进程中),使用 AttachThreadInput.
我已经很多年没有使用 Web 浏览器控件了,但我记得很久以前的一个项目,当我们将 Web 浏览器控件添加为另一个 window 的子项时,我们遇到了类似的问题过程。使用 AttachThreadInput 解决了很多错误。缺点是任何一个线程中的错误(如挂起)都会有效地挂起两个线程。在拆卸过程中,我们还必须小心分离线程。
我认为问题出在主机应用程序的消息队列实现中,其中处理了一些消息而不是传递,例如实现热键。由于您无法更改他们的代码,挂钩消息队列听起来是一种合理的方法。
以下代码片段展示了问题和解决方案:
#define WINDOW_CLASS _T("Whosebug_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,因为这些键用于加速器 table。但是,对于 InstallHook()
,挂钩会强制执行正常的消息队列行为,并且一切正常。
建议的钩子代码有以下兴趣点:
- 它只会影响您的 window,不会影响其他
- 它的工作方式与通常的消息队列相同,而不是搞乱
SendMessage
/ PostMessage
- 它可以防止 double-effect 条未被托管应用程序拦截的消息
我正在使用 IWebBrowser2
界面在父应用程序中加载嵌入式浏览器。我的代码被编译为 dll,即浏览器组件在运行时通过插件接口动态加载。
我遇到的问题是加载我的 dll 的应用程序捕获了某些按键消息,因此它们没有到达我的 IWebBrowser2
实例。
因此,我在我的 dll 中使用 SetWindowsHookEx()
API 捕获这些消息。
然后我如何将 WM_KEYDOWN
或 WM_CHAR
消息转发到我的 IWebBrowser2
实例,以便它们可以,例如用于在浏览器的聚焦文本框中输入文本?
看来,这比平时发消息要麻烦一点:
首先,您需要在其上获得In place活动对象(https://msdn.microsoft.com/en-us/library/windows/desktop/ms691299(v=vs.85).aspx) of your web browser and then call the 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:没有尝试此代码,使用风险自负:)
听起来根本问题在于您的 window 与宿主应用程序的 window 位于不同的线程上,这可能会混淆焦点状态。您很容易陷入主持人 window 和主持人 window 都认为自己有焦点的情况。
解决方案是在与父 window 相同的线程上创建您的 window,并且,如果这不可能(例如,由于插件模型或插件是运行 在单独的进程中),使用 AttachThreadInput.
我已经很多年没有使用 Web 浏览器控件了,但我记得很久以前的一个项目,当我们将 Web 浏览器控件添加为另一个 window 的子项时,我们遇到了类似的问题过程。使用 AttachThreadInput 解决了很多错误。缺点是任何一个线程中的错误(如挂起)都会有效地挂起两个线程。在拆卸过程中,我们还必须小心分离线程。
我认为问题出在主机应用程序的消息队列实现中,其中处理了一些消息而不是传递,例如实现热键。由于您无法更改他们的代码,挂钩消息队列听起来是一种合理的方法。
以下代码片段展示了问题和解决方案:
#define WINDOW_CLASS _T("Whosebug_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,因为这些键用于加速器 table。但是,对于 InstallHook()
,挂钩会强制执行正常的消息队列行为,并且一切正常。
建议的钩子代码有以下兴趣点:
- 它只会影响您的 window,不会影响其他
- 它的工作方式与通常的消息队列相同,而不是搞乱
SendMessage
/PostMessage
- 它可以防止 double-effect 条未被托管应用程序拦截的消息