当前位置 博文首页 > u012179390的博客:CEF使用元素事件移动无边框窗口

    u012179390的博客:CEF使用元素事件移动无边框窗口

    作者:[db:作者] 时间:2021-07-26 14:56

    拖动嵌入了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 -
    
    下一篇:没有了