VC技巧 - 常用控制之二 可以使用Drag&Drop的CTreeView CTreeViewExt是MFC使用者可重用的类,该类由CTreeView派生,可以用在文档- 视结构的应用程序中,并且支持Drag&Drop。 使用者所需要做的仅仅是将文件加到AppWizard产生的工程中,(CTreeView为基 类)并且将CTreeView替换为CTreeViewExt。并重载三个Virtual Function。 (WenYY:这很简单吧,下面是源代码,我会在必要的地方加上注释,但由于原作 者并未加,所以如果有出入请原谅,作者定义了三处虚拟函数,其作用是让使 用者重载后,加入自己的判断条件和结果处理的功能,很巧妙的思想: CopyItemProperties IsItemCanBeDroppedOn(HTREEITEM hSource, HTREEITEM hTarget); BOOL ItemCanBeDragged(HTREEITEM hItem);//检查是否可以对该ITEM实施Drag&Drop ) Header file #if !defined(AFX_TREEVIEWEXT_H__99D8F6F8_79F0_11D1_8DC6_0000E8125FE5__INCLUDED_) #define AFX_TREEVIEWEXT_H__99D8F6F8_79F0_11D1_8DC6_0000E8125FE5__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 // TreeViewExt.h : header file // ///////////////////////////////////////// // CTreeViewExt view class CTreeViewExt : public CTreeView { protected: CTreeViewExt(); // protected constructor used by dynamic creation DECLARE_DYNCREATE(CTreeViewExt) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CTreeViewExt) protected: virtual void OnDraw(CDC* pDC); // overridden to draw this view //}}AFX_VIRTUAL // Implementation protected: HTREEITEM m_hDraggedItem; BOOL m_bDraggingNow; //标记 CImageList *m_pDragImageList; virtual ~CTreeViewExt(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif // Generated message map functions protected: virtual void CopyItemProperties(HTREEITEM hNewItem, HTREEITEM hDraggedItem); virtual BOOL IsItemCanBeDroppedOn(HTREEITEM hSource, HTREEITEM hTarget); virtual BOOL ItemCanBeDragged(HTREEITEM hItem);//检查是否可以对该ITEM实施Drag&Drop //{{AFX_MSG(CTreeViewExt) 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 DECLARE_MESSAGE_MAP() }; ///////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Developer Studio will insert additional declarations immediately before the previous line. #endif // !defined(AFX_TREEVIEWEXT_H__99D8F6F8_79F0_11D1_8DC6_0000E8125FE5__INCLUDED_) Implementation file // TreeViewExt.cpp : implementation file // #include "stdafx.h" #include "TreeViewExt.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////// // CTreeViewExt IMPLEMENT_DYNCREATE(CTreeViewExt, CTreeView) CTreeViewExt::CTreeViewExt() { m_bDraggingNow = FALSE; m_hDraggedItem = NULL; m_pDragImageList = NULL; } CTreeViewExt::~CTreeViewExt() { } BEGIN_MESSAGE_MAP(CTreeViewExt, CTreeView) //{{AFX_MSG_MAP(CTreeViewExt) ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag) ON_WM_MOUSEMOVE() ON_WM_LBUTTONUP() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////// // CTreeViewExt drawing void CTreeViewExt::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); // TODO: add draw code here } ///////////////////////////////////////// // CTreeViewExt diagnostics #ifdef _DEBUG void CTreeViewExt::AssertValid() const { CTreeView::AssertValid(); } void CTreeViewExt::Dump(CDumpContext& dc) const { CTreeView::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////// // CTreeViewExt message handlers void CTreeViewExt::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; if (!m_bDraggingNow) {//先检查是否正在过程中 if (ItemCanBeDragged(pNMTreeView->itemNew.hItem)) {//查询条件,是否允许Drag&Drop CTreeCtrl& tree = GetTreeCtrl(); tree.SetCapture(); m_bDraggingNow = TRUE; m_hDraggedItem = pNMTreeView->itemNew.hItem;//保存变量 tree.Select(m_hDraggedItem, TVGN_CARET); m_pDragImageList = tree.CreateDragImage(m_hDraggedItem); m_pDragImageList->DragEnter(&tree, pNMTreeView->ptDrag); m_pDragImageList->BeginDrag(0, CPoint(0, 0)); } } *pResult = 0; } void CTreeViewExt::OnMouseMove(UINT nFlags, CPoint point) { if (m_bDraggingNow) { CTreeCtrl& tree = GetTreeCtrl(); m_pDragImageList->DragEnter(&tree, point); m_pDragImageList->DragMove(point); } CTreeView::OnMouseMove(nFlags, point); } void CTreeViewExt::OnLButtonUp(UINT nFlags, CPoint point) { if (m_bDraggingNow) { ReleaseCapture(); m_bDraggingNow = FALSE; m_pDragImageList->EndDrag(); delete m_pDragImageList; m_pDragImageList = NULL; CTreeCtrl& tree = GetTreeCtrl(); UINT flags; HTREEITEM hTargetItem = tree.HitTest(point, &flags);//得到目标 if (hTargetItem != NULL && IsItemCanBeDroppedOn(m_hDraggedItem, hTargetItem)) {//查询是否可以成功完成,条件是开始时选中的Item和结束使选中的ITEM是否满足你的条件 HTREEITEM hNewItem = tree.InsertItem("Untitled", hTargetItem); CopyItemProperties(hNewItem, m_hDraggedItem);//进行处理 if (nFlags != MK_CONTROL) tree.DeleteItem(m_hDraggedItem); } m_hDraggedItem = NULL; } CTreeView::OnLButtonUp(nFlags, point); } BOOL CTreeViewExt::ItemCanBeDragged(HTREEITEM hItem) {//作用为决定现在是否能开始,可以作为一种运行时的选项 return FALSE; } BOOL CTreeViewExt::IsItemCanBeDroppedOn(HTREEITEM hSource, HTREEITEM hTarget) {//决定现在结束条件是否正常,如不正常则放弃这次操作 return FALSE; } void CTreeViewExt::CopyItemProperties(HTREEITEM hNewItem, HTREEITEM hDraggedItem) {//按照你的需要对两个ITEM进行处理。 } 有关属性对话框(property sheet )的几个提示 闻怡洋 下面的所有例子,都假定你从CPropertySheet中派生了新类。 1、隐藏APPLY按钮 使用 PSH_NOAPPLYNOW 标志. propsheet.m_psh.dwFlags |= PSH_NOAPPLYNOW; 2、增加新的子窗口 使用成员变量。CEdit m_edit. BOOL CMyPropSheet::OnInitDialog() { BOOL bResult = CPropertySheet::OnInitDialog(); CRect rectWnd; GetWindowRect(rectWnd); SetWindowPos(NULL, 0, 0, rectWnd.Width() + 100, rectWnd.Height(), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); m_edit.CreateEx( WS_EX_CLIENTEDGE, _T("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, rectWnd.Width(), 20, 80, 24, m_hWnd, 0, 0 ); m_edit.SetFont( GetFont() ); CenterWindow(); return bResult; } 3、改变页片上的字体 在 OnInitDialog() 中: // m_fontEdit is a member variable // Create a bold font m_fontEdit.CreateFont( -8, 0, 0, 0, 700, 0, 0, 0, 1, 0, 0, 0, 0, _T("MS Sans Serif") ); GetTabControl()->SetFont( &m_fontEdit ); 4、使用Image m_imageTab为成员变量。 BOOL CMyPropSheet::OnInitDialog() { BOOL bResult = CPropertySheet::OnInitDialog(); m_imageTab.Create( IDB_TABIMAGES, 13, 1, RGB(255,255,255) ); CTabCtrl *pTab = GetTabControl(); pTab->SetImageList( &m_imageTab ); TC_ITEM tcItem; tcItem.mask = TCIF_IMAGE; for( int i = 0; i <3; i++ ) { tcItem.iImage = i; pTab->SetItem( i, &tcItem ); } return bResult; }   为TreeCtrl中的项增加ToolTip 文洋译 要点:通过OnToolHitTest来增加ToolTip,CTreeCtrl::HitTest(...)的使用,TTN_NEEDTEXT消息的处理。 定义一个新类,有CTreeCtrl派生 Step 1:打开ToolTip 功能 void CTreeCtrlX::PreSubclassWindow() { CTreeCtrl::PreSubclassWindow(); EnableToolTips(TRUE); } Step 2: OnToolHitTest() 的重载 首先通过HitTest决定是否增加ToolTip,如果需要则返回非零。 在本例中ToolTip使用了LPSTR_TEXTCALLBACK,而没有立即设定显示字符串。 在本例只在鼠标指向每项的图片时才显示ToolTip int CTreeCtrlX::OnToolHitTest(CPoint point, TOOLINFO * pTI) const { RECT rect; UINT nFlags; HTREEITEM hitem = HitTest( point, &nFlags ); if( nFlags & TVHT_ONITEMICON ) { CImageList *pImg = GetImageList( TVSIL_NORMAL ); IMAGEINFO imageinfo; pImg->GetImageInfo( 0, &imageinfo ); GetItemRect( hitem, &rect, TRUE ); rect.right = rect.left - 2; rect.left -= (imageinfo.rcImage.right + 2); pTI->hwnd = m_hWnd; pTI->uId = (UINT)hitem; pTI->lpszText = LPSTR_TEXTCALLBACK; pTI->rect = rect; return pTI->uId; } else if( nFlags & TVHT_ONITEMSTATEICON ) { CImageList *pImg = GetImageList( TVSIL_NORMAL ); IMAGEINFO imageinfo; pImg->GetImageInfo( 0, &imageinfo ); GetItemRect( hitem, &rect, TRUE ); rect.right = rect.left - (imageinfo.rcImage.right + 2); pImg = GetImageList( TVSIL_STATE ); rect.left = rect.right - imageinfo.rcImage.right ; pTI->hwnd = m_hWnd; pTI->uId = (UINT)hitem; pTI->lpszText = LPSTR_TEXTCALLBACK; pTI->rect = rect; // return value should be different from that used for item icon return pTI->uId*2; } return -1; } Step 3: ?? TTN_NEEDTEXT 由于在增加ToolTip是使用用了 LPSTR_TEXTCALLBACK,因此ToolTip在显示 时会发送该消息来得到显示字符串。 BEGIN_MESSAGE_MAP(CTreeCtrlX, CTreeCtrl) //{{AFX_MSG_MAP(CTreeCtrlX) : : //}}AFX_MSG_MAP ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText) END_MESSAGE_MAP() protected: //{{AFX_MSG(CTreeCtrlX) : : //}}AFX_MSG afx_msg BOOL OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult ); DECLARE_MESSAGE_MAP() BOOL CTreeCtrlX::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult ) { // need to handle both ANSI and UNICODE versions of the message TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; CString strTipText; UINT nID = pNMHDR->idFrom; // Do not process the message from built in tooltip if( nID == (UINT)m_hWnd && (( pNMHDR->code == TTN_NEEDTEXTA && pTTTA->uFlags & TTF_IDISHWND ) || ( pNMHDR->code == TTN_NEEDTEXTW && pTTTW->uFlags & TTF_IDISHWND ) ) ) return FALSE; // Get the mouse position const MSG* pMessage; CPoint pt; pMessage = GetCurrentMessage(); ASSERT ( pMessage ); pt = pMessage->pt; ScreenToClient( &pt ); UINT nFlags; HTREEITEM hitem = HitTest( pt, &nFlags ); if( nFlags & TVHT_ONITEMICON ) { int nImage, nSelImage; GetItemImage( (HTREEITEM ) nID, nImage, nSelImage ); strTipText.Format( "Image : %d", nImage ); } else { strTipText.Format( "State : %d", GetItemState( (HTREEITEM ) nID, TVIS_STATEIMAGEMASK ) ); } #ifndef _UNICODE if (pNMHDR->code == TTN_NEEDTEXTA) lstrcpyn(pTTTA->szText, strTipText, 80); else _mbstowcsz(pTTTW->szText, strTipText, 80); #else if (pNMHDR->code == TTN_NEEDTEXTA) _wcstombsz(pTTTA->szText, strTipText, 80); else lstrcpyn(pTTTW->szText, strTipText, 80); #endif *pResult = 0; return TRUE; // message was handled }