CTreeList 拖拽功能实现

转载地址:http://tech.ddvip.com/2008-11/122662837992492.html

 

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、 添加代码,编译运行程序。

  三、程序代码

双击代码全选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// 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类的几个成员函数的用法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值