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 + -
显示快捷键?