📄 cooltabctrls.h
字号:
BOOL GetItemRect(int iItem, LPRECT prcItem) const
{
ATLASSERT(::IsWindow(m_hWnd));
ATLASSERT(prcItem);
if( prcItem == NULL ) return FALSE;
::SetRectEmpty(prcItem);
if( iItem < 0 || iItem >= m_Items.GetSize() ) return FALSE;
*prcItem = m_Items[iItem]->rcSize;
return TRUE;
}
void GetMetrics(TCMETRICS *pMetrics) const
{
ATLASSERT(!::IsBadWritePtr(pMetrics,sizeof(TCMETRICS)));
*pMetrics = m_metrics;
}
void SetMetrics(const TCMETRICS *pMetrics)
{
ATLASSERT(::IsWindow(m_hWnd));
ATLASSERT(!::IsBadReadPtr(pMetrics,sizeof(TCMETRICS)));
m_metrics = *pMetrics;
_Repaint();
}
void SetSelFont(HFONT hFont)
{
m_hSelFont = hFont;
}
HFONT GetSelFont() const
{
return (m_dwExtStyle & TCS_EX_SELHIGHLIGHT) != 0 ? m_hSelFont : m_hFont;
}
int FindItem(LPTCITEM pFindInfo, int nStart = -1) const
{
ATLASSERT(::IsWindow(m_hWnd));
if( nStart < 0 ) nStart = -1;
// Find the next item matching the criteria specified
const UINT maskFind = pFindInfo->mask;
int nCount = m_Items.GetSize();
for( int i = nStart + 1; i < nCount; i++ ) {
LPTCITEM pItem = m_Items[i];
UINT mask = pItem->mask;
if( (maskFind & mask) != maskFind ) continue;
if( (maskFind & TCIF_PARAM) != 0 && pItem->lParam != pFindInfo->lParam ) continue;
if( (maskFind & TCIF_TEXT) != 0 && pItem->pszText != NULL && pFindInfo->pszText != NULL && ::lstrcmp(pItem->pszText, pFindInfo->pszText) != 0 ) continue;
if( (maskFind & TCIF_IMAGE) != 0 && pItem->iImage != pFindInfo->iImage ) continue;
if( (maskFind & TCIF_STATE) != 0 && pItem->dwState != pFindInfo->dwState ) continue;
return i;
}
return -1;
}
// Implementation
void _Init()
{
ATLASSERT(::IsWindow(m_hWnd));
ATLASSERT(GetStyle() & WS_CHILD);
m_idDlgCtrl = GetDlgCtrlID();
m_wndNotify = GetParent();
SendMessage(WM_SETTINGCHANGE);
// This is a little WTL subclass helper notification
NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_INITIALIZE };
m_wndNotify.SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
}
ATLINLINE bool _ValidateItem(int iItem) const
{
ATLASSERT(iItem>=0 && iItem<m_Items.GetSize());
if( iItem < 0 || iItem >= m_Items.GetSize() ) return false;
return true;
}
ATLINLINE void _Repaint()
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
Invalidate();
}
// Message map and handlers
BEGIN_MSG_MAP(CCustomTabCtrl)
CHAIN_MSG_MAP(COffscreenDrawRect< T >)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonClick)
MESSAGE_HANDLER(WM_RBUTTONDOWN, OnRButtonClick)
MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
LRESULT lRes = DefWindowProc();
_Init();
return lRes;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
DeleteAllItems(); // Make sure to clean up memory
bHandled = FALSE;
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
bHandled = FALSE;
return 0;
}
LRESULT OnNcHitTest(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ScreenToClient(&pt);
T* pT = static_cast<T*>(this);
int iItem = pT->HitTest(pt);
if( iItem == -1 ) return HTTRANSPARENT;
return HTCLIENT;
}
LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return (LRESULT) (HFONT)( m_hFont != NULL ? m_hFont : AtlGetDefaultGuiFont() );
}
LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
m_hFont = (HFONT) wParam;
if( lParam != 0 && IsWindowVisible() ) _Repaint();
return 0;
}
LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
_Repaint();
return 0;
}
LRESULT OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
return DefWindowProc(uMsg, wParam, lParam) | DLGC_WANTARROWS;
}
LRESULT OnLButtonClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Send click notification
NMHDR nmh = { m_hWnd, m_idDlgCtrl, NM_CLICK };
m_wndNotify.SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
// Select tab below
T* pT = static_cast<T*>(this);
int iItem = pT->HitTest(pt);
if( iItem != -1 ) {
SetFocus();
SetCurSel(iItem);
}
return 0;
}
LRESULT OnRButtonClick(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
SendMessage(WM_LBUTTONDOWN, wParam, lParam); // BUG: Triggers NM_CLICK as well!
NMHDR nmh = { m_hWnd, m_idDlgCtrl, NM_RCLICK };
m_wndNotify.SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
return 0;
}
LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
MSG msg = { m_hWnd, uMsg, wParam, lParam };
if( m_Tip.IsWindow() ) m_Tip.RelayEvent(&msg);
bHandled = FALSE;
return 1;
}
LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
switch( wParam ) {
case VK_LEFT:
if( m_iCurSel > 0 ) SetCurSel(m_iCurSel - 1);
return 0;
case VK_RIGHT:
if( m_iCurSel < m_Items.GetSize() - 1 ) SetCurSel(m_iCurSel + 1);
return 0;
}
bHandled = FALSE;
return 0;
}
// Overridables
void UpdateLayout()
{
int nCount = m_Items.GetSize();
if( nCount == 0 ) return;
CClientDC dc(m_hWnd);
HFONT hOldFont = dc.SelectStockFont(DEFAULT_GUI_FONT);
RECT rcClient;
GetClientRect(&rcClient);
SIZE szIcon = { 0 };
if( !m_ImageList.IsNull() ) m_ImageList.GetIconSize(szIcon);
HFONT hFont = GetFont();
HFONT hSelFont = GetSelFont();
DWORD dwStyle = GetStyle();
// Reposition buttons
int xpos = m_metrics.cxIndent;
for( int i = 0; i < nCount; i++ ) {
COOLTCITEM* pItem = m_Items[i];
// Hidden button?
if( (pItem->dwState & TCIS_HIDDEN) != 0 ) continue;
// Determine width...
int cx = 0;
// Expand width according to decorations
if( pItem->iImage >= 0 && (pItem->mask & TCIF_IMAGE) != 0 && !m_ImageList.IsNull() ) {
cx += szIcon.cx + (m_metrics.cxImagePadding * 2);
}
if( pItem->mask & TCIF_TEXT ) {
RECT rcText = { 0 };
dc.SelectFont(i == m_iCurSel ? hSelFont : hFont);
dc.DrawText(pItem->pszText, ::lstrlen(pItem->pszText), &rcText, DT_SINGLELINE|DT_CALCRECT);
cx += (rcText.right - rcText.left) + (m_metrics.cxPadding * 2);
}
// Add margins
cx += m_metrics.cxMargin * 2;
// Selected button is allowed to grow further
if( m_iCurSel == i ) cx += m_metrics.cxSelMargin * 2;
// Need separators?
if( m_dwExtStyle & TCS_EX_FLATSEPARATORS ) cx += 2;
// Fixed width?
if( pItem->mask & TCIF_WIDTH ) cx = pItem->cx;
// Minimum width?
if( cx < m_nMinWidth ) cx = m_nMinWidth;
// Finally...
RECT& rc = pItem->rcSize;
rc.top = 0;
rc.bottom = rcClient.bottom - rcClient.top;
rc.left = xpos;
rc.right = xpos + cx;
xpos += cx + m_metrics.cxButtonSpacing;
}
// Allow buttons to fill entire row
int cx = (rcClient.right - rcClient.left) - xpos;
if( dwStyle & TCS_RAGGEDRIGHT ) {
if( cx > 0 ) {
int iDiff = cx / m_Items.GetSize();
for( int i = 0; i < nCount; i++ ) {
m_Items[i]->rcSize.right += iDiff;
if( i > 0 ) m_Items[i]->rcSize.left += iDiff;
iDiff *= 2;
}
}
}
// Compress buttons on same line
if( cx < 0 && (m_dwExtStyle & TCS_EX_COMPRESSLINE) != 0 ) {
int xpos = m_metrics.cxIndent;
int iWidth = (rcClient.right - rcClient.left) / nCount;
for( int i = 0; i < nCount; i++ ) {
COOLTCITEM* pItem = m_Items[i];
int cx = min( iWidth, pItem->rcSize.right - pItem->rcSize.left );
pItem->rcSize.right = xpos + cx;
pItem->rcSize.left = xpos;
xpos += cx;
}
}
// Expand currently selected button to overlap other buttons.
// NOTE: To make sense, take into the cxIndent/cxMargin/cxSelMargin into
// account when choosing a value.
if( m_iCurSel != - 1 ) ::InflateRect(&m_Items[m_iCurSel]->rcSize, m_metrics.cxOverlap, 0);
dc.SelectFont(hOldFont);
// Remove tooltips
if( m_Tip.IsWindow() ) {
TOOLINFO ti = { 0 };
ti.cbSize = sizeof(ti);
while( m_Tip.EnumTools(0, &ti) ) m_Tip.DelTool(&ti);
}
// Recreate tooltip rects
for( int j = 0; j < nCount; j++ ) {
if( m_Items[j]->mask & TCIF_TOOLTIP ) {
if( !m_Tip.IsWindow() ) m_Tip.Create(m_hWnd);
m_Tip.AddTool(m_hWnd, m_Items[j]->pszTipText, &m_Items[j]->rcSize, j + 1);
}
else if( dwStyle & TCS_TOOLTIPS ) {
if( !m_Tip.IsWindow() ) m_Tip.Create(m_hWnd);
m_Tip.AddTool(m_hWnd, m_Items[j]->pszText, &m_Items[j]->rcSize, j + 1);
}
}
// Reactivate tooltips
if( m_Tip.IsWindow() ) m_Tip.Activate(m_Tip.GetToolCount() > 0);
}
void DoPaint(CDCHandle dc, RECT &rcClip)
{
// NOTE: The handling of NM_CUSTOMDRAW is probably not entirely correct
// in the code below. But at least it makes a brave attempt to
// implement all the states described in MSDN docs.
// Save current DC selections
int save = dc.SaveDC();
ATLASSERT(save!=0);
// Make sure we don't paint outside client area (possible with paint dc)
RECT rcClient;
GetClientRect(&rcClient);
::IntersectClipRect(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
dc.FillRect(&rcClient, ::GetSysColorBrush(COLOR_3DFACE));
// Prepare DC
dc.SelectFont(GetFont());
T* pT = static_cast<T*>(this);
LRESULT lResStage;
NMCUSTOMDRAW nmc = { 0 };
nmc.hdr.hwndFrom = m_hWnd;
nmc.hdr.idFrom = m_idDlgCtrl;
nmc.hdr.code = NM_CUSTOMDRAW;
nmc.hdc = dc;
nmc.dwDrawStage = CDDS_PREPAINT;
lResStage = m_wndNotify.SendMessage(WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
if( lResStage == CDRF_NOTIFYITEMDRAW || lResStage == CDRF_DODEFAULT ) {
RECT rc;
int nCount = m_Items.GetSize();
// Draw the list items, except the selected one. It is drawn last
// so it can cover the tabs below it.
RECT rcIntersect;
for( int i = 0; i < nCount; i++ ) {
rc = m_Items[i]->rcSize;
if( rc.bottom - rc.top == 0 ) pT->UpdateLayout();
if( i != m_iCurSel ) {
if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
nmc.dwItemSpec = i;
nmc.uItemState = 0;
if( (m_Items[i]->dwState & TCIS_DISABLED) != 0 ) nmc.uItemState |= CDIS_DISABLED;
nmc.rc = rc;
pT->ProcessItem(lResStage, nmc);
}
}
}
if( m_iCurSel != -1 ) {
rc = m_Items[m_iCurSel]->rcSize;
if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
nmc.dwItemSpec = m_iCurSel;
nmc.uItemState = CDIS_SELECTED;
nmc.rc = rc;
pT->ProcessItem(lResStage, nmc);
}
}
}
if( lResStage == CDRF_NOTIFYPOSTPAINT ) {
nmc.dwItemSpec = 0;
nmc.uItemState = 0;
nmc.dwDrawStage = CDDS_POSTPAINT;
m_wndNotify.SendMessage(WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
}
dc.RestoreDC(save);
}
void ProcessItem(LRESULT lResStage, NMCUSTOMDRAW &nmc)
{
LRESULT lResItem = CDRF_DODEFAULT;
if( lResStage == CDRF_NOTIFYITEMDRAW ) {
nmc.dwDrawStage = CDDS_ITEMPREPAINT;
lResItem = m_wndNotify.SendMessage(WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
}
if( lResItem != CDRF_SKIPDEFAULT ) {
// Do default item-drawing
T* pT = static_cast<T*>(this);
pT->DoItemPaint(nmc);
}
if( lResStage == CDRF_NOTIFYITEMDRAW && lResItem == CDRF_NOTIFYPOSTPAINT ) {
nmc.dwDrawStage = CDDS_ITEMPOSTPAINT;
m_wndNotify.SendMessage(WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
}
}
void DoItemPaint(NMCUSTOMDRAW &/*nmc*/)
{
}
};
/////////////////////////////////////////////////////////////////////////////
//
// The sample tab controls
//
// The follwing samples derive directly from CCustomTabCtrl.
// This means that they can actually use the internal members
// of this class. But they will not! To keep the code clean, I'm only
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -