拖动是界面编程频繁使用的一个效果,在windows系统下可谓大行其道。纵观时下的应用软件几乎各个都支持各种各样拖动的效果,windows7更是把拖动做到了极致。其实说起来拖动的实现也很简单,对于有句柄的对象都可以通过MoveWindow或SetWindowPos实现位置变动,而没有句柄的对象实现拖动无非就是做些参数修改,说到底实现拖动就是在OnLButtonDown、OnMouseMove和OnLButtonUp中处理数据,当然你可以使用鼠标右键甚至中建消息来实现,基本原理是一样的。
      基本原理是不难,不过要想做到效果二字就要动一番脑筋了。让我们来看看win7下的图标拖放,鼠标会拖起一个半透明的图标副本到你想要的位置,透过这个透明的图标你可以看到其下面的情况,这样的效果其实在windows的早期版本就已经实现了,它有着很好的用户体验。那么我们能不能轻松的实现类似的拖动效果呢?答案当然是肯定的!最近看到论坛里几个讨论拖动的帖子,正巧前一段时间自己也做了一些相关的工作,小研究了一下,于是就想把研究成果拿出来和大家分享,这样才有利于交流和进步嘛。以前我写博客没贴过效果图,以至于很多网友下载示例代码之后发现不是自己想要的东西,这个确实不好,在此我向大家表示歉意。这次把效果图贴上,如果觉得这个效果很一般或者不是你所需要的那就不要浪费你宝贵的时间阅读文章和下载代码了。

从图中可以看出,我的小猪头像是可以被拖动的,半透明的那个就是拖动的副本,截图的时候鼠标没有截到,呵呵。为了让半透明效果能够明显的看出来我特意为对话框贴了张背景图。被拖动的其实是一个picture ctrl,也就是一个静态控件,当然通过后面的介绍大家会发现这个方法的扩展性比较强,可以应用于很多场合,甚至可以应用于非控件的拖动对象的情况。好了,效果就是这样了,下面切入正题开始介绍实现方法。
      对于熟悉拖动效果制作的朋友们都应该知道,实现拖动有一个很简单的方法就是通过CImageList。CImageList提供了BeginDrag、DragEnter、DragMove、DragLeave、EndDrag系列函数,分别在OnLButtonDown、OnMouseMove和OnLButtonUp等消息中合理调用这些函数就可以轻松实现对CImageList的元素的拖动效果。那么我们要做的就是构造一个CImageList,使它的元素是我们想要拖动的图片,这样就大功告成了。那怎样获取图像呢?答案也很简单,就是到被拖动的对象的DC中将所要拖动的区域拷贝到一个内存位图中即可。具体到我的这个例子,我是这样实现的:
      在OnLButtonDown中判断鼠标是否在控件范围内,如果在就将控件范围内的DC内容拷贝到内存位图中,然后创建CImageList将包含有控件内容的位图添加进CImageList作为其元素,接着通过这个ImageList实现拖动。具体代码如下

void CDragDemoDlg::OnLButtonDown(UINT nFlags, CPoint point)  

{  

    CRect   rectPic;  

    POINT   ptPut   = point;  

    GetDlgItem(IDC_STATIC_DEMO)->GetWindowRect(rectPic);  

    ClientToScreen(&ptPut);  

    if(rectPic.PtInRect(ptPut))  

    {  

        CBitmap     bitmapTemp, *pOldBitmap;  

        CDC         *pDC    = GetDlgItem(IDC_STATIC_DEMO)->GetDC(),  

                    *pMemDC = new CDC;  

        //创建位图内存  

        bitmapTemp.CreateCompatibleBitmap(pDC, rectPic.Width(), rectPic.Height());  

        pMemDC->CreateCompatibleDC(pDC);  

        pOldBitmap  = pMemDC->SelectObject(&bitmapTemp);  

        pMemDC->BitBlt(0, 0, rectPic.Width(), rectPic.Height(), pDC, 0, 0, SRCCOPY);  

        pMemDC->SelectObject(pOldBitmap);  

        delete  pMemDC;  

        ReleaseDC(pDC);  

        m_bIsLButtonDown    = TRUE;  

        m_ptOffset.x    = ptPut.x-rectPic.left;  

        m_ptOffset.y    = ptPut.y-rectPic.top;  

        m_imgDrag.DeleteImageList();  

        m_imgDrag.Create(rectPic.Width(), rectPic.Height(), ILC_COLOR32|ILC_MASK, 0, 1);  

        m_imgDrag.Add(&bitmapTemp, RGB(0, 0, 0));  

        m_imgDrag.BeginDrag(0, m_ptOffset);  

        m_imgDrag.DragEnter(NULL, ptPut);  

        SetCapture();  

    }  

    CDialog::OnLButtonDown(nFlags, point);  

}

这里我说明两个问题: 一是BeginDrag(0, m_ptOffset);的m_ptOffset参数,BeginDrag函数很容易理解了,就是进入拖动状态,而m_ptOffset参数是拖动时鼠标相对于拖动图标的偏移,注意是相对偏移。大家可以自己改一下这个参数,比如改成CPoint(0, 0)来感受一下这个设置的作用。二是DragEnter(NULL, ptPut);的ptPut这个参数指定了初始拖动时图标出现的位置,这里注意这个位置不是图标左上角的位置,而是左上角加上偏移后的位置。这个位置应用的也不是相对坐标或客户区坐标,而是屏幕坐标。
      接下来是移动的处理,其实很简单就是一个DragMove函数。他有一个参数,也是一个点,意义和DragEnter的ptPut参数相似。例子中我限制了图标不能超出窗口范围,也是通过修改这个参数实现的。理论上我们可以拖着图标在屏幕范围内任意移动,不过结合这个例子如果在窗口范围以外释放鼠标那控件就找不到了,所以我做了限制,同时也可以更好的理解m_ptMove参数的意义。具体实现可以参考以下代码。

void CDragDemoDlg::OnMouseMove(UINT nFlags, CPoint point)  

{  

    if(m_bIsLButtonDown)  

    {  

        CRect       rtClient, rtPicture;  

        m_ptMove    = point;  

        GetDlgItem(IDC_STATIC_DEMO)->GetWindowRect(rtPicture);  

        GetClientRect(rtClient);  

        ClientToScreen(&rtClient);  

        ClientToScreen(&m_ptMove);  

        if(rtClient.left>m_ptMove.x-m_ptOffset.x)  

            m_ptMove.x  = rtClient.left+m_ptOffset.x;  

        if(rtClient.top>m_ptMove.y-m_ptOffset.y)  

            m_ptMove.y  = rtClient.top+m_ptOffset.y;  

        if(rtClient.right-rtPicture.Width()         m_ptMove.x  = rtClient.right-rtPicture.Width()+m_ptOffset.x;  

        if(rtClient.bottom-rtPicture.Height()           m_ptMove.y  = rtClient.bottom-rtPicture.Height()+m_ptOffset.y;  

        CImageList::DragMove(m_ptMove);  

    }  

    CDialog::OnMouseMove(nFlags, point);  

}

好了,现在就剩结束拖动状态的相关操作了,这部分就比较简单了,我代码中还加了一些容错判断和移动控件的操作,大家注意提取有效信息。

void CDragDemoDlg::OnLButtonUp(UINT nFlags, CPoint point)  

{  

    if(m_bIsLButtonDown)  

    {  

        CRect   rectPic;  

        CWnd*   pPic    = GetDlgItem(IDC_STATIC_DEMO);  

        ScreenToClient(&m_ptMove);  

        pPic->GetWindowRect(rectPic);  

        pPic->MoveWindow(m_ptMove.x-m_ptOffset.x, m_ptMove.y-m_ptOffset.y, rectPic.Width(), rectPic.Height());  

        m_bIsLButtonDown    = FALSE;  

        CImageList::DragLeave(this);  

        CImageList::EndDrag();  

        ReleaseCapture();  

        pPic->Invalidate();  

    }  

    CDialog::OnLButtonUp(nFlags, point);  

}

到此拖动效果就实现了,最后再说一点,例子中有关于鼠标捕获和释放的操作,目的是为了当鼠标离开窗口范围仍然可以响应,而且针对窗口有可能被其它程序抢夺焦点的情况,例程中专门处理了OnActivate消息,具体实现可以参考示例源码,就不在这里赘述了。应该说通过CImageList实现拖动操作是十分方便的,而且效果也很好。据说VS2008下的CImageList还支持PNG,那样就可以做出更炫的拖动效果了。
       再说一个题外话,本文是通过Windows Live Writer编辑并发布的,这个工具是论坛里的muzizongheng推荐的,确实很好用,在此也对muzizongheng表达一下谢意。本文只是对拖动效果的一个简单实现,如果有更好的方法还望大家赐教,在此谢过

void CDragDemoDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
CDialog::OnActivate(nState, pWndOther, bMinimized); if(nState==WA_INACTIVE)//当失去焦点后,
{
m_bIsLButtonDown = FALSE; CImageList::DragLeave(this);
CImageList::EndDrag();
ReleaseCapture();
}
}

vc++ mfc中拖动效果的实现 借助于CImageList的更多相关文章

  1. VC/MFC中计算程序运行时间

    转自原文VC/MFC中计算程序运行时间 说明,这四种方法也分别代表了类似的实现,在MFC中,所可以从哪些类集合去考虑. 方法一 利用GetTickCount函数(ms) CString str; lo ...

  2. VC++ MFC中如何将应用程序的配置信息保存到注册表中(二)

    在上一篇中介绍了几个写入注册表数据和读取注册表数据的接口,并介绍了使用方法. 这一片教你如何使得你的应用程序在下次打开时保持上一次关闭前的状态. 在上一篇添加的代码的基础上,要添加WM_CLOSE消息 ...

  3. 在VC/MFC中嵌入Google地图——图文并茂

    近期须要实验室须要将在无人机地面站中嵌入地图,在网上找了非常多资料,最终有些眉目了, 首先.做这个须要用到的知识有.MFC控件.MFC类库.JavaScript脚本语言.Google API.Goog ...

  4. VC++/MFC中调用CHM帮助文档的方法

    转载:http://blog.csdn.net/hediping9811/article/details/23341387 (1)用Word编辑好帮助文档,并保存为网页格式,如mhtml格式. (2) ...

  5. VC/MFC中的CComboBox控件使用详解

    CComboBox控件详解 CComboBox控件又称作组合框控件,其有三种形态可供选择,1.简单组合框(Simple)2.下拉组合框(Drop-down)3.下拉列表式组合框(Drop-down l ...

  6. [转]关于VC++ MFC中的空闲Idle处理机制!

    关键词: 先根据空闲标志以及消息队列是否为空这两个条件判断当前线程是否处于空闲状态(这个“空闲”的含义同操作系统的含义不同,是MFC自己所谓的“空闲”),如果是,就调用CWinThread::OnId ...

  7. VC/MFC中为程序定义全局快捷键

    VC 2010-05-01 18:01:34 阅读287 评论0 字号:大中小 订阅 1.注册快捷键 在初始化函数,如OnInitDialog() 注册快捷键,代码如下: #define HotKey ...

  8. VC/MFC中通过CWebPage类调用javascript函数(给js函数传参,并取得返回值)

    转自:http://www.cnblogs.com/javaexam2/archive/2012/07/14/2632959.html ①需要一个别人写好的类CWebPage,将其对于的两个文件Web ...

  9. MFC中的一些视图

    本章主要介绍MFC中主要的视图类,这些继承自Cview类. 继承关系如上图所示. 滚动视图 CscrollView给Cview添加了基本的滚动功能,它包含WM_VSCROLL和WM_HSCROLL消息 ...

随机推荐

  1. 深入详解DataTable

    在学习DataTable知识之前,我们有必要了解下ADO.NET.以下摘自MSDN: ADO.NET 对 Microsoft SQL Server 和 XML 等数据源以及通过 OLE DB 和 XM ...

  2. NSOperationQueue与GCD的使用原则和场景

    首先,我们要明确NSOperationQueue与GCD之间的关系: NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象. 其次,我们要区别两者的不同: GCD仅仅支持FIFO队列 ...

  3. 关于MySql 关键字与字段名冲突 的问题

    我在用mysql创建数据表时,其中一个表怎么创建都提示失败,最终我把语句翻来覆去折腾了许多遍之后发现原来我的一个字段值的名称为order的字段出了问题,把它去了就好了,最后结论就是设置字段值名称时不要 ...

  4. java编译后字节码解析

    java编译后字节码解析 参考网摘: https://my.oschina.net/indestiny/blog/194260

  5. spring 和 spring mvc

    spring3 http://jinnianshilongnian.iteye.com/blog/1482071 spring mvc http://jinnianshilongnian.iteye. ...

  6. sql里面的分页

    SELECT TOP 5 * FROM hos_house WHERE HMID NOT IN( SELECT TOP 5 HMID FROM hos_house WHERE PRICE>250 ...

  7. http://paulgraham.com/arcfaq.html

    Why not use some other delimiter than parentheses?为什么不使用一些其他的分隔符比括号?We tried various possibilities. ...

  8. IIS启用兼容模式设置(win2k3—Win7)

    点击添加按钮(上图),弹出下面的对话框(下图).在自定义HTTP头名处输入: X-UA-compatible 在自定义HTTP头值处输入: IE=EmulateIE7 (输入时注意不要留下空格)输入完 ...

  9. OpenGL学习笔记2——顶点数组

    #pragma comment(lib,"glut32.lib") #pragma comment(lib,"glut.lib") #pragma commen ...

  10. 5、IMS网元

    1.会话管理和路由类(call session control function,呼叫会话控制功能) (1)代理呼叫会话控制功能P-CSCF 是IMS中与用户的第一个连接点,提供”代理(proxy)“ ...