ncgutter.cpp

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

CPP
2,008
字号
// NcGutter.cpp: implementation of the CNcGutter class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "NcGutter.h"
#include "holdredraw.h"
#include "themed.h"
#include "dlgunits.h"

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

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

const UINT WM_NCG_FORCERESIZE = ::RegisterWindowMessage("WM_NCG_FORCERESIZE");
UINT HEADERHEIGHT = 0; // set in WM_NCCALCSIZE so not const
UINT BORDER = 0; // set in WM_NCCALCSIZE so not const
const UINT SORTWIDTH = 10;
const UINT MINCLIENTWIDTH = GetSystemMetrics(SM_CXVSCROLL) * 4; 

CNcGutter::CNcGutter(DWORD dwStyles) : 
	m_bSetRedraw(TRUE), 
	m_bFirstRecalc(TRUE),
	m_nHeaderColDown(-1),
	m_dwButtonDownItem(0),
	m_dwStyles(dwStyles)
{
	// add client header hot rect placeholder
	if (HasStyle(NCGS_SHOWHEADER) && CThemed().AreControlsThemed())
		m_hotTrack.AddRect();

	// client column is always last so we add it first
	m_aColumns.Add(new COLUMNDESC(NCG_CLIENTCOLUMNID));
}

CNcGutter::~CNcGutter()
{
	// delete column defs
	int nCol = m_aColumns.GetSize();

	while (nCol--)
		delete m_aColumns[nCol];
}

BOOL CNcGutter::Initialize(HWND hwnd)
{
	if (CSubclassWnd::HookWindow(hwnd))
	{
		// reset calculated widths
		int nCol = m_aColumns.GetSize() - 1; // omit last column == client

		while (nCol--)
		{
			if (m_aColumns[nCol]->bCalcWidth)
				m_aColumns[nCol]->nWidth = 0;
		}
		
		RecalcGutter();

		// hot tracking
		if (HasStyle(NCGS_SHOWHEADER) && CThemed().AreControlsThemed())
			m_hotTrack.Initialize(GetCWnd());

		return TRUE;
	}

	return FALSE;
}

void CNcGutter::EnableStyle(DWORD dwStyle, BOOL bEnable)
{
	if (bEnable)
		m_dwStyles |= dwStyle;
	else
		m_dwStyles &= ~dwStyle;

	if (!IsHooked() || m_bFirstRecalc)
		return;

	// special handling
	if (dwStyle & NCGS_SHOWHEADER)
	{
		if (CThemed().AreControlsThemed())
		{
			if (!bEnable)
				m_hotTrack.Reset();
			else
			{
				m_hotTrack.Initialize(GetCWnd());
				
				for (int nCol = 0; nCol < m_aColumns.GetSize(); nCol++)
					m_hotTrack.AddRect();
				
				UpdateHeaderHotRects();
			}
		}

		PostMessage(WM_NCG_FORCERESIZE);
	}
	else if (dwStyle & NCGS_RIGHTCOLUMNS)
		PostMessage(WM_NCG_FORCERESIZE);
}

BOOL CNcGutter::AddColumn(UINT nColID, LPCTSTR szTitle, UINT nWidth, UINT nTextAlign)
{
	ASSERT (GetColumnIndex(nColID) == -1);

	if (GetColumnIndex(nColID) >= 0)
		return FALSE;

	COLUMNDESC* pCD = new COLUMNDESC(nColID);

	pCD->sTitle = szTitle;
	pCD->nWidth = nWidth;
	pCD->nTextAlign = nTextAlign;
	pCD->bCalcWidth = !nWidth;

	if (szTitle)
	{
		CWindowDC dc(GetCWnd());
		CFont* pOldFont = PrepareFont(&dc, TRUE);
		
		pCD->nTextWidth = dc.GetTextExtent(szTitle).cx;
		
		dc.SelectObject(pOldFont);
	}
	else
		pCD->nTextWidth = 0;
	
	if (nWidth)
		pCD->nWidth = max(nWidth, pCD->nTextWidth + 2 * NCG_COLPADDING);

	// client column is always last
	m_aColumns.InsertAt(m_aColumns.GetSize() - 1, pCD);

	// add a placeholder to the hot tracker
	if (CThemed().AreControlsThemed())
		m_hotTrack.AddRect();

	if (IsHooked())
		RecalcGutter();

	return TRUE;
}

