当前位置 博文首页 > u012179390的博客:CEF使用元素事件移动无边框窗口
拖动嵌入了cef3的无标题栏win32窗口,本质上就是拖动子窗口移动父窗口这么个事情,这个在自己创建窗口的程序上处理非常简单,cef封装较多,想修改的完善点步骤较多。
cef所用版本3.2623。
就用cefclient来做测试工程,首先建议在cefclient_win.cc的RunMain函数中的app = new ClientAppRenderer();前面添加一个messagebox,打印上出进程id,如果使用命令行–renderer-startup-dialog --no-sandbox 弹出messagebox的时候,调试上去,有些类的构造过程已经过去了。
用spy++查看cefclient的窗口,可以看到最上层的是标题为Chrome Legacy Window子窗口,找到其对应的代码在CEF\source\chromium\src\content\browser\renderer_host\legacy_render_widget_host_win.h中,在类LegacyRenderWidgetHostHWND的响应函数OnMouseRange中,如果添加以下代码
if(message == WM_LBUTTONDOWN)
{
::PostMessage(HwndRootWindow,WM_NCLBUTTONDOWN,HTCAPTION,0);
}
return 0;
编译后运行,就能发现cef打开的网页,整个范围内都是可以拖动整个宿主窗体的了。如果需要显示的网页有部分比较固定不变,那么指定上点坐标范围,也能勉强凑合。不过通常情况下,显然是不够使用的,也非常不灵活。
最好的办法是,提供js接口,让前端小伙伴们自己决定哪些地方可以拖动宿主窗口。
前端来做拖拽的话,常用方法就是使用draggable属性和ondragstart回调函数了,使用此方法还有一个好处,就是浏览器已经对拖拽事件进行了识别,前端和c++端都不用再考虑这个问题以及拖拽范围的问题。先贴上测试用的html代码。
<html>
<head>
<script language="JavaScript">
function MulCall() {
alert('123456');
}
function drag(ev) {
ev.preventDefault();
ev.MoveRootWinow();
}
</script>
</head>
<body>
<input type="text" id="value_1" value="请输入" />
<br />
<br />
<input type="button" value="拖动窗口" οnclick="MulCall();" draggable="true" οndragstart="drag(event)"/>
</body>
</html>
这里的js函数MoveRootWindow就是我们需要提供的函数了。这个js函数不建议采用常用的提供js接口函数的方式,而是添加到chromium\src\third_party\WebKit\Source\core\events\Event.idl这个接口定义文件中,添加位置可以在 void stopPropagation();和
void stopImmediatePropagation();两个函数后面,添加内容如下:
void MoveRootWinow();
编译时候,它会自动在你的chromium的输出目录\gen\blink\bindings\core\v8\V8Event.cpp中生成如下代码
static void MoveRootWinowMethod(const v8::FunctionCallbackInfo<v8::Value>& info)
{
Event* impl = V8Event::toImpl(info.Holder());
impl->MoveRootWinow();
}
static void MoveRootWinowMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info)
{
TRACE_EVENT_SET_SAMPLING_STATE("blink", "DOMMethod");
EventV8Internal::MoveRootWinowMethod(info);
TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8Execution");
}
并且会自动在数组V8EventMethods中添加如下映射
{"MoveRootWinow", EventV8Internal::MoveRootWinowMethodCallback, 0, 0, v8::None, V8DOMConfiguration::ExposedToAllScripts, V8DOMConfiguration::OnPrototype}
如此,js函数 ev.MoveRootWinow();最终就调用c++类Event的MoveRootWindow()接口,给chromium\src\third_party\WebKit\Source\core\events\Event.h的Event类提供这个接口
bool bMoveRootWindow() const { return m_MoveRootWinow; }
virtual void MoveRootWinow();
void setMoveRootWindow(bool bMove) { m_MoveRootWinow = bMove; }
并且添加如下成员变量,表示js希望移动宿主窗口,初始化为false
unsigned m_MoveRootWinow:1;
实现上面的虚函数
void Event::MoveRootWinow()
{
m_MoveRootWinow = true;
}
至此,js通知cef移动窗口的消息可以传递进来了。
接下来,需要关心的是chromium对拖拽事件的处理了。chromium多进程下js的拖拽事件是在render进程处理的,我们上面的处理,也是在render进程的,那么下面只需要将这个消息发送到主进程。
隔了几十层堆栈,发现chromium\src\third_party\WebKit\Source\core\input\EventHandler.h定义的类EventHandler可以方便的接收Event类的数据并且能够发送进程消息。
首先,在EventHandler类中定义一个bool成员m_bMoveRootWindow,然后在dispatchDragEvent()接口的return前面添加如下代码,获取上面Event中的值
m_bMoveRootWindow = me.get()->bMoveRootWindow();
接下来关注的是接口tryStartDrag(),大家看名字就知道这个接口是干什么的,我提一下的是,这里会调用html文件中ondragstart事件对应的drag(ev)回调函数,在回调函数中我调用了preventDefault()函数,阻止了chromium对拖拽事件的默认处理,这个很关键,必须阻止,这个js函数对应c++函数其实在上面已经提到,可以直接在我们的c++函数中调用,这样js中就不需要调用了。
发送进程间消息,RenderViewImpl类可以做到,在tryStartDrag()中,DragController类的变量,是能够将移动窗口的信号,传递给RenderViewImpl类的。依次给DragController.h的类DragController,DragClientImpl.h的类DragClientImpl,WebViewImpl.h的类WebViewImpl,RenderViewImpl.h的类RenderViewImpl这四个类添加startMoveRootWindow()接口,注意有的需要到其纯虚基类添加接口,其实现分别如下
void DragController::StartMoveRootWindow()
{
m_client->startMoveRootWindow();
}
void DragClientImpl::startMoveRootWindow()
{
m_webView->StartMoveRootWindow();
}
void WebViewImpl::StartMoveRootWindow()
{
m_client->startMoveRootWindow();
}
void RenderViewImpl::startMoveRootWindow()
{
Send(new DragHostMsg_MoveRootWindow(routing_id()));
}
其中DragHostMsg_MoveRootWindow是我们自定义的进程间消息,通知主进程移动窗口事件的发生。定义其在drag_messages.h文件中,添加如下代码
IPC_MESSAGE_ROUTED0(DragHostMsg_MoveRootWindow)
接着返回到tryStartDrag(),在return之前添加如下代码,调用接口发送如上消息
if (m_bMoveRootWindow)
{
dragController.StartMoveRootWindow();
}
接下来我们就要在主进程接收这个消息了
在render_view_host_impl.h中,添加消息响应函数 void OnStartMoveRootWindow(),在.cc中添加消息映射
IPC_MESSAGE_HANDLER(DragHostMsg_MoveRootWindow, OnStartMoveRootWindow)
在OnStartMoveRootWindow()中接收到render进程的消息后,需要做的就是通知最上层的子窗口类LegacyRenderWidgetHostHWND了。
新建render_view_move_root_window.h,添加纯虚基类如下
#ifndef RENDER_VIEW_MOVE_ROOT_WINDOW
#define RENDER_VIEW_MOVE_ROOT_WINDOW
namespace content
{
class RenderViewMoveRootWindow
{
public:
RenderViewMoveRootWindow() {};
~RenderViewMoveRootWindow() {};
virtual void SetMoveWindow(bool bMove) = 0;
private:
};
}
#endif // RENDER_VIEW_MOVE_ROOT_WINDOW
同时在content_browser.gypi中加入这个文件。
给类LegacyRenderWidgetHostHWND添加基类RenderViewMoveRootWindow。
给类RenderViewHostImpl添加成员变量RenderViewMoveRootWindow* pMoveRootWindow_callback_;
添加成员函数void SetMoveRootWindowCallback( RenderViewMoveRootWindow* callback);实现该类新增的2个函数
void RenderViewHostImpl::OnStartMoveRootWindow()
{
if (pMoveRootWindow_callback_ != nullptr)
{
pMoveRootWindow_callback_->SetMoveWindow(true);
}
}
void RenderViewHostImpl::SetMoveRootWindowCallback(RenderViewMoveRootWindow* callback)
{
pMoveRootWindow_callback_ = callback;
}
修改render_widget_host_view_aura.cc中InternalSetBounds()函数如下地方
if (!legacy_render_widget_host_HWND_) {
legacy_render_widget_host_HWND_ = LegacyRenderWidgetHostHWND::Create(
reinterpret_cast<HWND>(GetNativeViewId()));
RenderViewHostImpl *hostimpl = RenderViewHostImpl::From(host_);
if (hostimpl != nullptr)
{
hostimpl->SetMoveRootWindowCallback(legacy_render_widget_host_HWND_);
}
}
如此,即可通过回调函数通知类LegacyRenderWidgetHostHWND产生了拖拽窗口的事件。
事情到这里,就已经相当明了了,跟普通win32程序一样,处理子窗口消息,移动父窗口即可了,贴上代码
实现先前增加的SetMoveWindow函数
void LegacyRenderWidgetHostHWND::SetMoveWindow(bool bMove)
{
bMoveRootWindow_ = bMove;
SetCapture();
}
修改OnMouseRange()如下
LRESULT LegacyRenderWidgetHostHWND::OnMouseRange(UINT message,
WPARAM w_param,
LPARAM l_param,
BOOL& handled) {
if (message == WM_MOUSEMOVE) {
if (!mouse_tracking_enabled_) {
mouse_tracking_enabled_ = true;
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hwnd();
tme.dwHoverTime = 0;
TrackMouseEvent(&tme);
}
}
//记录点击点的位置
if (WM_LBUTTONDOWN)
{
if (bMoveRootWindow_ == FALSE)
{
pt_buttondown_.x = GET_X_LPARAM(l_param);
pt_buttondown_.y = GET_Y_LPARAM(l_param);
}
}
//移动父窗口
if (bMoveRootWindow_)
{
if (message == WM_MOUSEMOVE && (w_param & MK_LBUTTON))
{
handled = true;
HWND hRoot = ::GetParent(::GetParent(GetParent()));
RECT rt;
::GetWindowRect(hRoot, &rt);
POINT pt;
//pt.x = GET_X_LPARAM(l_param);
//pt.y = GET_Y_LPARAM(l_param);
GetCursorPos(&pt);
ScreenToClient(&pt);
//取两次鼠标差值
int offX = pt.x - pt_buttondown_.x;
int offY = pt.y -