程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> VC程序中樹型控件節點拖動的完美實現

VC程序中樹型控件節點拖動的完美實現

編輯:關於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、添加代碼,編譯運行程序。

三、程序代碼

// 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類 的幾個成員函數的用法。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved