treedragdrophelper.cpp

来自「管理项目进度工具的原代码」· C++ 代码 · 共 957 行 · 第 1/2 页

CPP
957
字号
// TreeDragDropHelper.cpp: implementation of the CTreeDragDropHelper class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "TreeDragDropHelper.h"
#include "holdredraw.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////

const int MAXIMAGEWIDTH = 200;

class CDragDropTreeData : public CDragDropData
{
public:
	CDragDropTreeData(const CTreeSelectionHelper& selection) :
		m_tree(selection.TreeCtrl()), m_ptDrawOffset(0)
	{
		selection.CopySelection(m_selection);
	}

protected:
	virtual CSize OnGetDragSize(CDC& /*dc*/)
	{
		CRect rDrag(0, 0, 0, 0), rItem;

		// iterate the current selection accumulating their rects
		POSITION pos = m_selection.GetHeadPosition();

		while (pos)
		{
			HTREEITEM hti = m_selection.GetNext(pos);

			if (m_tree.GetItemRect(hti, rItem, TRUE))
				rDrag |= rItem;
		}

		// save this for when we draw
		m_ptDrawOffset = rDrag.TopLeft();

		rDrag.right = min(rDrag.right, rDrag.left + MAXIMAGEWIDTH);

		return rDrag.Size();
	}
	
	virtual void OnDrawData(CDC& dc, CRect& rc, COLORREF& crMask)
	{
		crMask = 1;
		dc.FillSolidRect(rc, crMask);

		dc.SetBkMode(OPAQUE);
		dc.SetBkColor(::GetSysColor(COLOR_HIGHLIGHT));
		dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
	
		// iterate the current selection accumulating their rects
		POSITION pos = m_selection.GetHeadPosition();

		while (pos)
		{
			HTREEITEM hti = m_selection.GetNext(pos);
			CRect rItem;

			if (m_tree.GetItemRect(hti, rItem, TRUE))
			{
				rItem -= m_ptDrawOffset;
				rItem += rc.TopLeft();
				rItem.IntersectRect(rc, rItem);

				dc.FillSolidRect(rItem, ::GetSysColor(COLOR_HIGHLIGHT));
				rItem.DeflateRect(2, 1);
				dc.DrawText(m_tree.GetItemText(hti), rItem, DT_LEFT | DT_END_ELLIPSIS);
			}
		}
	}
	
	virtual void* OnGetData() { return NULL; }

protected:
	CHTIList m_selection;
	const CTreeCtrl& m_tree;
	CPoint m_ptDrawOffset;
};

//////////////////////////////////////////////////////////////////////

// helper for copying
struct TDDHCOPY
{
	TDDHCOPY() { hti = NULL; dwItemData = 0; }
	TDDHCOPY(const TDDHCOPY& htc) { *this = htc; }
	
	HTREEITEM hti;
	DWORD dwItemData;
	CArray<TDDHCOPY, TDDHCOPY&> childItems;
	
	const TDDHCOPY& operator=(const TDDHCOPY& htc)
	{
		hti = htc.hti;
		dwItemData = htc.dwItemData;
		childItems.Copy(htc.childItems);
		
		return *this;
	}
	
	void Clear()
	{
		dwItemData = 0;
		hti = NULL;
		childItems.RemoveAll();
	}
	
	BOOL IsValid() const { return (NULL != hti); }
};

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTreeDragDropHelper* CTreeDragDropHelper::s_pTDDH = NULL;

enum
{
	DELAY_INTERVAL = 150, 
	SCROLL_INTERVAL = 100, 
	SCROLL_MARGIN = 20, 
	EXPAND_INTERVAL = 500,
};

const CPoint OUTERSPACE(-10000, -10000);

CTreeDragDropHelper::CTreeDragDropHelper(CTreeSelectionHelper& selection, 
	 CTreeCtrl& tree, BOOL bTreeUsesTextCallback, BOOL bTreeUsesImageCallback)
	: m_selection(selection), m_tree(tree), 
	  m_bTreeUsesTextCallback(bTreeUsesTextCallback), 
	  m_bTreeUsesImageCallback(bTreeUsesImageCallback),
	  m_htiDropTarget(NULL), m_htiDropAfter(NULL), m_bEnabled(FALSE), 
	  m_nScrollTimer(0), m_nExpandTimer(0)
{

}

CTreeDragDropHelper::~CTreeDragDropHelper()
{

}

BOOL CTreeDragDropHelper::Initialize(CWnd* pOwner, BOOL bEnabled, BOOL bAllowNcDrag)
{
	if (m_ddMgr.Install(pOwner, m_tree))
	{
		m_tree.ModifyStyle(0, TVS_DISABLEDRAGDROP);
		
		m_bEnabled = bEnabled;
		m_bAllowNcDrag = bAllowNcDrag;

		return TRUE;
	}

	// else
	return FALSE;
}

BOOL CTreeDragDropHelper::AddTargetWnd(CWnd* pWnd)
{
	ASSERT(pWnd->GetSafeHwnd());

	if (pWnd->GetSafeHwnd())
	{
		m_ddMgr.AddWindow(pWnd->GetSafeHwnd(), DDW_TARGET);
		return TRUE;
	}

	// else
	return FALSE;
}

