新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → VC程序中树型控件节点拖动的完美实现 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 5735 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: VC程序中树型控件节点拖动的完美实现 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 VC程序中树型控件节点拖动的完美实现

    Visual C++中提供的MFC类CtreeCtrl(树型控件)用来显示具有一定层次结构的数据项时方便、直观,所以它已经被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是树型控件,我们在编程中也会经常用到这个控件,但是这个控件也有缺陷,那就是它并不直接支持拖动节点等高级特性,这使得程序员在编程时使用它受到了很大限制,同时又给软件用户带来了一些不便。为此,本实例通过从 CTreeCtrl 中派生了一个类 CXTreeCtrl ,实现树型控件中节点的拖动。这个类具有如下的功能:⑴ 基本项目条拖动的实现;⑵ 处理项目条的无意拖动;⑶ 能处理项目条拖动过程中的滚动问题;⑷ 拖动过程中节点会智能展开。程序编译运行后的效果如图所示:

    按此在新窗口浏览图片
    图一:树型控件节点拖动示例

      一、实现方法

      我们针对上述自定义类的实现功能,介绍实现思路和方法。

      (1)基本项目条拖动的实现

      当我们要拖动树型控件的一个项目条时,树型控件会给它的父窗口发送一个TVN_BEGINDRAG通知消息,所以可以在此消息的响应函数中,调用 CTreeCtrl ::CreateDragImage ()函数创建表示当前项目条正处在拖动操作中的图象,该函数创建的图象由项目条的图象和标签文本组成。创建了拖动图象后,调用CImageList::BeginDrag()函数指定拖动图象的热点位置,然后调用CImageList::DragEnter()函数显示拖动图象。接下来处理 WM_MOUSEMOVE 消息用于更新拖动图象,我们想让移动中的图象经过某些项目时高亮度显示,这可以调用 CTreeCtrl ::SelectDropTarget() 来实现。在调用 SelectDropTarget()函数之前,需要先调用CImageList::DragShowNolock ( false )函数来隐藏图象列表,然后再调用CImageList::DragShowNolock ( true ) 函数来恢复图象列表的显示,这样就不会在拖动过程中留下难看的轨迹。最后我们处理 WM_LBUTTONUP 消息用于完成拖动操作,在该消息响应函数中,我们需要完成结束拖动图象的显示、删除拖动图象、释放鼠标、节点的拷贝/删除等操作。在节点的拷贝/删除操作中,如果是父节点拖到子节点上,我们可以先将父节点拷到根结点下的临时节点中,再从临时结点处拷到子节点,然后将根结点下的临时节点删除,这样做的目的是防止产生异常。

      (2)处理项目条的无意拖动

    如果在鼠标按下时不小心移动了鼠标,这时系统就认为产生了一个移动操作,这就产生了误操作。解决这个问题的方法是设置时间延迟,也就是说当用户按下鼠标后必须在原位置停留一段时间,才能激活拖动操作。

      (3)处理拖动过程中的滚动问题

      当我们拖动树型控件的项目条时,如果目的节点不可见,则需要拖动滚动条或收拢其它一些节点以使得目的节点显示出来,无疑,这会给我们带来很大的不便。为此就要给树型控件添加自动滚动支持。首先设置一个定时器,在 WM_TIMER 消息中检测鼠标的位置,如果靠近树型控件的下边缘,则使得控件向下滚动。靠近上边缘则向上滚动。滚动速度根据鼠标的位置确定。

      (4)拖动过程中节点的智能展开

      为了实现在拖动过程中鼠标停留在某个节点上一段时间后,该节点会自动展开的功能。设置一个定时器,当鼠标在拖动过程中停止在某个节点上时,定时器被启动,再设置一变量保存当前的鼠标位置。

      二、编程步骤

      1、 新建一对话框工程DragTree,编辑资源,在对话框中加入一树型控件IDC_TREE ,属性设置为:Has Buttons、Has Lines、Lines at root、Edit Labels、Border;

      2、 使用Class Wizard给该控件添加一个成员变量 m_wndTree ,在代码部分将该控件的类型修改为CXTreeCtrl。

      3、 在对话框的OnInitDialog()函数中添加代码,初始化树型控件的项目条;

      4、 制作一个图像资源(ID为IDB_TREEIMAGE),其中包含两个小图标,用来作为树型控件项目条的显示图标;

      5、 添加代码,编译运行程序。


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/12/4 15:01:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 
    三、程序代码

    // XTreeCtrl.h : header file
    #if !defined(AFX_XTREECTRL_H__3EF12526_EF66_4FD9_A572_59476441D79A__INCLUDED_)
    #define AFX_XTREECTRL_H__3EF12526_EF66_4FD9_A572_59476441D79A__INCLUDED_
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    class CXTreeCtrl : public CTreeCtrl
    {
     // Construction
     public:
      CXTreeCtrl();
      // Attributes
     public:
      // Operations
     public:
      // Overrides
      // ClassWizard generated virtual function overrides
      //{{AFX_VIRTUAL(CXTreeCtrl)
      //}}AFX_VIRTUAL
      // Implementation
     public:
      virtual ~CXTreeCtrl();
      // Generated message map functions
     protected:
      UINT m_TimerTicks; //处理滚动的定时器所经过的时间
      UINT m_nScrollTimerID; //处理滚动的定时器
      CPoint m_HoverPoint; //鼠标位置
      UINT m_nHoverTimerID; //鼠标敏感定时器
      DWORD m_dwDragStart; //按下鼠标左键那一刻的时间
      BOOL m_bDragging; //标识是否正在拖动过程中
      CImageList* m_pDragImage; //拖动时显示的图象列表
      HTREEITEM m_hItemDragS; //被拖动的标签
      HTREEITEM m_hItemDragD; //接受拖动的标签
      //{{AFX_MSG(CXTreeCtrl)
       afx_msg void OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult);
       afx_msg void OnMouseMove(UINT nFlags, CPoint point);
       afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
       afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
       afx_msg void OnTimer(UINT nIDEvent);
      //}}AFX_MSG
      DECLARE_MESSAGE_MAP()
     private:
      HTREEITEM CopyBranch(HTREEITEM htiBranch,HTREEITEM htiNewParent,HTREEITEM htiAfter);
      HTREEITEM CopyItem(HTREEITEM hItem,HTREEITEM htiNewParent,HTREEITEM htiAfter);
    };
    #endif
    //////////////////////////////////////////////////////////// CXTreeCtrl
    #include "stdafx.h"
    #include "DragTree.h"
    #include "XTreeCtrl.h"
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    #define DRAG_DELAY 60
    CXTreeCtrl::CXTreeCtrl()
    {
     m_bDragging = false;
    }
    CXTreeCtrl::~CXTreeCtrl()
    {}

    BEGIN_MESSAGE_MAP(CXTreeCtrl, CTreeCtrl)
    //{{AFX_MSG_MAP(CXTreeCtrl)
     ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)
     ON_WM_MOUSEMOVE()
     ON_WM_LBUTTONUP()
     ON_WM_LBUTTONDOWN()
     ON_WM_TIMER()
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    void CXTreeCtrl::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
    {
     NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
     *pResult = 0;
     //如果是无意拖曳,则放弃操作
     if( (GetTickCount() - m_dwDragStart) < DRAG_DELAY )
      return;
     m_hItemDragS = pNMTreeView->itemNew.hItem;
     m_hItemDragD = NULL;
     //得到用于拖动时显示的图象列表
     m_pDragImage = CreateDragImage( m_hItemDragS );
     if( !m_pDragImage )
      return;
     m_bDragging = true;
     m_pDragImage->BeginDrag ( 0,CPoint(8,8) );
     CPoint pt = pNMTreeView->ptDrag;
     ClientToScreen( &pt );
     m_pDragImage->DragEnter ( this,pt ); //"this"将拖曳动作限制在该窗口
     SetCapture();
     m_nScrollTimerID = SetTimer( 2,40,NULL );
    }

    void CXTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
    {
     HTREEITEM hItem;
     UINT flags;
     //检测鼠标敏感定时器是否存在,如果存在则删除,删除后再定时
     if( m_nHoverTimerID )
     {
      KillTimer( m_nHoverTimerID );
      m_nHoverTimerID = 0;
     }
     m_nHoverTimerID = SetTimer( 1,800,NULL ); //定时为 0.8 秒则自动展开
     m_HoverPoint = point;
     if( m_bDragging )
     {
      CPoint pt = point;
      CImageList::DragMove( pt );
      //鼠标经过时高亮显示
      CImageList::DragShowNolock( false ); //避免鼠标经过时留下难看的痕迹
      if( (hItem = HitTest(point,&flags)) != NULL )
      {
       SelectDropTarget( hItem );
       m_hItemDragD = hItem;
      }
      CImageList::DragShowNolock( true );
      //当条目被拖曳到左边缘时,将条目放在根下
      CRect rect;
      GetClientRect( &rect );
      if( point.x < rect.left + 20 )
       m_hItemDragD = NULL;
     }
     CTreeCtrl::OnMouseMove(nFlags, point);
    }

    void CXTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
    {
     CTreeCtrl::OnLButtonUp(nFlags, point);
     if( m_bDragging )
     {
      m_bDragging = FALSE;
      CImageList::DragLeave( this );
      CImageList::EndDrag();
      ReleaseCapture();
      delete m_pDragImage;
      SelectDropTarget( NULL );
      if( m_hItemDragS == m_hItemDragD )
      {
       KillTimer( m_nScrollTimerID );
       return;
      }
      Expand( m_hItemDragD,TVE_EXPAND );
      HTREEITEM htiParent = m_hItemDragD;
      while( (htiParent = GetParentItem(htiParent)) != NULL )
      {
       if( htiParent == m_hItemDragS )
       {
        HTREEITEM htiNewTemp = CopyBranch( m_hItemDragS,NULL,TVI_LAST );
        HTREEITEM htiNew = CopyBranch( htiNewTemp,m_hItemDragD,TVI_LAST );
        DeleteItem( htiNewTemp );
        SelectItem( htiNew );
        KillTimer( m_nScrollTimerID );
        return;
       }
      }
      HTREEITEM htiNew = CopyBranch( m_hItemDragS,m_hItemDragD,TVI_LAST );
      DeleteItem( m_hItemDragS );
      SelectItem( htiNew );
      KillTimer( m_nScrollTimerID );
     }
    }

    HTREEITEM CXTreeCtrl::CopyItem(HTREEITEM hItem, HTREEITEM htiNewParent, HTREEITEM htiAfter) //拷贝条目
    {
     TV_INSERTSTRUCT tvstruct;
     HTREEITEM hNewItem;
     CString sText;
     //得到源条目的信息
     tvstruct.item.hItem = hItem;
     tvstruct.item.mask=TVIF_CHILDREN|TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
     GetItem( &tvstruct.item );
     sText = GetItemText( hItem );
     tvstruct.item.cchTextMax = sText.GetLength ();
     tvstruct.item.pszText = sText.LockBuffer ();
     //将条目插入到合适的位置
     tvstruct.hParent = htiNewParent;
     tvstruct.hInsertAfter = htiAfter;
     tvstruct.item.mask = TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_TEXT;
     hNewItem = InsertItem( &tvstruct );
     sText.ReleaseBuffer ();
     //限制拷贝条目数据和条目状态
     SetItemData( hNewItem,GetItemData(hItem) );
     SetItemState( hNewItem,GetItemState(hItem,TVIS_STATEIMAGEMASK),TVIS_STATEIMAGEMASK);
     return hNewItem;
    }

    HTREEITEM CXTreeCtrl::CopyBranch(HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter) //拷贝分支
    {
     HTREEITEM hChild;
     HTREEITEM hNewItem = CopyItem( htiBranch,htiNewParent,htiAfter );
     hChild = GetChildItem( htiBranch );
     while( hChild != NULL )
     {
      CopyBranch( hChild,hNewItem,htiAfter );
      hChild = GetNextSiblingItem( hChild );
     }
     return hNewItem;
    }

    void CXTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point) //处理无意拖曳
    {
     m_dwDragStart = GetTickCount();
     CTreeCtrl::OnLButtonDown(nFlags, point);
    }

    void CXTreeCtrl::OnTimer(UINT nIDEvent)
    {
     //鼠标敏感节点
     if( nIDEvent == m_nHoverTimerID )
     {
      KillTimer( m_nHoverTimerID );
      m_nHoverTimerID = 0;
      HTREEITEM trItem = 0;
      UINT uFlag = 0;
      trItem = HitTest( m_HoverPoint,&uFlag );
      if( trItem && m_bDragging )
      {
       SelectItem( trItem );
       Expand( trItem,TVE_EXPAND );
      }
     }
     //处理拖曳过程中的滚动问题
     else if( nIDEvent == m_nScrollTimerID )
     {
      m_TimerTicks++;
      CPoint pt;
      GetCursorPos( &pt );
      CRect rect;
      GetClientRect( &rect );
      ClientToScreen( &rect );
      HTREEITEM hItem = GetFirstVisibleItem();
      if( pt.y < rect.top +10 )
      {
       //向上滚动
       int slowscroll = 6 - (rect.top + 10 - pt.y )/20;
       if( 0 == (m_TimerTicks % ((slowscroll > 0) ? slowscroll : 1)) )
       {
        CImageList::DragShowNolock ( false );
        SendMessage( WM_VSCROLL,SB_LINEUP );
        SelectDropTarget( hItem );
        m_hItemDragD = hItem;
        CImageList::DragShowNolock ( true );
       }
      }
      else if( pt.y > rect.bottom - 10 )
      {
       //向下滚动
       int slowscroll = 6 - (pt.y - rect.bottom + 10)/20;
       if( 0 == (m_TimerTicks % ((slowscroll > 0) ? slowscroll : 1)) )
       {
        CImageList::DragShowNolock ( false );
        SendMessage( WM_VSCROLL,SB_LINEDOWN );
        int nCount = GetVisibleCount();
        for( int i=0 ; i<nCount-1 ; i++ )
         hItem = GetNextVisibleItem( hItem );
         if( hItem )
          SelectDropTarget( hItem );
         m_hItemDragD = hItem;
         CImageList::DragShowNolock ( true );
       }
      }
     }
     else
      CTreeCtrl::OnTimer(nIDEvent);
    }

    ////////////////////////////////////////////////////////////
    BOOL CDragTreeDlg::OnInitDialog()
    {
     CDialog::OnInitDialog();
     …………………….//此处代码省略
     // TODO: Add extra initialization here
     m_image.Create ( IDB_TREEIMAGE,16,1,RGB(255,255,255) );
     m_wndTree.SetImageList ( &m_image,TVSIL_NORMAL );
     HTREEITEM hti1 = m_wndTree.InsertItem ( _T("唐詩"),0,1 );
     HTREEITEM hti2 = m_wndTree.InsertItem ( _T("宋詞"),0,1 );
     HTREEITEM hti3 = m_wndTree.InsertItem ( _T("元曲"),0,1 );
     HTREEITEM hti4 = m_wndTree.InsertItem ( _T("李白"),0,1,hti1 );
     m_wndTree.InsertItem ( _T("靜夜思(床前明月光)"),0,1,hti4 );
     m_wndTree.InsertItem ( _T("將進酒(君不見黃河之水天上來)"),0,1,hti4 );
     m_wndTree.InsertItem ( _T("望廬山瀑布(日照香爐生紫煙)"),0,1,hti4 );
     m_wndTree.InsertItem ( _T("蜀道難(噫吁戲,危乎高哉)"),0,1,hti4 );
     HTREEITEM hti5 = m_wndTree.InsertItem ( _T("杜甫"),0,1,hti1 );
     m_wndTree.InsertItem ( _T("蜀相(丞相祠堂何處尋)"),0,1,hti5 );
     m_wndTree.InsertItem ( _T("春望(國破山河在)"),0,1,hti5 );
     m_wndTree.InsertItem ( _T("茅屋為秋風所破歌(八月秋高風怒號)"),0,1,hti5 );
     HTREEITEM hti6 = m_wndTree.InsertItem ( _T("白居易"),0,1,hti1 );
     m_wndTree.InsertItem ( _T("長恨歌(漢皇重色思傾國)"),0,1,hti6 );
     m_wndTree.InsertItem ( _T("琵琶行並序(潯陽江頭夜送客)"),0,1,hti6 );
     m_wndTree.InsertItem ( _T("李清照"),0,1,hti2 );
     m_wndTree.InsertItem ( _T("柳永"),0,1,hti2 );
     return TRUE; // return TRUE unless you set the focus to a control
    }

      四、小结

      本实例介绍了树型控件如何实现项目条的拖动,它的实现思路主要是利用了定时器,不同的定时器来实现不同的功能,另外,读者朋友在学习中要主要掌握CimageList类的几个成员函数的用法。

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/12/4 15:02:00
     
     buxingfeng 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:3
      积分:64
      门派:XML.ORG.CN
      注册:2006/12/4

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给buxingfeng发送一个短消息 把buxingfeng加入好友 查看buxingfeng的个人资料 搜索buxingfeng在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看buxingfeng的博客3
    发贴心情 
    版主同志,能帮偶解决下那个菜鸟问题么?非常感谢。。。
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/12/4 15:40:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/26 14:17:40

    本主题贴数3,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    74.219ms