转载请说明原出处,谢谢~~:https://redrain.blog.csdn.net/article/details/107105312

cef显示web分为窗口模式和离屏渲染模式(osr,off screen rendering)。窗口模式使用起来比较简单,基本的功能都已经实现,包括web内部的拖拽。而osr模式需要实现相关接口比较麻烦

窗口模式:

窗口模式的拖拽控制接口只需要关心CefDragHandler。

class CefDragHandler : public virtual CefBaseRefCounted {public:typedef cef_drag_operations_mask_t DragOperationsMask;///// Called when an external drag event enters the browser window. |dragData|// contains the drag event data and |mask| represents the type of drag// operation. Return false for default drag handling behavior or true to// cancel the drag event.////*--cef()--*/virtual bool OnDragEnter(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDragData> dragData,DragOperationsMask mask) {return false;}///// Called whenever draggable regions for the browser window change. These can// be specified using the '-webkit-app-region: drag/no-drag' CSS-property. If// draggable regions are never defined in a document this method will also// never be called. If the last draggable region is removed from a document// this method will be called with an empty vector.////*--cef()--*/virtual void OnDraggableRegionsChanged(CefRefPtr<CefBrowser> browser,const std::vector<CefDraggableRegion>& regions) {}
};

其中CefDragHandler::OnDragEnter在web中有内容被拖拽时被调用,这时可以根据拖拽的内容,决定是否要阻止拖拽。

CefDragHandler::OnDraggableRegionsChanged是让web内部自己设置一个拖拽区域,然后通知给c++,让c++把这块区域也设置为非客户区,用户可以拖拽这块区域来移动整个窗口

osr模式:

离屏渲染模式需要自己实现拖拽接口,离屏渲染继承了CefRenderHandler接口,其中有两个方法是实现拖拽的:

  // Called when the user starts dragging content in the web view. Contextual// information about the dragged content is supplied by |drag_data|.// (|x|, |y|) is the drag start location in screen coordinates.// OS APIs that run a system message loop may be used within the// StartDragging call.//// Return false to abort the drag operation. Don't call any of// CefBrowserHost::DragSource*Ended* methods after returning false.//// Return true to handle the drag operation. Call// CefBrowserHost::DragSourceEndedAt and DragSourceSystemDragEnded either// synchronously or asynchronously to inform the web view that the drag// operation has ended.////*--cef()--*/virtual bool StartDragging(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDragData> drag_data,DragOperationsMask allowed_ops,int x,int y) {return false;}///// Called when the web view wants to update the mouse cursor during a// drag & drop operation. |operation| describes the allowed operation// (none, move, copy, link).////*--cef()--*/virtual void UpdateDragCursor(CefRefPtr<CefBrowser> browser,DragOperation operation) {}

其中StartDragging方法是web开始拖拽时的回调,在这里可以按照windows系统的拖拽模块来实现一个阻塞的拖拽功能。参照cef demo的写法,把osr_dragdrop_win.h、osr_dragdrop_win.cc、osr_dragdrop_events.h这三个文件搬过来,里面实现了windows的拖拽需要的DropTargetWin类。把cef demo的代码搬过来填充到StartDragging里。

为了让DropTargetWin可以正常工作,需要实现osr_dragdrop_events.h中的OsrDragEvents接口。

除了这些工作,就是windows窗口需要实现拖拽功能,需要调用一个api RegisterDragDrop,这个api让窗口的拖拽事件与DropTargetWin关联,当窗口收到拖拽相关消息时会通知DropTargetWin,DropTargetWin再去调用browser中对应一些接口来通知web进行拖拽响应。

理论上实现完这些步骤就可以完成拖拽了。具体的实现代码可以参考cef client demo。

我遇到的坑:

我的osr模式的拖拽实现完毕后,出现了一个奇怪的问题:

  1. 某些网页中被拖拽的内容松开后,会托拽失败,回到原位
  2. 某些网页中被拖拽的内容松开后,就会执行网页的跳转操作

刚碰到这个问题,从现象来看,我以为是osr模式中一些鼠标坐标处理有问题,调试了2天也没发现问题。与cef demo反复对比也没发现什么差异。最终看StartDragging方法的描述时注意到一点:

  ///// Called when the user starts dragging content in the web view. Contextual// information about the dragged content is supplied by |drag_data|.// (|x|, |y|) is the drag start location in screen coordinates.// OS APIs that run a system message loop may be used within the// StartDragging call.//// Return false to abort the drag operation. Don't call any of// CefBrowserHost::DragSource*Ended* methods after returning false.//// Return true to handle the drag operation. Call// CefBrowserHost::DragSourceEndedAt and DragSourceSystemDragEnded either// synchronously or asynchronously to inform the web view that the drag// operation has ended.////*--cef()--*/virtual bool StartDragging(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDragData> drag_data,DragOperationsMask allowed_ops,int x,int y);

