转载请注明原始出处。谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/41144283

duilib库中原本没有显示的对控件添加拖拽的功能。而实际使用过程中拖拽功能也是实用武之地的。

看群里有人问题duilib怎么支持拖拽。我也就写这篇文章说明一下duilib实现控件拖拽的方法。

当我刚接触duilib不就的时候,考虑过duilib拖拽这个功能,当时的想法是,在xml布局中设置一个浮动的控件。正常状态下他是隐藏的。当出发了拖拽条件后将他显示而且尾随鼠标移动。

这种方法显然并不优雅,编写代码后,因为还要借助其它的控件,所以耦合度太高,还要受到非常多条件限制,并不easy把拖拽功能封装到一个单独控件里。

后来读完duilib的源代码而且慢慢熟悉他后发现,实际上。duilib尽管没有显示的支持控件拖拽,但他自身已经必备了控件拖拽的条件和机制,而且在一些控件中已经运用!比如横纵向布局的sepwidth属性和sepimm属性来支持布局手动拖动边框改变大小,ListHeader支持拖动改变自身的宽度。

分析机制:

这里我先分析一下横向布局CHorizontalLayoutUI的拖拽机制,两个关键属性的介绍例如以下:

       <Attribute name="sepwidth" default="0" type="INT" comment="分隔符宽,正负表示分隔符在左边还是右边,如(-4)"/><Attribute name="sepimm" default="false" type="BOOL" comment="拖动分隔符是否马上改变大小,如(false)"/>

sepwidth属性非常easy,我就不说了。sepimm属性表示拖动布局的分隔符时是否马上改变大小。以下两幅图各自是不马上改变大小的和马上改变大小的:

能够看到不马上改变大小的拖拽。会有一个半透明黑色阴影来表示当前拖动的位置。马上拖动就是立刻改变了容器的大小。拖拽功能的实现代码主要在DoEvent函数和DoPostPaint函数中完毕的。DoEvent函数代码例如以下:

    void CHorizontalLayoutUI::DoEvent(TEventUI& event){if( m_iSepWidth != 0 ) {if( event.Type == UIEVENT_BUTTONDOWN && IsEnabled() ){RECT rcSeparator = GetThumbRect(false);if( ::PtInRect(&rcSeparator, event.ptMouse) ) {m_uButtonState |= UISTATE_CAPTURED;ptLastMouse = event.ptMouse;m_rcNewPos = m_rcItem;if( !m_bImmMode && m_pManager ) m_pManager->AddPostPaint(this);return;}}if( event.Type == UIEVENT_BUTTONUP ){if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {m_uButtonState &= ~UISTATE_CAPTURED;m_rcItem = m_rcNewPos;if( !m_bImmMode && m_pManager ) m_pManager->RemovePostPaint(this);NeedParentUpdate();return;}}if( event.Type == UIEVENT_MOUSEMOVE ){if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {LONG cx = event.ptMouse.x - ptLastMouse.x;ptLastMouse = event.ptMouse;RECT rc = m_rcNewPos;if( m_iSepWidth >= 0 ) {if( cx > 0 && event.ptMouse.x < m_rcNewPos.right - m_iSepWidth ) return;if( cx < 0 && event.ptMouse.x > m_rcNewPos.right ) return;rc.right += cx;if( rc.right - rc.left <= GetMinWidth() ) {if( m_rcNewPos.right - m_rcNewPos.left <= GetMinWidth() ) return;rc.right = rc.left + GetMinWidth();}if( rc.right - rc.left >= GetMaxWidth() ) {if( m_rcNewPos.right - m_rcNewPos.left >= GetMaxWidth() ) return;rc.right = rc.left + GetMaxWidth();}}else {if( cx > 0 && event.ptMouse.x < m_rcNewPos.left ) return;if( cx < 0 && event.ptMouse.x > m_rcNewPos.left - m_iSepWidth ) return;rc.left += cx;if( rc.right - rc.left <= GetMinWidth() ) {if( m_rcNewPos.right - m_rcNewPos.left <= GetMinWidth() ) return;rc.left = rc.right - GetMinWidth();}if( rc.right - rc.left >= GetMaxWidth() ) {if( m_rcNewPos.right - m_rcNewPos.left >= GetMaxWidth() ) return;rc.left = rc.right - GetMaxWidth();}}CDuiRect rcInvalidate = GetThumbRect(true);m_rcNewPos = rc;m_cxyFixed.cx = m_rcNewPos.right - m_rcNewPos.left;if( m_bImmMode ) {m_rcItem = m_rcNewPos;NeedParentUpdate();}else {rcInvalidate.Join(GetThumbRect(true));rcInvalidate.Join(GetThumbRect(false));if( m_pManager ) m_pManager->Invalidate(rcInvalidate);}return;}}if( event.Type == UIEVENT_SETCURSOR ){RECT rcSeparator = GetThumbRect(false);if( IsEnabled() && ::PtInRect(&rcSeparator, event.ptMouse) ) {::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE)));return;}}}CContainerUI::DoEvent(event);}

在DoEvent函数里主要用UIEVENT_BUTTONDOWN、UIEVENT_BUTTONUP、UIEVENT_MOUSEMOVE三个事件完毕了这个功能。

1、在UIEVENT_BUTTONDOWN事件里将m_uButtonState变量赋值为UISTATE_CAPTURED,这个标志用来表示这个容器当前是否处在拖拽状态下;ptLastMouse变量赋值为当前鼠标的坐标;m_rcNewPos赋值为当前容器所在的位置,从名字上能够看出来他表示控件的新的位置的区域。然后依据m_bImmMode变量的状态(表示是否马上改变大小)来决定是否调用AddPostPaint函数(这个函数非常关键,后面讲)

2、在UIEVENT_MOUSEMOVE事件里。LONG cx = event.ptMouse.x - ptLastMouse.x;计算出鼠标移动过程的偏移量,用来计算出控件移动的新位置,在事件处理的尾部的代码

                 if( m_bImmMode ) {m_rcItem = m_rcNewPos;NeedParentUpdate();}else {rcInvalidate.Join(GetThumbRect(true));rcInvalidate.Join(GetThumbRect(false));if( m_pManager ) m_pManager->Invalidate(rcInvalidate);}

依据m_bImmMode来选择两种画图的方式。一个是直接指定容器为新的区域大小。还有一个是指定了无效区域,然后由DoPostPaint函数来完毕绘制工作。

3、在UIEVENT_BUTTONUP事件里。将m_uButtonState变量取消为UISTATE_CAPTURED状态。而且强制刷新了界面。

分析完成:


在上面说到的内容里,马上改变容器大小非常easy理解。我就不再说明了。我说一下非马上改变大小和位置,而是出现一个阴影效果的实现方式。

这个效果更经常使用,比方我们在IM软件时,拖拽一个好友到还有一个分组内,经常是有一个代表好友的阴影框跟着鼠标移动,能够通过这里的代码实现。

要实现非马上改变大小和位置。必须用到让自己的控件重写DoPostPaint函数,而且在适当的是否调用PaintManager的AddPostPaint函数来注冊自己,比如上面的样例在UIEVENT_BUTTONDOWN事件里调用AddPostPaint,在UIEVENT_BUTTONUP里调用RemovePostPaint。

调用AddPostPaint后PaintManager就会在WM_PAINT消息里绘制完一次完整界面后。去调用我们的控件的DoPostPaint函数,让我们自己来完毕绘制完之后的绘制!这时我们在DoPostPaint函数里完毕阴影的绘制就能够了。

假设不使用DuiLib的这个机制而直接在DoPaint函数里绘制阴影。就无法达到我们须要的效果,由于在DoPaint函数调用的时候软件的界面还可能没有全然绘制完,我们绘制的阴影可能会被其它控制的DoPaint函数覆盖!

       须要说明的是,在DoPosiPaint函数中,绘制的范围不不过本控件的范围,而是整个程序的客户区。所以这个拖拽的阴影能够画到窗口的任何位置,当然我们也能够通过代码推断来控制绘制的范围,这也就是我之前说的duilib本身已经有支持拖拽的机制和条件了。

看一下DoPostPaint的函数代码:

    void CHorizontalLayoutUI::DoPostPaint(HDC hDC, const RECT& rcPaint){if( (m_uButtonState & UISTATE_CAPTURED) != 0 && !m_bImmMode ) {RECT rcSeparator = GetThumbRect(true);CRenderEngine::DrawColor(hDC, rcSeparator, 0xAA000000);}}

函数里通过GetThumbRect函数获取了当前的切割条的位置,然后绘制了一个半透明的黑色阴影。说到这里就已经全然说明了duilib中控件拖拽功能的实现方法了。

自己实现拖拽:

我们相同能够有两种拖拽方案:

一、马上拖拽模式:

继承须要的控件并重写DoEvent函数。在UIEVENT_BUTTONDOWN、UIEVENT_BUTTONUP、UIEVENT_MOUSEMOVE三个事件里直接依据鼠标的位置来改变控件的坐标和大小。显然这就要求我们设置float属性为真,这样的方式实现起来也简单。我就不写代码了。

二、非马上拖拽模式:

1、 继承须要的控件并重写DoEvent函数,在UIEVENT_BUTTONDOWN、UIEVENT_BUTTONUP、UIEVENT_MOUSEMOVE三个事件,重写DoPostPaint函数完毕阴影的绘制