void CNcGutter::PressHeader(UINT nColID, BOOL bPress)
{
	int nColumn = GetColumnIndex(nColID);

	UnpressAllColumnHeaders(nColumn);
	
	if (nColumn >= 0 && nColumn < m_aColumns.GetSize())
	{
		COLUMNDESC* pCD = m_aColumns[nColumn];

		if (pCD->bPressed != bPress)
		{
			pCD->bPressed = bPress;
			Redraw();
		}
	}
}

void CNcGutter::SetHeaderTitle(UINT nColID, LPCTSTR szTitle, LPCTSTR szFont, BOOL bSymbolFont)
{
	int nColumn = GetColumnIndex(nColID);

	if (nColumn >= 0 && nColumn < m_aColumns.GetSize())
	{
		BOOL bRedraw = FALSE;
		COLUMNDESC* pCD = m_aColumns[nColumn];

		if (!pCD->hFont && szFont)
		{
			LOGFONT lf;
			HFONT hDef = (HFONT)GetStockObject(DEFAULT_GUI_FONT);

			if (GetObject(hDef, sizeof(lf), &lf))
			{
				lstrcpy(lf.lfFaceName, szFont);

				if (bSymbolFont)
				{
					lf.lfCharSet = SYMBOL_CHARSET;
					lf.lfQuality = ANTIALIASED_QUALITY;
					lf.lfHeight = MulDiv(lf.lfHeight, 12, 10);
				}
				
				pCD->hFont = CreateFontIndirect(&lf);

				if (pCD->hFont)
					pCD->bSymbolFont = bSymbolFont;

				bRedraw = (NULL != pCD->hFont);
			}
		}
		
		if (pCD->sTitle != szTitle)
		{
			pCD->sTitle = szTitle;

			if (szTitle)
			{
				CWindowDC dc(GetCWnd());
				CFont* pOldFont = PrepareFont(&dc, TRUE, pCD->hFont);
				
				pCD->nTextWidth = dc.GetTextExtent(szTitle).cx;
				dc.SelectObject(pOldFont);
			}
			else
				pCD->nTextWidth = 0;
			
			if (!pCD->bCalcWidth)
				pCD->nWidth = max(pCD->nWidth, pCD->nTextWidth + 2 * NCG_COLPADDING);

			bRedraw = TRUE;
		}

		if (bRedraw && IsHooked())
		{
			// do the redraw
			CWindowDC dc(GetCWnd());
			CRect rHeader;

			GetHeaderRect(rHeader, GHR_NONCLIENT, FALSE);
			NcDrawHeader(&dc, rHeader, NONCLIENT, NULL);
		}
	}
}

void CNcGutter::SetColumnSort(UINT nColID, NCGSORT nSortDir, BOOL bExclusive)
{
	BOOL bNeedsRecalc = FALSE;

	int nCol = m_aColumns.GetSize();

	while (nCol--)
	{
		COLUMNDESC* pCD = m_aColumns[nCol];

		if (pCD->nColID != nColID) // other column
		{
			if (bExclusive)
			{
				BOOL bChange = (pCD->nSortDir != NCGSORT_NONE);

				// adjust column width if fixed size and was sorted
				if (bChange)
				{
					if (!pCD->bCalcWidth)
						pCD->nWidth -= SORTWIDTH;
					else
						bNeedsRecalc = TRUE;
				}

				pCD->nSortDir = NCGSORT_NONE;
				bNeedsRecalc |= bChange;
			}
		}
		else
		{
			BOOL bChange = (pCD->nSortDir != NCGSORT_NONE && nSortDir == NCGSORT_NONE) ||
							(pCD->nSortDir == NCGSORT_NONE && nSortDir != NCGSORT_NONE);

			// adjust column width if fixed size and was sorted
			if (bChange)
			{
				if (!pCD->bCalcWidth)
					pCD->nWidth += SORTWIDTH;
				else
					bNeedsRecalc = TRUE;
			}

			pCD->nSortDir = nSortDir;
		}
	}
	
	if (bNeedsRecalc)
		RecalcGutter();
	else
		Redraw();
}

void CNcGutter::EnableHeaderClicking(UINT nColID, BOOL bEnable)
{
	int nColumn = GetColumnIndex(nColID);

	if (nColumn >= 0 && nColumn < m_aColumns.GetSize())
		m_aColumns[nColumn]->bClickable = bEnable;
}

int CNcGutter::GetColumnIndex(UINT nColID) const
{
	int nCol = m_aColumns.GetSize();

	while (nCol-- && (nColID != m_aColumns[nCol]->nColID));

	return nCol; // can be -1
}

void CNcGutter::UnpressAllColumnHeaders(int nExcludeCol)
{
	int nCol = m_aColumns.GetSize();

	while (nCol--)
	{
		if (nCol != nExcludeCol)
			m_aColumns[nCol]->bPressed = FALSE;
	}
}

void CNcGutter::Redraw()
{
	CNcRedraw hr(GetHwnd(), "5");
}

void CNcGutter::RecalcGutter(BOOL bForceRedraw)
{
	int nCurWidth = GetGutterWidth();
	int nNewWidth = RecalcGutterWidth();

	if (IsHooked())
	{
		if (nNewWidth != nCurWidth || m_bFirstRecalc)
		{
			m_bFirstRecalc = FALSE;
			PostMessage(WM_NCG_FORCERESIZE);

			// notify parent then hook window
			UINT nID = GetDlgCtrlID();

			::SendMessage(GetParent(), WM_NCG_WIDTHCHANGE, nID, MAKELPARAM(nCurWidth, nNewWidth));
			SendMessage(WM_NCG_WIDTHCHANGE, nID, MAKELPARAM(nCurWidth, nNewWidth));

			// update header tracking rects
			UpdateHeaderHotRects();
		}
		else if (bForceRedraw)
			Redraw();
	}
}

BOOL CNcGutter::RecalcColumn(UINT nColID)
{
	if (!IsHooked() || m_bFirstRecalc || !m_bSetRedraw)
		return FALSE;

	if (nColID == NCG_CLIENTCOLUMNID)
		return FALSE;

	int nCol = GetColumnIndex(nColID);

	if (nCol == -1)
		return FALSE;

	COLUMNDESC* pCD = m_aColumns[nCol];

	if (!pCD->bCalcWidth)
		return FALSE;

	// do the recalc
	int nCurWidth = GetGutterWidth(); // cache this

	CWindowDC dc(GetCWnd());
	CFont* pOldFont = PrepareFont(&dc, FALSE);

	NCGRECALCCOLUMN ncrc = { nColID, &dc, 0 };

	// try parent then hook window
	UINT nCtrlID = GetDlgCtrlID();

	if (!::SendMessage(GetParent(), WM_NCG_RECALCCOLWIDTH, nCtrlID, (LPARAM)&ncrc))
		SendMessage(WM_NCG_RECALCCOLWIDTH, nCtrlID, (LPARAM)&ncrc);

	if (ncrc.nWidth > 0)
	{
		pCD->nWidth = max(ncrc.nWidth, pCD->nTextWidth) + 2 * NCG_COLPADDING;
	 
		if (pCD->nSortDir != NCGSORT_NONE)
			pCD->nWidth += SORTWIDTH;
	}
	else
		pCD->nWidth = 0;

	if (pOldFont)
		dc.SelectObject(pOldFont);

	// check for a change
	int nNewWidth = GetGutterWidth();

	if (nNewWidth != nCurWidth) // there's been a change
	{
		PostMessage(WM_NCG_FORCERESIZE);
		
		// notify hooked wnd and parent
		SendMessage(WM_NCG_WIDTHCHANGE, nCtrlID, MAKELPARAM(nCurWidth, nNewWidth));
		::SendMessage(GetParent(), WM_NCG_WIDTHCHANGE, nCtrlID, MAKELPARAM(nCurWidth, nNewWidth));
		
		// update header tracking rects
		UpdateHeaderHotRects();

		return TRUE;
	}

	return FALSE;
}

int CNcGutter::GetGutterWidth() const
{
	int nWidth = 0;

	for (int nCol = 0; nCol < m_aColumns.GetSize() - 1; nCol++)
		nWidth += m_aColumns[nCol]->nWidth;

	return nWidth;
}

void CNcGutter::AddRecalcMessage(UINT nMessage, UINT nNotification)
{
	m_mapRecalcMessages.SetAt(MAKELONG((WORD)nMessage, (WORD)nNotification), 0);
}

void CNcGutter::AddRedrawMessage(UINT nMessage, UINT nNotification)
{
	m_mapRedrawMessages.SetAt(MAKELONG((WORD)nMessage, (WORD)nNotification), 0);
}

LRESULT CNcGutter::WindowProc(HWND hRealWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch (msg)
	{
	case WM_CAPTURECHANGED:
		if (!lp || (HWND)lp == GetHwnd())
			break;

		// else
		m_nHeaderColDown = -1;
		// fall thru
	case WM_SETFOCUS:
	case WM_VSCROLL:
	case WM_KILLFOCUS:
		if (m_bSetRedraw)
		{
			CNcRedraw hr(hRealWnd, "(WM_SETFOCUS or WM_VSCROLL or WM_KILLFOCUS)");
			return Default();
		}
		break;

	case WM_MOUSEWHEEL:
		if (m_bSetRedraw)
		{
			CNcRedraw hr(hRealWnd, "(WM_MOUSEWHEEL)");
			return Default();
		}
		break;

	case WM_KEYDOWN:
		switch (wp) // virtual key
		{
		// the standard scroll keypresses unless the parent is handling these
		case VK_DOWN:
		case VK_UP:
		case VK_LEFT:
		case VK_RIGHT:
		case VK_PRIOR: 
		case VK_NEXT:  
		case VK_END:   
		case VK_HOME:  
			if (!ParentWantRedraw())
				return Default();
			// else fall thru
		case VK_ESCAPE:  
			if (m_bSetRedraw)
			{
				CNcRedraw hr(hRealWnd, "WM_KEYDOWN");
				return Default();
			}
			break;

		// keys that might modify the cursor
		case VK_CONTROL:
		case VK_SHIFT:
		case VK_MENU:
			// but only on the first event (ie not repeats)
			if (!(lp & 0x40000000))
				OnSetCursor();
		}
		break;

	case WM_KEYUP:
		switch (wp) // virtual key
		{
		// keys that might modify the cursor
		case VK_CONTROL:
		case VK_SHIFT:
		case VK_MENU:
			if (!OnSetCursor())
			{
				// draw the default cursor
				SendMessage(WM_SETCURSOR, (WPARAM)GetHwnd(), MAKELPARAM(HTCLIENT, WM_MOUSEMOVE));
			}
		}
		break;

	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
		OnButtonDown(msg, lp);
		break;

	case WM_LBUTTONUP:
	case WM_RBUTTONUP:
		OnButtonUp(msg, lp);
		break;

	case WM_NCRBUTTONDOWN:
	case WM_NCLBUTTONDOWN:
	case WM_NCLBUTTONDBLCLK:
		if (wp == HTBORDER) // means gutter
			OnNcButtonDown(msg, lp);
		break;

	case WM_NCRBUTTONUP:
		if (wp == HTBORDER) // means gutter
		{
			OnNcButtonUp(msg, lp);
			return 0;
		}
		break;

	case WM_NCLBUTTONUP:
		if (wp == HTBORDER) // means gutter
			OnNcButtonUp(msg, lp);
		break;

	case WM_ERASEBKGND:
		// its not a neat solution but it does ensure that when expanding and collapsing
		// tree controls that any animations look correct
		{
			HDC hdc = (HDC)wp;
			UINT nFlags = PRF_CLIENT;
			OnPrint(hdc, nFlags);
		}
		return TRUE; // always

	case WM_NCHITTEST:
		return OnNcHitTest(lp);

 	case WM_PAINT:
		if (HasStyle(NCGS_DOUBLEBUFFERCLIENT))
		{
 			OnPaint();
 			return 0;
		}
		break;

	case WM_NCCALCSIZE:
		if (wp)
			return OnNcCalcSize((LPNCCALCSIZE_PARAMS)lp);
		break;

	case WM_NCPAINT:
		Default();

		if (m_bSetRedraw)
			OnNcPaint();
		return 0;

	case WM_PRINT:
		if (m_bSetRedraw)
			OnPrint((HDC)wp, (UINT&)lp);
		break;

	case WM_SETREDRAW:
		m_bSetRedraw = wp;
		
		if (m_bSetRedraw)
		{
			RecalcGutter();
			Redraw();
		}
		break;

	case WM_SETFONT:
		Default();
		RecalcGutter();
		return 0;

	case WM_SETCURSOR:
		if (LOWORD(lp) == HTBORDER && OnSetCursor())
			return TRUE;
		break;

	case WM_SIZE:
		if (m_hotTrack.IsValid())
		{
			Default();
			UpdateHeaderHotRects();
			return 0;
		}
		break;
	}

	// registered messages must be handled explicitly
	if (msg == WM_NCG_FORCERESIZE)
	{
		LRESULT lr  = SetWindowPos(hRealWnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); 
		UpdateHeaderHotRects();

		return lr;
	}

	else if (msg == WM_HTHOTCHANGE)
		OnHotChange(wp, lp);

	// test for specific messages wanting recalcs/redraws
	LRESULT lr = 0;

	if (ProcessRecalcMsg(msg, wp, lp, lr) || ProcessRedrawMsg(msg, wp, lp, lr))
		return lr;

	return CSubclassWnd::WindowProc(hRealWnd, msg, wp, lp);
}

