duilib拖动控制功能的实现(源代码)
转载请注明原始出处。谢谢~~: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属性表示拖动布局的分隔符时是否马上改变大小。以下两幅图各自是不马上改变大小的和马上改变大小的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemh1aG9uZ3NodQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
能够看到不马上改变大小的拖拽。会有一个半透明黑色阴影来表示当前拖动的位置。马上拖动就是立刻改变了容器的大小。拖拽功能的实现代码主要在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
版权声明:本文博客原创文章,博客,未经同意,不得转载。
duilib拖动控制功能的实现(源代码)的更多相关文章
- Duilib 入门教程: 怎么创建一个自定义的窗口
一直想找一个好用UI 界面库,看过Direct UI,也想过 金山的界面库,后来找到了这个Duilib 现在的软件界面很多都是利用XML 来布局和定位. 像迅雷7,QQ,金山卫士等 [html] vi ...
- Duilib改进窗口拖动,使整个窗口都能拖动两种方法(转载)
转载:http://www.cnblogs.com/XiHua/articles/3490490.html 转载:http://blog.csdn.net/lostspeed/article/deta ...
- Redrain个人维护并使用的DuiLib和UiLib库源代码下载地址
转载请说明原出处:http://blog.csdn.net/zhuhongshu/article/details/40740353,谢谢~~ 首先说明一下Duilib和Uilib的差别:UiLIb是D ...
- Android 高仿 频道管理----网易、今日头条、腾讯视频 (能够拖动的GridView)附源代码DEMO
距离上次公布(android高仿系列)今日头条 --新闻阅读器 (二) 相关的内容已经半个月了.近期利用空暇时间,把今日头条client完好了下.完好的功能一个一个所有实现后.就放整个源代码.开发的进 ...
- Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源代码
在android学习中,动作交互是软件中重要的一部分.当中的Scroller就是提供了拖动效果的类,在网上.比方说一些Launcher实现滑屏都能够通过这个类去实现.以下要说的就是上次Scroller ...
- duilib进阶教程 -- 改进窗口拖动 (12)
现在大家应该都知道caption="0,0,0,32",是指示标题栏区了吧,如果想要整个窗口都能拖动呢? 那直接把高度改成和窗口一样不就得了~O(∩_∩)O~ 嗯,这样是可以,比如 ...
- duilib 实现列表头任意拖动
1.表头(xml) <List name="List_records" padding="5,10,5,5" bkcolor="#FFFFFFF ...
- Duilib非官方更新贴~
GitHub: https://github.com/movsb/duilib.git 2014-07-20: [76a04d1] [BugFix] 修复无法解析类似<Control/&g ...
- arcgis api for js入门开发系列七图层控制(含源代码)
上一篇实现了demo的地图分屏对比模块,本篇新增图层控制模块,截图如下(源代码见文章底部): 图层控制模块实现的思路如下: 1.在地图配置文件map.config.js里面配置图层目录树节点信息,作为 ...
随机推荐
- ZOJ 2514 Generate Passwords 水
啦啦啦,水一发准备去复习功课~ ------------------------------------------水一发的分割线----------------------------------- ...
- PHP中出现BOM字符\ufeff,PHP去掉诡异的BOM \ufeff
研究一个PHP项目的时候,今天项目突然打不开了. 前几天还好好的,用Chrome看了下Response的内容,AJAX页面和普通HTML页面内容前面有一个红色的点. 鼠标移上去,提示"\uf ...
- 在Eclipse中运行hadoop程序 分类: A1_HADOOP 2014-12-14 11:11 624人阅读 评论(0) 收藏
1.下载hadoop-eclipse-plugin-1.2.1.jar,并将之复制到eclipse/plugins下. 2.打开map-reduce视图 在eclipse中,打开window--> ...
- css3-7 如何让页面元素水平垂直都居中(元素定位要用css定位属性)
css3-7 如何让页面元素水平垂直都居中(元素定位要用css定位属性) 一.总结 一句话总结:元素定位要用css定位属性,而且一般脱离文档流更加好操作.先设置为绝对定位,上左都50%,然后margi ...
- VIM HML
D:\skill\Apps\Vim\vim80\defaults.vim "set scrolloff=5 设置为默认值0即可
- leveldb学习:Versionedit和Versionset
VersionEdit: compact过程中会有一系列改变当前Version的操作(FileNumber添加.删除input的sstable,添加输出的sstable).为了缩小version切换的 ...
- 版本控制(1)——SVN
一.工具下载 下载SVN: http://subversion.apache.org/ 我们选择Windows系统中的可视化的VisualSVN 如下图,左边是客户端,右边是服务器端,我们下载服务器端 ...
- Java冒泡排序与直接选择排序代码随笔
冒泡排序:延申的有很多种,有的是先确定最大值放到后面,有的是先确定最小值放到前边,还有就是反过来,先确定最小值的位置,但是本质都是:不断两两比较,交换位置...第一趟确定一个最大(最小)值放到前边(后 ...
- 《iOS8 Swift编程指南》类书图像
终于拿到了样书.虽然已经猜到这将是一本很厚的书(63万字),但要真正看到实体书或者当我吃了一惊: 从以下这张照片看则更直观了.居然比艾伦.J.马库斯的<投资学>(634页)还要厚: 这本书 ...
- Java SpringMVC实现国际化整合案例分析(i18n) 专题
所谓国际化就是支持多种语言,web应用在不同的浏览环境中可以显示出不同的语言,比如说汉语.英语等.下面我将以具体的实例来举例说明: (1)新建动态Javaweb项目,并导入几个SpringMVC必需的 ...