2、在UIEVENT_BUTTONDOWN事件里调用AddPostPaint函数注冊本控件

3、在UIEVENT_MOUSEMOVE事件里计算新的控件位置。而且将新旧位置组合起来调用Invalidate函数刷新位置(否则会有残影)

4、在UIEVENT_BUTTONUP事件里调用RemovePostPaint反注冊自己。而且刷新控件。

我写了个简单的样例,而且实验成功了,这是实验代码:

void CModulePaneCellUI::DoEvent(TEventUI& event)
{if( event.Type == UIEVENT_BUTTONDOWN && IsEnabled() ){if( ::PtInRect(&m_rcItem, event.ptMouse) ) {m_uButtonState |= UISTATE_CAPTURED;m_ptLastMouse = event.ptMouse;m_rcNewPos = m_rcItem;if( m_pManager )m_pManager->AddPostPaint(this);return;}}if( event.Type == UIEVENT_BUTTONUP ){if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {m_uButtonState &= ~UISTATE_CAPTURED;CModulePaneConfigUI* pParent = static_cast<CModulePaneConfigUI*>(m_pParent);pParent->NotifyDrag(this);    // NotifyDrag函数是CModulePaneConfigUI容器的函数。和拖拽的效果本身没关系if(  m_pManager ) {m_pManager->RemovePostPaint(this);m_pManager->Invalidate(m_rcNewPos);}NeedParentUpdate();return;}}if( event.Type == UIEVENT_MOUSEMOVE ){if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {LONG cx = event.ptMouse.x - m_ptLastMouse.x;LONG cy = event.ptMouse.y - m_ptLastMouse.y;m_ptLastMouse = event.ptMouse;RECT rcCurPos = m_rcNewPos;rcCurPos.left += cx;rcCurPos.right += cx;rcCurPos.top += cy;rcCurPos.bottom += cy;          //将当前拖拽块的位置 和 当前拖拽块的前一时刻的位置。刷新CDuiRect rcInvalidate = m_rcNewPos;m_rcNewPos = rcCurPos;rcInvalidate.Join(m_rcNewPos);if( m_pManager ) m_pManager->Invalidate(rcInvalidate);return;}}if( event.Type == UIEVENT_SETCURSOR ){if( IsEnabled() ) {::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND)));return;}}CLabelUI::DoEvent(event);
}void CModulePaneCellUI::DoPostPaint(HDC hDC, const RECT& rcPaint)
{if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {CDuiRect rcParent = m_pParent->GetPos();RECT rcUpdate ={0};rcUpdate.left = m_rcNewPos.left < rcParent.left ? rcParent.left : m_rcNewPos.left;rcUpdate.top = m_rcNewPos.top < rcParent.top ? rcParent.top : m_rcNewPos.top;rcUpdate.right = m_rcNewPos.right > rcParent.right ?

rcParent.right : m_rcNewPos.right; rcUpdate.bottom = m_rcNewPos.bottom > rcParent.bottom ? rcParent.bottom : m_rcNewPos.bottom; CRenderEngine::DrawColor(hDC, rcUpdate, 0xAA000000); } }

这是实验效果,蓝色部分是原本的button控件。黑色阴影是正在被拖拽的过程。我在DoPostPaint中控制了阴影绘制范围不超过父容器。

这仅仅是实验,我没实用好看的素材去做,可是证明了可行性,替换素材后将会变得非常好看。CModulePaneConfigUI是红色的容器,实现了拖拽元素的类似磁块效果自己主动布局,这个和拖拽效果本身没关系。CModulePaneCellUI是拖拽元素控件。他能够是继承不论什么Duilib控件而来的。

使用这种方法。能够改动duilib原本的代码,为List控件和TreeView控件支持拖拽功能。也能够自己定义某些特殊用途的控件。

这里提供了绘制拖拽效果的思路,详细使用方法是多变的。

总结:

这是我之前为了完毕某个功能而写的代码,希望能够帮到须要的朋友。假设代码中有bug,或者有更好的拖拽实现方法,请联系我或者留言。


Redrain  2014.11.15


QQ:491646717

版权声明:本文博客原创文章,博客,未经同意,不得转载。

转载于:https://www.cnblogs.com/gcczhongduan/p/4682610.html

duilib拖动控制功能的实现(源代码)相关推荐

  1. Android之实现RecyclerView拖拽效果和固定部分元素不进行交换位置

    1.效果爆照 启动的效果 拖动过程中的效果 拖动后的效果 源代码下载地址:https://download.csdn.net/download/u011068702/13984549

  2. 【C#】WPF实现经典纸牌游戏,适合新手入门

    文章目录 1 纸牌类 2 布局 3 初始化 4 事件 点击牌堆 拖动 牌的去留 源代码 1 纸牌类 之所以产生这个无聊至极的念头,是因为发现Unicode中竟然有这种字符... 黑桃

  3. apk文件反编译成android代码

    文章主要介绍apktool,jd-gui,dex2j的编译步骤,若有其他需要可直接滑到文章最后有自动化工具下载地址.自动化工具不需要代码操作,直接拖动反编译出源代码. 1.Apk反编译步骤 准备工具 ...

  4. 教你如何用duilib实现控件可拖动,可拖拽

    要实现的效果: 鼠标点击控件(自绘控件,可继承任意控件类,下文将给出示例),并且进行拖拽,会有一个半透明黑色阴影来表示当前拖动的位置.当松开鼠标,控件重新绘制在鼠标松开的位置.拖拽功能的实现代码主要在 ...

  5. duilib进阶教程 -- 改进窗口拖动 (12)

    现在大家应该都知道caption="0,0,0,32",是指示标题栏区了吧,如果想要整个窗口都能拖动呢? 那直接把高度改成和窗口一样不就得了~O(∩_∩)O~ 嗯,这样是可以,比如 ...

  6. 【181127】VC++ 实现鼠标可随意拖动图像源代码

    源码下载简介 VC++ 6.0图片处理源码实例,鼠标拖动图片移动,按住鼠标可移动图片,按鼠标的同时鼠标的形状发生变化,我想这个功能可用到图片处理软件中吧,新手们可学习下此功能是如何实现的. 源码下载地 ...

  7. 基于java的俄罗斯方块游戏系统设计与实现(项目报告+答辩PPT+源代码+数据库+截图+部署视频)

    基于Java的俄罗斯方块游戏的设计与实现 俄罗斯方块是一款风靡全球,从一开始到现在都一直经久不衰的电脑.手机.掌上游戏机产品,是一款游戏规则简单,但又不缺乏乐趣的简单经典小游戏,上手容易,适用范围广泛 ...

  8. 【iOS】自定义控件入门:可拖动的环形进度

    有时候UIKit的标准控件并不能满足我们的需求,因此我们可以通过自定义控件得到满足我们需求的控件,例如这篇文章将教你如何自定义一个圆形的进度条,并且用户可以通过拖动进度条上的手柄来改变进度值.主要参考 ...

  9. 最短路径问题 java实现 源代码

    2019独角兽企业重金招聘Python工程师标准>>> 最短路径问题  java实现 源代码下载地址:http://download.csdn.net/source/246269 用 ...

最新文章

  1. java钩子函数(hook)以spring源码为例
  2. 自动化软件部署的shell脚本
  3. 成都python数据分析师职业技能_数据分析师需要什么技能,数据分析行业都有什么职业?...
  4. Linux umask限制导致php的mkdir 0777无效
  5. 洛谷P1312 Mayan游戏
  6. Java学习总结(随笔)——利用JSON解析实现网络下载
  7. 《Unity着色器和屏幕特效开发秘笈》—— 1.7 创建渐变纹理来控制漫反射着色...
  8. 云豹直播源码v8.2
  9. 用Linux编写C语言程序
  10. 光盘显示0字节可用_松下PLC编程软件FPWIN Pro7.4.0.0
  11. 数据结构-直接选择排序
  12. 教育类App原型制作分享-Busuu
  13. IDEA常用插件、设置、注释
  14. 汤姆猫代码python_用树莓派实现会说话的汤姆猫
  15. 从零一起学Spring Boot之LayIM项目长成记(二) LayIM初体验
  16. html5 ogv格式转换,ffmpeg – Mac上的HTML5视频OGV转换
  17. 二等水准测量记录数据_2等水准测量原始记录表
  18. 带翻转特效的会员登录注册html页面源码
  19. 2020-10-17
  20. RocksDB 常见问题

热门文章

  1. 浅析网站建设的基本原则
  2. 网站SEO优化之如何提升访客量?
  3. github 慢_告别github 下载慢问题,让你的github下载速度起飞
  4. 六年级下册计算机电子板报教案,小学信息技术闽教版四年级下册第6课 制作电子板报教案设计...
  5. java unsafe cas_Java Unsafe CAS 小试
  6. .dat文件写入byte类型数组_深入浅出MATLAB数据处理之文件读写
  7. Wireshark筛选常用命令
  8. 开发日记-20190701 关键词 读书笔记《Linux 系统管理技术手册(第二版)》DAY 7
  9. Faas 典型场景——应用负载有显著的波峰波谷,典型用例-基于事件的数据处理...
  10. NFL原则告诉我们做决策的时候,试图找到一个能解决所有问题,“大而全”的方案是不存在的。我们应当找到最关心的问题,因地制宜做出选择。——聚焦目标,取舍有道!...