BOOL CTreeDragDropHelper::GetDropTarget(HTREEITEM& htiDrop, HTREEITEM& htiAfter)
{
	htiDrop = m_htiDropTarget ? m_htiDropTarget : TVI_ROOT;
	htiAfter = m_htiDropAfter;

	return (htiDrop || htiAfter);
}

void CTreeDragDropHelper::SetTimer(int nTimer, UINT nPeriod)
{
	switch (nTimer)
	{
	case TIMER_SCROLL:
		if (m_nScrollTimer)
		{
			::KillTimer(NULL, m_nScrollTimer);
			m_nScrollTimer = 0;
		}
			
		if (nPeriod)
			m_nScrollTimer = ::SetTimer(NULL, 0, nPeriod, TimerProc);
		break;
		
	case TIMER_EXPAND:
		if (m_nExpandTimer)
		{
			::KillTimer(NULL, m_nExpandTimer);
			m_nExpandTimer = 0;
		}
			
		if (nPeriod)
			m_nExpandTimer = ::SetTimer(NULL, 0, nPeriod, TimerProc);
		break;
	}

	s_pTDDH = (m_nExpandTimer || m_nScrollTimer) ? this : NULL;
}

UINT CTreeDragDropHelper::ProcessMessage(const MSG* pMsg) 
{ 
	DRAGDROPINFO* pDDI = (DRAGDROPINFO*)pMsg->lParam;

	if (pMsg->message == WM_DD_DRAGENTER)
		return OnDragEnter(pDDI);

	else if (pMsg->message == WM_DD_PREDRAGMOVE)
		return OnDragPreMove(pDDI);

	else if (pMsg->message == WM_DD_DRAGOVER)
		return OnDragOver(pDDI);

	else if (pMsg->message == WM_DD_DRAGDROP)
		return OnDragDrop(pDDI);

	else if (pMsg->message == WM_DD_DRAGABORT)
		return OnDragAbort();

	// else
	return m_ddMgr.ProcessMessage(pMsg, m_bAllowNcDrag);
}

BOOL CTreeDragDropHelper::OnDragEnter(DRAGDROPINFO* pDDI)
{
	if (!m_bEnabled || !m_selection.GetCount())
		return FALSE;

	// make sure we clicked on an item
	HTREEITEM htiHit = HitTest(pDDI->pt);

	if (!htiHit || !m_selection.HasItem(htiHit))
		return FALSE;

	// make sure we didn't click on an expansion button
	UINT nFlags = 0;
	m_tree.HitTest(pDDI->pt, &nFlags);

	if (nFlags & TVHT_ONITEMBUTTON)
		return FALSE;
	
	// make sure this has not initiated a label edit
	m_tree.SendMessage(TVM_ENDEDITLABELNOW, TRUE, 0);
	
	pDDI->pData = new CDragDropTreeData(m_selection);
	
	// reset droptarget
	m_htiDropTarget = m_htiDropAfter = NULL;
	
	m_dropPos.htiDrop = NULL;
	m_dropPos.nWhere = DD_ON;
	
	return TRUE;
}

BOOL CTreeDragDropHelper::OnDragPreMove(const DRAGDROPINFO* /*pDDI*/)
{
	if (!m_bEnabled || !m_selection.GetCount())
		return FALSE;
	
	m_tree.SetInsertMark(NULL);
	
	return TRUE;
}

UINT CTreeDragDropHelper::OnDragOver(const DRAGDROPINFO* pDDI)
{
	if (!m_bEnabled || !m_selection.GetCount())
		return 0;
	
	HTREEITEM htiDrop = NULL;
	
	if (pDDI->hwndTarget == m_tree.GetSafeHwnd())
	{
		htiDrop = HighlightDropTarget(pDDI->pt);
		
		// Set a timer if the cursor is at the top or bottom of the window,
		// or if it's over a collapsed item.
		CRect rect;
		GetClientRect(pDDI->hwndTarget, rect);

		int cy = rect.Height();
		
		if ((pDDI->pt.y >= 0 && pDDI->pt.y <= SCROLL_MARGIN) || 
			(pDDI->pt.y >= cy - SCROLL_MARGIN && pDDI->pt.y <= cy))
		{
			SetTimer(TIMER_SCROLL, DELAY_INTERVAL);
		}
		
		if (htiDrop != NULL && m_tree.ItemHasChildren(htiDrop) &&
			!(m_tree.GetItemState(htiDrop, TVIS_EXPANDED) & TVIS_EXPANDED))
		{
			SetTimer(TIMER_EXPAND, EXPAND_INTERVAL);
		}
	}
	else
		HighlightDropTarget(OUTERSPACE);
			
	if (htiDrop)
	{
		if (pDDI->bLeftDrag)
		{
			BOOL bCopy = (GetKeyState(VK_CONTROL) & 0x8000);
			return bCopy ? DD_DROPEFFECT_COPY : DD_DROPEFFECT_MOVE;
		}
		else
			return DD_DROPEFFECT_MOVE;
	}

	// else
	return DD_DROPEFFECT_NONE;
}