文档最后说到在拖拽操作完成后,需要同步异步的调用DragSourceEndedAtDragSourceSystemDragEnded方法来通知拖拽接口。我在StartDragging中的确同步调用了这两个方法,然后继续看这两个方法的文档:

  ///// Call this method when the drag operation started by a// CefRenderHandler::StartDragging call has ended either in a drop or// by being cancelled. |x| and |y| are mouse coordinates relative to the// upper-left corner of the view. If the web view is both the drag source// and the drag target then all DragTarget* methods should be called before// DragSource* mthods.// This method is only used when window rendering is disabled.////*--cef()--*/virtual void DragSourceEndedAt(int x, int y, DragOperationsMask op) = 0;///// Call this method when the drag operation started by a// CefRenderHandler::StartDragging call has completed. This method may be// called immediately without first calling DragSourceEndedAt to cancel a// drag operation. If the web view is both the drag source and the drag// target then all DragTarget* methods should be called before DragSource*// mthods.// This method is only used when window rendering is disabled.////*--cef()--*/virtual void DragSourceSystemDragEnded() = 0;

文档里描述DragTarget* 等方法需要在DragSource*等方法之前被调用,于是我下断点调试,发现的确是DragTarget*等方法在DragSource*之后被调用了。

原因是我开始了cef的多线程消息循环multi_threaded_message_loop)。DragTarget*等方法在主程序的ui线程(因为用了多线程消息循环,所以主程序的ui线程和cef的ui线程是两个独立线程)里被调用了。他们内部发现线程并不是cef的ui线程,所以会被DragTarget*等方法的调用转到cef的ui线程。从而导致DragTarget*等方法的调用被延迟了,所以导致了最终的bug。

但是为什么DragTarget*等方法会在主程序的ui线程里触发呢?DragTarget*等方法是在StartDragging调用了win32的api ::DoDragDop而从同步触发的,StartDragging是在cef的ui线程被触发的,怎么同步触发到DragTarget*等方法就变成了主程序的ui线程了?

最终我发现是我之前说道的win32 api RegisterDragDrop的一个细节,我在主程序的ui线程里调用了这个api,如果在cef的ui线程里调用。那么DragTarget*等方法就会在cef的ui线程里被触发了。bug就解决了!

RegisterDragDrop内部会在调用这个API的线程里创建一个窗口,用过这个窗口来做消息循环模拟阻塞的过程,所以哪个线程调用RegisterDragDrop,就会在哪个线程阻塞并触发IDragTarget回调。见https://docs.microsoft.com/zh-cn/windows/win32/api/ole2/nf-ole2-registerdragdrop

总结:

执行::DoDragDrop时,会在调用RegisterDragDrop的线程触发的DragOver、DragLeave、Drop、Drop回调
进而调用browser_->GetHost()->DragTargetDragEnter、DragTargetDragOver、DragTargetDragLeave、DragTargetDrop
这几个cef接口内部发现不在cef ui线程触发,则会转发到cef ui线程
导致DragSourceEndedAt接口被调用时有部分DragTarget*方法没有被调用
最终拖拽效果就会有问题,详见DragSourceEndedAt接口描述
所以在cef ui线程调用RegisterDragDrop,让后面一系列操作都在cef ui线程里同步执行,则没问题RegisterDragDrop内部会在调用这个API的线程里创建一个窗口,用过这个窗口来做消息循环模拟阻塞的过程
所以哪个线程调用RegisterDragDrop,就会在哪个线程阻塞并触发IDragTarget回调
见https://docs.microsoft.com/zh-cn/windows/win32/api/ole2/nf-ole2-registerdragdrop

题外话:

对于普通需求来说这样已经足够了,每一个browser对象都分配了一个对应的CefClient,都有对应的拖拽的实现。不过cef demo里面的实现是拖拽功能必须限制一个窗口内部只有一个browser,而我的需求是一个窗口内多个osr browser,每个browser都可以执行拖拽操作。为此我另外重写了cef demo附带的DropTargetWin,可以让一个窗口支持同时嵌入多个osr browser并完成拖拽。这个不是这篇分享的重点,我就不另外写了。

Redrain

QQ:491646717

2020.7.3

cef osr拖拽功能实现相关推荐

  1. android列表实现置顶,Android利用RecyclerView实现全选、置顶和拖拽功能示例

    Android利用RecyclerView实现全选.置顶和拖拽功能示例 发布时间:2020-08-23 16:26:42 来源:脚本之家 阅读:159 作者:爱开发 前言 今天给大家分享是如何在Rec ...

  2. android 当前置顶的控件,Android利用RecyclerView实现全选、置顶和拖拽功能示例

    前言 今天给大家分享是如何在RecyclerView实现全选,ItemTouchHelper实现侧滑删除,拖拽功能.比较基础.关于RecyclerView的强大,就不多说了.在Android L SD ...

  3. java 拖拽生成_JAVA UI 拖拽功能

    java GUI拖拽功能是很实用也相对高级一些的功能. 有一小部分的GUI控件支持 他们有dragEnabled属性.这些JComponent包括: javax.swing.JColorChooser ...

  4. Python+selenium 自动化高级应用篇:借助pyautogui实现web前端带轨迹拖拽功能,解决ActionChains拖拽失效问题

    有一些 web 前端的交互,必须有鼠标轨迹才能成功的实现拖拽功能. 而 selenium 自带的 ActionChains 方法,是一瞬间从 A 点到 B 点的. 解决思路: 利用元素返回的相对浏览器 ...

  5. tableau必知必会之拖拽功能失效是怎么回事

    大家反馈的问题如上图所示,可以发现,原本正常的拖拽功能失效了. 数据粉们别着急,这不是 Bug. 首先,我们来看一下你可能遇到的拖拽失效的几种情况: 一.维度.度量无法拖入标记卡 二.维度.度量无法拖 ...

  6. easyui树拖拽排序java_easyui tree 拖拽功能并将数据返回后台保存至数据库

    1.需要实现tree的拖拽功能,首先在定义一个tree时候需要设置 dnd : true 2.easyui 给我们提供了一个onDrop : function(target, source, poin ...

  7. css元素可拖动,使用css-transform实现更好的拖拽功能

    拖拽功能是目前网页上一种非常常见的功能,例如"登录弹窗"的拖拽.本文将使用transform来实现这一功能. 一.拖拽的用户行为分析与原理解析 二.代码实现 三.总结 本文所涉及的 ...

  8. swift 拖动按钮_Swift下使用UICollectionView 实现长按拖拽功能

    导读 简单用Swift写了一个collectionview的拖拽点击排序效果; 拖拽排序是新闻类的App可以说是必有的交互设计,如今日头条,网易新闻等. 效果 主要代码 手势长按移动 1.给Colle ...

  9. js实现鼠标拖拽功能基本思路

    如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒. onmousedown:鼠标按下事件 onmousemove:鼠标移动事件 onmouseup:鼠标抬起事件 拖拽的基本原 ...

最新文章

  1. sqlite 增删改查附代码
  2. 解决IllegalStateException: Can not perform this action after onSaveInstanceState
  3. C语言动态二维数组,结构体动态申请内存空间
  4. MIT最新课程:一文看尽深度学习各领域最新突破(附视频、PPT)
  5. Ubuntu 新建qt项目时出现 No valid kits found.
  6. graphic方法重写 unity_Unity面试题精选(2)
  7. 联想揭晓首款基于 Arm 的电脑,未来的发展方向怎样?
  8. Material使用05 MdListModule模块 MdButtonToggleModule模块
  9. 如何自己搭建测试环境
  10. eu指什么_鞋码eu是什么意思 鞋子尺码eu对照表
  11. SQL Server 2008 Service Pack 1 - CTP 发布
  12. 论文阅读:RGCF: Refined Graph Convolution Collaborative Filering with Concise and Expressive Embedding
  13. 有道获取单词读音api
  14. NLP标签/关键词-提取工具-java开发
  15. 2022Q4手机银行新版本聚焦提升客群专属、财富开放平台、智能化能力,活跃用户规模6.91亿人
  16. 一些电子资源的分享(1)
  17. R语言——(六)、线性回归模型
  18. 使用tushare获取股票数据并计算历史概念板块的平均涨跌幅
  19. 同样是研究生,读2年和3年有什么区别?
  20. 脉冲函数、阶跃函数和斜坡函数

热门文章

  1. 软件工程基础知识--认识软件工程
  2. 象形文字(表意文字)、字符文字(表音文字)由来畅谈
  3. 杨澜经典语录:与思想交朋友
  4. matlab类中增加公共属性,在面向对象的MATLAB中,属性如何工作?
  5. javascript开源电子表格
  6. 重庆大学计算机学院与马云,相聚计科,执梦起航——重庆大学计算机学院2020级研究生迎新会圆满结束...
  7. 华为eNSP配置dhcp 下发ipv4地址
  8. 深圳捷迅YL-800MT PE13管脚千万不能悬空
  9. 【转载】ARPU ARPPU傻傻分不清楚?手游收入指标名词解释
  10. c:set -----------JSTL