LRESULT CNcGutter::OnNcCalcSize(LPNCCALCSIZE_PARAMS lpncsp)
{
	LRESULT lr = Default();
	
	if (BORDER == 0)
		BORDER = lpncsp->rgrc[0].left - lpncsp->rgrc[1].left; // diff between window and client

	// make sure the client width is _never_ reduced to zero
	CRect rWindow;
	GetWindowRect(rWindow);

	UINT nGutterWidth = GetGutterWidth();
	nGutterWidth = min(rWindow.Width() - MINCLIENTWIDTH - BORDER * 2, nGutterWidth);
	
	if (HasStyle(NCGS_RIGHTCOLUMNS))
		lpncsp->rgrc[0].right -= nGutterWidth;
	else
		lpncsp->rgrc[0].left += nGutterWidth;
	
	if (HasStyle(NCGS_SHOWHEADER))
    {
        if (HEADERHEIGHT == 0)
            HEADERHEIGHT = CDlgUnits(GetParent()).ToPixelsY(11); // handles font sizes

		lpncsp->rgrc[0].top += HEADERHEIGHT;
    }
	else
        HEADERHEIGHT = 0;
	
	return lr;
}

BOOL CNcGutter::ParentWantRecalc()
{
	// ask parent/derived if they still want to recalc
	UINT nID = GetDlgCtrlID();
	const MSG* pMsg = GetCurrentMessage();
	
	BOOL bCancel = ::SendMessage(GetParent(), WM_NCG_WANTRECALC, nID, (LPARAM)pMsg);
	

⌨️ 快捷键说明

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