BOOL CTreeDragDropHelper::OnDragDrop(const DRAGDROPINFO* pDDI)
{
	TRACE ("CTreeDragDropHelper::OnDragDrop(enter)\n");

	if (!m_bEnabled || !m_selection.GetCount())
		return FALSE;
	
	if (pDDI->hwndTarget == m_tree.GetSafeHwnd() && m_dropPos.htiDrop)
	{
		TRACE ("CTreeDragDropHelper::OnDragDrop(%s, %s)\n", m_tree.GetItemText(m_dropPos.htiDrop),
					(m_dropPos.nWhere == DD_ON) ? "On" : (m_dropPos.nWhere == DD_ABOVE) ? "Above" : "Below");
		
		// figure out where to drop
		switch (m_dropPos.nWhere)
		{
		case DD_ON:
			m_htiDropTarget = m_dropPos.htiDrop;
			m_htiDropAfter = NULL; // indicates this is a drop ON
			break;
			
		case DD_ABOVE:
			{
				m_htiDropAfter = m_tree.GetPrevSiblingItem(m_dropPos.htiDrop);
				
				if (!m_htiDropAfter || m_htiDropAfter == m_dropPos.htiDrop)
					m_htiDropAfter = TVI_FIRST;
				
				m_htiDropTarget = m_tree.GetParentItem(m_dropPos.htiDrop);
			}
			break;
			
		case DD_BELOW:
			m_htiDropAfter = m_dropPos.htiDrop;
			m_htiDropTarget = m_tree.GetParentItem(m_dropPos.htiDrop);
			break;
		}
	}
	else
	{
		TRACE ("CTreeDragDropHelper::OnDragDrop(not on tree)\n");
		m_htiDropTarget = m_htiDropAfter = NULL;
	}
	
	SetTimer(TIMER_SCROLL, 0);
	SetTimer(TIMER_EXPAND, 0);
	
	m_tree.SetInsertMark(NULL);
	m_tree.SelectDropTarget(NULL);
	m_tree.Invalidate(FALSE);

	TRACE ("CTreeDragDropHelper::OnDragDrop(leave)\n");
	
	return (m_htiDropTarget || m_htiDropAfter);
}

BOOL CTreeDragDropHelper::OnDragAbort()
{
	if (!m_bEnabled || !m_selection.GetCount())
		return FALSE;
	
	// reset droptarget
	m_htiDropTarget = m_htiDropAfter = NULL;
	
	// cleanup
	m_tree.SetInsertMark(NULL);
	m_tree.SelectDropTarget(NULL);
	m_tree.Invalidate(FALSE);
	
	SetTimer(TIMER_SCROLL, 0);
	SetTimer(TIMER_EXPAND, 0);
	
	return TRUE;
}

HTREEITEM CTreeDragDropHelper::HitTest(CPoint point, DDWHERE& nWhere) const
{
	if (point == OUTERSPACE)
		return NULL;

	if (point.y < 0)
		return NULL;
	
	if (point.x < 0 && m_bAllowNcDrag)
	{
		// check if point is within entire window
		CRect rWindow;
		m_tree.GetWindowRect(rWindow);
		m_tree.ScreenToClient(rWindow);

		if (rWindow.PtInRect(point))
			point.x = 0;
		else // outside window
			return NULL;
	}

	UINT nFlags;
	HTREEITEM hItem = m_tree.HitTest(point, &nFlags);

	if (!hItem)
		return NULL;

	CRect rItem;

	if (m_tree.GetItemRect(hItem, rItem, FALSE))
	{
		int nMargin = (rItem.Height() + 3) / 4;

		if (point.y < rItem.top + nMargin) // top 1/4 == above the item
			nWhere = DD_ABOVE;

		else if (point.y > rItem.bottom - nMargin) // bottom 1/4 == below the item
			nWhere = DD_BELOW;
		else
			nWhere = DD_ON; // ie its on the item

		return hItem;
	}

	return NULL;
}

HTREEITEM CTreeDragDropHelper::HighlightDropTarget()
{
	CPoint point(::GetMessagePos());
	m_tree.ScreenToClient(&point);

	return HighlightDropTarget(point);
}

HTREEITEM CTreeDragDropHelper::HighlightDropTarget(CPoint point)
{
	// Find out which item (if any) the cursor is over.
	DDWHERE nWhere;
	HTREEITEM hItem = HitTest(point, nWhere);

	// don't do nuthin' if nuthin's changed
	if (hItem == m_dropPos.htiDrop && nWhere == m_dropPos.nWhere)
	{
		TRACE("CTreeDragDropHelper::HighlightDropTarget(nothing's changed)\n");
	}

	// drop item cannot be selected unless this is a copy
   // nor can it be a child of a selected item unless its a copy
	BOOL bCopy = (GetKeyState(VK_CONTROL) & 0x8000);

	if (!bCopy && (m_selection.HasItem(hItem) || m_selection.HasSelectedParent(hItem)))

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?