// Windows Template Library - WTL version 10.0
// Copyright (C) Microsoft Corporation, WTL Team. All rights reserved.
//
// This file is a part of the Windows Template Library.
// The use and distribution terms for this software are covered by the
// Microsoft Public License (http://opensource.org/licenses/MS-PL)
// which can be found in the file MS-PL.txt at the root folder.
#ifndef __ATLCTRLX_H__
#define __ATLCTRLX_H__
#pragma once
#ifndef __ATLAPP_H__
#error atlctrlx.h requires atlapp.h to be included first
#endif
#ifndef __ATLCTRLS_H__
#error atlctrlx.h requires atlctrls.h to be included first
#endif
///////////////////////////////////////////////////////////////////////////////
// Classes in this file:
//
// CBitmapButtonImpl<T, TBase, TWinTraits>
// CBitmapButton
// CCheckListViewCtrlImpl<T, TBase, TWinTraits>
// CCheckListViewCtrl
// CHyperLinkImpl<T, TBase, TWinTraits>
// CHyperLink
// CWaitCursor
// CCustomWaitCursor
// CMultiPaneStatusBarCtrlImpl<T, TBase>
// CMultiPaneStatusBarCtrl
// CPaneContainerImpl<T, TBase, TWinTraits>
// CPaneContainer
// CSortListViewImpl<T>
// CSortListViewCtrlImpl<T, TBase, TWinTraits>
// CSortListViewCtrl
// CTabViewImpl<T, TBase, TWinTraits>
// CTabView
namespace WTL
{
///////////////////////////////////////////////////////////////////////////////
// CBitmapButton - bitmap button implementation
// bitmap button extended styles
#define BMPBTN_HOVER 0x00000001
#define BMPBTN_AUTO3D_SINGLE 0x00000002
#define BMPBTN_AUTO3D_DOUBLE 0x00000004
#define BMPBTN_AUTOSIZE 0x00000008
#define BMPBTN_SHAREIMAGELISTS 0x00000010
#define BMPBTN_AUTOFIRE 0x00000020
#define BMPBTN_CHECK 0x00000040
#define BMPBTN_AUTOCHECK 0x00000080
// Note: BMPBTN_CHECK/BMPBTN_AUTOCHECK disables BN_DOUBLECLICKED,
// BMPBTN_AUTOFIRE doesn't work with BMPBTN_CHECK/BMPBTN_AUTOCHECK
template <class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CBitmapButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
enum
{
_nImageNormal = 0,
_nImagePushed,
_nImageFocusOrHover,
_nImageDisabled,
_nImageCount = 4,
};
enum
{
ID_TIMER_FIRST = 1000,
ID_TIMER_REPEAT = 1001
};
// Bitmap button specific extended styles
DWORD m_dwExtendedStyle;
CImageList m_ImageList;
int m_nImage[_nImageCount];
CToolTipCtrl m_tip;
LPTSTR m_lpstrToolTipText;
// Internal states
unsigned m_fMouseOver:1;
unsigned m_fFocus:1;
unsigned m_fPressed:1;
unsigned m_fChecked:1;
// Constructor/Destructor
CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
m_dwExtendedStyle(dwExtendedStyle), m_ImageList(hImageList),
m_lpstrToolTipText(NULL),
m_fMouseOver(0), m_fFocus(0), m_fPressed(0), m_fChecked(0)
{
m_nImage[_nImageNormal] = -1;
m_nImage[_nImagePushed] = -1;
m_nImage[_nImageFocusOrHover] = -1;
m_nImage[_nImageDisabled] = -1;
#ifdef _DEBUG
if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
#endif // _DEBUG
}
~CBitmapButtonImpl()
{
if((m_dwExtendedStyle & BMPBTN_SHAREIMAGELISTS) == 0)
m_ImageList.Destroy();
delete [] m_lpstrToolTipText;
}
// overridden to provide proper initialization
BOOL SubclassWindow(HWND hWnd)
{
BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
if(bRet != FALSE)
{
T* pT = static_cast<T*>(this);
pT->Init();
}
return bRet;
}
// Attributes
DWORD GetBitmapButtonExtendedStyle() const
{
return m_dwExtendedStyle;
}
DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
{
DWORD dwPrevStyle = m_dwExtendedStyle;
if(dwMask == 0)
m_dwExtendedStyle = dwExtendedStyle;
else
m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
#ifdef _DEBUG
if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && IsCheckMode())
ATLTRACE2(atlTraceUI, 0, _T("CBitmapButtonImpl - Check mode and BMPBTN_AUTOFIRE cannot be used together, BMPBTN_AUTOFIRE will be ignored.\n"));
#endif // _DEBUG
return dwPrevStyle;
}
HIMAGELIST GetImageList() const
{
return m_ImageList;
}
HIMAGELIST SetImageList(HIMAGELIST hImageList)
{
HIMAGELIST hImageListPrev = m_ImageList;
m_ImageList = hImageList;
if(((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0) && ::IsWindow(this->m_hWnd))
SizeToImage();
return hImageListPrev;
}
int GetToolTipTextLength() const
{
return (m_lpstrToolTipText == NULL) ? -1 : lstrlen(m_lpstrToolTipText);
}
bool GetToolTipText(LPTSTR lpstrText, int nLength) const
{
ATLASSERT(lpstrText != NULL);
if(m_lpstrToolTipText == NULL)
return false;
errno_t nRet = ATL::Checked::tcsncpy_s(lpstrText, nLength, m_lpstrToolTipText, _TRUNCATE);
return ((nRet == 0) || (nRet == STRUNCATE));
}
bool SetToolTipText(LPCTSTR lpstrText)
{
if(m_lpstrToolTipText != NULL)
{
delete [] m_lpstrToolTipText;
m_lpstrToolTipText = NULL;
}
if(lpstrText == NULL)
{
if(m_tip.IsWindow())
m_tip.Activate(FALSE);
return true;
}
int cchLen = lstrlen(lpstrText) + 1;
ATLTRY(m_lpstrToolTipText = new TCHAR[cchLen]);
if(m_lpstrToolTipText == NULL)
return false;
ATL::Checked::tcscpy_s(m_lpstrToolTipText, cchLen, lpstrText);
if(m_tip.IsWindow())
{
m_tip.Activate(TRUE);
m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
}
return true;
}
bool GetCheck() const
{
return (m_fChecked == 1);
}
void SetCheck(bool bCheck, bool bUpdate = true)
{
m_fChecked = bCheck ? 1 : 0;
if(bUpdate)
{
this->Invalidate();
this->UpdateWindow();
}
}
// Operations
void SetImages(int nNormal, int nPushed = -1, int nFocusOrHover = -1, int nDisabled = -1)
{
if(nNormal != -1)
m_nImage[_nImageNormal] = nNormal;
if(nPushed != -1)
m_nImage[_nImagePushed] = nPushed;
if(nFocusOrHover != -1)
m_nImage[_nImageFocusOrHover] = nFocusOrHover;
if(nDisabled != -1)
m_nImage[_nImageDisabled] = nDisabled;
}
BOOL SizeToImage()
{
ATLASSERT(::IsWindow(this->m_hWnd) && (m_ImageList.m_hImageList != NULL));
int cx = 0;
int cy = 0;
if(!m_ImageList.GetIconSize(cx, cy))
return FALSE;
return this->ResizeClient(cx, cy);
}
// Overrideables
void DoPaint(CDCHandle dc)
{
ATLASSERT(m_ImageList.m_hImageList != NULL); // image list must be set
ATLASSERT(m_nImage[0] != -1); // main bitmap must be set
// set bitmap according to the current button state
bool bHover = IsHoverMode();
bool bPressed = (m_fPressed == 1) || (IsCheckMode() && (m_fChecked == 1));
int nImage = -1;
if(!this->IsWindowEnabled())
nImage = m_nImage[_nImageDisabled];
else if(bPressed)
nImage = m_nImage[_nImagePushed];
else if((!bHover && (m_fFocus == 1)) || (bHover && (m_fMouseOver == 1)))
nImage = m_nImage[_nImageFocusOrHover];
// if none is set, use default one
if(nImage == -1)
nImage = m_nImage[_nImageNormal];
// draw the button image
bool bAuto3D = (m_dwExtendedStyle & (BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTO3D_DOUBLE)) != 0;
int xyPos = (bPressed && bAuto3D && (m_nImage[_nImagePushed] == -1)) ? 1 : 0;
m_ImageList.Draw(dc, nImage, xyPos, xyPos, ILD_NORMAL);
// draw 3D border if required
if(bAuto3D)
{
RECT rect = {};
this->GetClientRect(&rect);
if(bPressed)
dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_SUNKENOUTER : EDGE_SUNKEN, BF_RECT);
else if(!bHover || (m_fMouseOver == 1))
dc.DrawEdge(&rect, ((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_RAISEDINNER : EDGE_RAISED, BF_RECT);
if(!bHover && (m_fFocus == 1))
{
::InflateRect(&rect, -2 * ::GetSystemMetrics(SM_CXEDGE), -2 * ::GetSystemMetrics(SM_CYEDGE));
dc.DrawFocusRect(&rect);
}
}
}
// Message map and handlers
BEGIN_MSG_MAP(CBitmapButtonImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
MESSAGE_HANDLER(WM_ENABLE, OnEnable)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
MESSAGE_HANDLER(WM_KEYUP, OnKeyUp)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
pT->Init();
bHandled = FALSE;
return 1;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_tip.IsWindow())
{
m_tip.DestroyWindow();
m_tip.m_hWnd = NULL;
}
bHandled = FALSE;
return 1;
}
LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
if(m_tip.IsWindow())
m_tip.RelayEvent(&msg);
bHandled = FALSE;
return 1;
}
LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return 1; // no background needed
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
if(wParam != NULL)
{
pT->DoPaint((HDC)wParam);
}
else
{
CPaintDC dc(this->m_hWnd);
pT->DoPaint(dc.m_hDC);
}
return 0;
}
LRESULT OnFocus(UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
m_fFocus = (uMsg == WM_SETFOCUS) ? 1 : 0;
this->Invalidate();
this->UpdateWindow();
bHandled = FALSE;
return 1;
}
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
LRESULT lRet = 0;
if(IsHoverMode())
this->SetCapture();
else
lRet = this->DefWindowProc(uMsg, wParam, lParam);
if(::GetCapture() == this->m_hWnd)
{
m_fPressed = 1;
this->Invalidate();
this->UpdateWindow();
}
if(((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0) && !IsCheckMode())
{
int nElapse = 250;
int nDelay = 0;
if(::SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &nDelay, 0))
nElapse += nDelay * 250; // all milli-seconds
this->SetTimer(ID_TIMER_FIRST, nElapse);
}
return lRet;
}
LRESULT OnLButtonDblClk(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
LRESULT lRet = 0;
if(!IsHoverMode() && !IsCheckMode())
lRet = this->DefWindowProc(uMsg, wParam, lParam);
if(::GetCapture() != this->m_hWnd)
this->SetCapture();
if(m_fPressed == 0)
{
m_fPressed = 1;
this->Invalidate();
this->UpdateWindow();
}
return lRet;
}
LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
if(((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0) && (m_fPressed == 1))
SetCheck(!GetCheck(), false);
LRESULT lRet = 0;
if(!IsHoverMode() && !IsCheckMode())
lRet = this->DefWindowProc(uMsg, wParam, lParam);
if(::GetCapture() == this->m_hWnd)
{
if((IsHoverMode() || IsCheckMode()) && (m_fPressed == 1))
this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
::ReleaseCapture();
}
return lRet;
}
LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_fPressed == 1)
{
m_fPressed = 0;
this->Invalidate();
this->UpdateWindow();
}
bHandled = FALSE;
return 1;
}
LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
this->Invalidate();
this->UpdateWindow();
bHandled = FALSE;
return 1;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(::GetCapture() == this->m_hWnd)
{
POINT ptCursor = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
this->ClientToScreen(&ptCursor);
RECT rect = {};
this->GetWindowRect(&rect);
unsigned int uPressed = ::PtInRect(&rect, ptCursor) ? 1 : 0;
if(m_fPressed != uPressed)
{
m_fPressed = uPressed;
this->Invalidate();
this->UpdateWindow();
}
}
else if(IsHoverMode() && m_fMouseOver == 0)
{
m_fMouseOver = 1;
this->Invalidate();
this->UpdateWindow();
StartTrackMouseLeave();
}
bHandled = FALSE;
return 1;
}
LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if(m_fMouseOver == 1)
{
m_fMouseOver = 0;
this->Invalidate();
this->UpdateWindow();
}
return 0;
}
LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if((wParam == VK_SPACE) && IsHoverMode())
return 0; // ignore if in hover mode
if((wParam == VK_SPACE) && (m_fPressed == 0))
{
m_fPressed = 1;
this->Invalidate();
this->UpdateWindow();
}
bHandled = FALSE;
return 1;
}
LRESULT OnKeyUp(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if((wParam == VK_SPACE) && IsHoverMode())
return 0; // ignore if in hover mode
if((wParam == VK_SPACE) && (m_fPressed == 1))
{
m_fPressed = 0;
if((m_dwExtendedStyle & BMPBTN_AUTOCHECK) != 0)
SetCheck(!GetCheck(), false);
this->Invalidate();
this->UpdateWindow();
}
bHandled = FALSE;
return 1;
}
LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
ATLASSERT((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0);
switch(wParam) // timer ID
{
case ID_TIMER_FIRST:
this->KillTimer(ID_TIMER_FIRST);
if(m_fPressed == 1)
{
this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
int nElapse = 250;
int nRepeat = 40;
if(::SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &nRepeat, 0))
nElapse = 10000 / (10 * nRepeat + 25); // milli-seconds, approximated
this->SetTimer(ID_TIMER_REPEAT, nElapse);
}
break;
case ID_TIMER_REPEAT:
if(m_fPressed == 1)
this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
else if(::GetCapture() != this->m_hWnd)
this->KillTimer(ID_TIMER_REPEAT);
break;
default: // not our timer
break;
}
return 0;
}
LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// If the control is subclassed or superclassed, this message can cause
// repainting without WM_PAINT. We don't use this state, so just do nothing.
return 0;
}
// Implementation
void Init()
{
// We need this style to prevent Windows from painting the button
this->ModifyStyle(0, BS_OWNERDRAW);
// create a tool tip
m_tip.Create(this->m_hWnd);
ATLASSERT(m_tip.IsWindow());
if(m_tip.IsWindow() && (m_lpstrToolTipText != NULL))
{
m_tip.Activate(TRUE);
m_tip.AddTool(this->m_hWnd, m_lpstrToolTipText);
}
if((m_ImageList.m_hImageList != NULL) && ((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0))
SizeToImage();
}
BOOL StartTrackMouseLeave()
{
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = this->m_hWnd;
return ::TrackMouseEvent(&tme);
}
bool IsHoverMode() const
{
return ((m_dwExtendedStyle & BMPBTN_HOVER) != 0);
}
bool IsCheckMode() const
{
return ((m_dwExtendedStyle & (BMPBTN_CHECK | BMPBTN_AUTOCHECK)) != 0);
}
};
class CBitmapButton : public CBitmapButtonImpl<CBitmapButton>
{
public:
DECLARE_WND_SUPERCLASS(_T("WTL_BitmapButton"), GetWndClassName())
CBitmapButton(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
CBitmapButtonImpl<CBitmapButton>(dwExtendedStyle, hImageList)
{ }
};
///////////////////////////////////////////////////////////////////////////////
// CCheckListCtrlView - list view control with check boxes
template <DWORD t_dwStyle, DWORD t_dwExStyle, DWORD t_dwExListViewStyle>
class CCheckListViewCtrlImplTraits
{
public:
static DWORD GetWndStyle(DWORD dwStyle)
{
return (dwStyle == 0) ? t_dwStyle : dwStyle;
}
static DWORD GetWndExStyle(DWORD dwExStyle)
{
return (dwExStyle == 0) ? t_dwExStyle : dwExStyle;
}
static DWORD GetExtendedLVStyle()
{
return t_dwExListViewStyle;
}
};
typedef CCheckListViewCtrlImplTraits<WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SHOWSELALWAYS, WS_EX_CLIENTEDGE, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT> CCheckListViewCtrlTraits;
template <class T, class TBase = CListViewCtrl, class TWinTraits = CCheckListViewCtrlTraits>
class ATL_NO_VTABLE CCheckListViewCtrlImpl : public ATL::CWindowImpl<T, TBase, TWinTraits >
{
public:
DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
// Attributes
static DWORD GetExtendedLVStyle()
{
return TWinTraits::GetExtendedLVStyle();
}
// Operations
BOOL SubclassWindow(HWND hWnd)
{
BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
if(bRet != FALSE)
{
T* pT = static_cast<T*>(this);
pT->Init();
}
return bRet;
}
void CheckSelectedItems(int nCurrItem)
{
// first check if this item is selected
LVITEM lvi = {};
lvi.iItem = nCurrItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_STATE;
lvi.stateMask = LVIS_SELECTED;
this->GetItem(&lvi);
// if item is not selected, don't do anything
if(!(lvi.state & LVIS_SELECTED))
return;
// new check state will be reverse of the current state,
BOOL bCheck = !this->GetCheckState(nCurrItem);
int nItem = -1;
int nOldItem = -1;
while((nItem = this->GetNextItem(nOldItem, LVNI_SELECTED)) != -1)
{
if(nItem != nCurrItem)
this->SetCheckState(nItem, bCheck);
nOldItem = nItem;
}
}
// Implementation
void Init()
{
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
ATLASSERT((pT->GetExtendedLVStyle() & LVS_EX_CHECKBOXES) != 0);
this->SetExtendedListViewStyle(pT->GetExtendedLVStyle());
}
// Message map and handlers
BEGIN_MSG_MAP(CCheckListViewCtrlImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDown)
MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
END_MSG_MAP()
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
// first let list view control initialize everything
LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
if(lRet == 0)
{
T* pT = static_cast<T*>(this);
pT->Init();
}
return lRet;
}
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
POINT ptMsg = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
LVHITTESTINFO lvh = {};
lvh.pt = ptMsg;
if((this->HitTest(&lvh) != -1) && (lvh.flags == LVHT_ONITEMSTATEICON) && (::GetKeyState(VK_CONTROL) >= 0))
{
T* pT = static_cast<T*>(this);
pT->CheckSelectedItems(lvh.iItem);
}
bHandled = FALSE;
return 1;
}
LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam == VK_SPACE)
{
int nCurrItem = this->GetNextItem(-1, LVNI_FOCUSED);
if((nCurrItem != -1) && (::GetKeyState(VK_CONTROL) >= 0))
{
T* pT = static_cast<T*>(this);
pT->CheckSelectedItems(nCurrItem);
}
}
bHandled = FALSE;
return 1;
}
};
class CCheckListViewCtrl : public CCheckListViewCtrlImpl<CCheckListViewCtrl>
{
public:
DECLARE_WND_SUPERCLASS(_T("WTL_CheckListView"), GetWndClassName())
};
///////////////////////////////////////////////////////////////////////////////
// CHyperLink - hyper link control implementation
#define HLINK_UNDERLINED 0x00000000
#define HLINK_NOTUNDERLINED 0x00000001
#define HLINK_UNDERLINEHOVER 0x00000002
#define HLINK_COMMANDBUTTON 0x00000004
#define HLINK_NOTIFYBUTTON 0x0000000C
#define HLINK_USETAGS 0x00000010
#define HLINK_USETAGSBOLD 0x00000030
#define HLINK_NOTOOLTIP 0x00000040
#define HLINK_AUTOCREATELINKFONT 0x00000080
#define HLINK_SINGLELINE 0x00000100
// Notes:
// - HLINK_USETAGS and HLINK_USETAGSBOLD are always left-aligned
// - When HLINK_USETAGSBOLD is used, the underlined styles will be ignored
template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CHyperLinkImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
LPTSTR m_lpstrLabel;
LPTSTR m_lpstrHyperLink;
HCURSOR m_hCursor;
HFONT m_hFontLink;
HFONT m_hFontNormal;
RECT m_rcLink;
CToolTipCtrl m_tip;
COLORREF m_clrLink;
COLORREF m_clrVisited;
DWORD m_dwExtendedStyle; // Hyper Link specific extended styles
bool m_bPaintLabel:1;
bool m_bVisited:1;
bool m_bHover:1;
bool m_bInternalLinkFont:1;
bool m_bInternalNormalFont:1;
// Constructor/Destructor
CHyperLinkImpl(DWORD dwExtendedStyle = HLINK_UNDERLINED) :
m_lpstrLabel(NULL), m_lpstrHyperLink(NULL),
m_hCursor(NULL), m_hFontLink(NULL), m_hFontNormal(NULL),
m_clrLink(RGB(0, 0, 255)), m_clrVisited(RGB(128, 0, 128)),
m_dwExtendedStyle(dwExtendedStyle),
m_bPaintLabel(true), m_bVisited(false),
m_bHover(false), m_bInternalLinkFont(false), m_bInternalNormalFont(false)
{
::SetRectEmpty(&m_rcLink);
}
~CHyperLinkImpl()
{
delete [] m_lpstrLabel;
delete [] m_lpstrHyperLink;
}
// Attributes
DWORD GetHyperLinkExtendedStyle() const
{
return m_dwExtendedStyle;
}
DWORD SetHyperLinkExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
{
DWORD dwPrevStyle = m_dwExtendedStyle;
if(dwMask == 0)
m_dwExtendedStyle = dwExtendedStyle;
else
m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
return dwPrevStyle;
}
bool GetLabel(LPTSTR lpstrBuffer, int nLength) const
{
if(m_lpstrLabel == NULL)
return false;
ATLASSERT(lpstrBuffer != NULL);
if(nLength <= lstrlen(m_lpstrLabel))
return false;
ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrLabel);
return true;
}
bool SetLabel(LPCTSTR lpstrLabel)
{
delete [] m_lpstrLabel;
m_lpstrLabel = NULL;
int cchLen = lstrlen(lpstrLabel) + 1;
ATLTRY(m_lpstrLabel = new TCHAR[cchLen]);
if(m_lpstrLabel == NULL)
return false;
ATL::Checked::tcscpy_s(m_lpstrLabel, cchLen, lpstrLabel);
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
if(this->m_hWnd != NULL)
this->SetWindowText(lpstrLabel); // Set this for accessibility
return true;
}
bool GetHyperLink(LPTSTR lpstrBuffer, int nLength) const
{
if(m_lpstrHyperLink == NULL)
return false;
ATLASSERT(lpstrBuffer != NULL);
if(nLength <= lstrlen(m_lpstrHyperLink))
return false;
ATL::Checked::tcscpy_s(lpstrBuffer, nLength, m_lpstrHyperLink);
return true;
}
bool SetHyperLink(LPCTSTR lpstrLink)
{
delete [] m_lpstrHyperLink;
m_lpstrHyperLink = NULL;
int cchLen = lstrlen(lpstrLink) + 1;
ATLTRY(m_lpstrHyperLink = new TCHAR[cchLen]);
if(m_lpstrHyperLink == NULL)
return false;
ATL::Checked::tcscpy_s(m_lpstrHyperLink, cchLen, lpstrLink);
if(m_lpstrLabel == NULL)
{
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
}
if(m_tip.IsWindow())
{
m_tip.Activate(TRUE);
m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
}
return true;
}
HFONT GetLinkFont() const
{
return m_hFontLink;
}
void SetLinkFont(HFONT hFont)
{
if(m_bInternalLinkFont)
{
::DeleteObject(m_hFontLink);
m_bInternalLinkFont = false;
}
m_hFontLink = hFont;
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
}
int GetIdealHeight() const
{
ATLASSERT(::IsWindow(this->m_hWnd));
if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
return -1;
if(!m_bPaintLabel)
return -1;
UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
CClientDC dc(this->m_hWnd);
RECT rect = {};
this->GetClientRect(&rect);
HFONT hFontOld = dc.SelectFont(m_hFontNormal);
RECT rcText = rect;
dc.DrawText(_T("NS"), -1, &rcText, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(m_hFontLink);
RECT rcLink = rect;
dc.DrawText(_T("NS"), -1, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(hFontOld);
return __max(rcText.bottom - rcText.top, rcLink.bottom - rcLink.top);
}
bool GetIdealSize(SIZE& size) const
{
int cx = 0, cy = 0;
bool bRet = GetIdealSize(cx, cy);
if(bRet)
{
size.cx = cx;
size.cy = cy;
}
return bRet;
}
bool GetIdealSize(int& cx, int& cy) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
return false;
if(!m_bPaintLabel)
return false;
CClientDC dc(this->m_hWnd);
RECT rcClient = {};
this->GetClientRect(&rcClient);
RECT rcAll = rcClient;
if(IsUsingTags())
{
// find tags and label parts
LPTSTR lpstrLeft = NULL;
int cchLeft = 0;
LPTSTR lpstrLink = NULL;
int cchLink = 0;
LPTSTR lpstrRight = NULL;
int cchRight = 0;
const T* pT = static_cast<const T*>(this);
pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
// get label part rects
UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
HFONT hFontOld = dc.SelectFont(m_hFontNormal);
RECT rcLeft = rcClient;
dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(m_hFontLink);
RECT rcLink = { rcLeft.right, rcLeft.top, rcClient.right, rcClient.bottom };
dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(m_hFontNormal);
RECT rcRight = { rcLink.right, rcLink.top, rcClient.right, rcClient.bottom };
dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(hFontOld);
int cyMax = __max(rcLeft.bottom, __max(rcLink.bottom, rcRight.bottom));
::SetRect(&rcAll, rcLeft.left, rcLeft.top, rcRight.right, cyMax);
}
else
{
HFONT hOldFont = NULL;
if(m_hFontLink != NULL)
hOldFont = dc.SelectFont(m_hFontLink);
LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
DWORD dwStyle = this->GetStyle();
UINT uFormat = DT_LEFT;
if (dwStyle & SS_CENTER)
uFormat = DT_CENTER;
else if (dwStyle & SS_RIGHT)
uFormat = DT_RIGHT;
uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
dc.DrawText(lpstrText, -1, &rcAll, uFormat | DT_CALCRECT);
if(m_hFontLink != NULL)
dc.SelectFont(hOldFont);
if (dwStyle & SS_CENTER)
{
int dx = (rcClient.right - rcAll.right) / 2;
::OffsetRect(&rcAll, dx, 0);
}
else if (dwStyle & SS_RIGHT)
{
int dx = rcClient.right - rcAll.right;
::OffsetRect(&rcAll, dx, 0);
}
}
cx = rcAll.right - rcAll.left;
cy = rcAll.bottom - rcAll.top;
return true;
}
// for command buttons only
bool GetToolTipText(LPTSTR lpstrBuffer, int nLength) const
{
ATLASSERT(IsCommandButton());
return GetHyperLink(lpstrBuffer, nLength);
}
bool SetToolTipText(LPCTSTR lpstrToolTipText)
{
ATLASSERT(IsCommandButton());
return SetHyperLink(lpstrToolTipText);
}
// Operations
BOOL SubclassWindow(HWND hWnd)
{
ATLASSERT(this->m_hWnd == NULL);
ATLASSERT(::IsWindow(hWnd));
if(m_hFontNormal == NULL)
m_hFontNormal = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0L);
BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
if(bRet != FALSE)
{
T* pT = static_cast<T*>(this);
pT->Init();
}
return bRet;
}
bool Navigate()
{
ATLASSERT(::IsWindow(this->m_hWnd));
bool bRet = true;
if(IsNotifyButton())
{
NMHDR nmhdr = { this->m_hWnd, (UINT_PTR)this->GetDlgCtrlID(), NM_CLICK };
this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
}
else if(IsCommandButton())
{
this->GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
}
else
{
ATLASSERT(m_lpstrHyperLink != NULL);
DWORD_PTR dwRet = (DWORD_PTR)::ShellExecute(0, _T("open"), m_lpstrHyperLink, 0, 0, SW_SHOWNORMAL);
bRet = (dwRet > 32);
ATLASSERT(bRet);
if(bRet)
{
m_bVisited = true;
this->Invalidate();
}
}
return bRet;
}
void CreateLinkFontFromNormal()
{
if(m_bInternalLinkFont)
{
::DeleteObject(m_hFontLink);
m_bInternalLinkFont = false;
}
CFontHandle font = (m_hFontNormal != NULL) ? m_hFontNormal : (HFONT)::GetStockObject(SYSTEM_FONT);
LOGFONT lf = {};
font.GetLogFont(&lf);
if(IsUsingTagsBold())
lf.lfWeight = FW_BOLD;
else if(!IsNotUnderlined())
lf.lfUnderline = TRUE;
m_hFontLink = ::CreateFontIndirect(&lf);
m_bInternalLinkFont = true;
ATLASSERT(m_hFontLink != NULL);
}
// Message map and handlers
BEGIN_MSG_MAP(CHyperLinkImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
MESSAGE_HANDLER(WM_CHAR, OnChar)
MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
MESSAGE_HANDLER(WM_ENABLE, OnEnable)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(WM_UPDATEUISTATE, OnUpdateUiState)
MESSAGE_HANDLER(WM_SIZE, OnSize)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->Init();
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_tip.IsWindow())
{
m_tip.DestroyWindow();
m_tip.m_hWnd = NULL;
}
if(m_bInternalLinkFont)
{
::DeleteObject(m_hFontLink);
m_hFontLink = NULL;
m_bInternalLinkFont = false;
}
if(m_bInternalNormalFont)
{
::DeleteObject(m_hFontNormal);
m_hFontNormal = NULL;
m_bInternalNormalFont = false;
}
bHandled = FALSE;
return 1;
}
LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
MSG msg = { this->m_hWnd, uMsg, wParam, lParam };
if(m_tip.IsWindow() && IsUsingToolTip())
m_tip.RelayEvent(&msg);
bHandled = FALSE;
return 1;
}
LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return 1; // no background painting needed (we do it all during WM_PAINT)
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(!m_bPaintLabel)
{
bHandled = FALSE;
return 1;
}
T* pT = static_cast<T*>(this);
if(wParam != NULL)
{
pT->DoEraseBackground((HDC)wParam);
pT->DoPaint((HDC)wParam);
}
else
{
CPaintDC dc(this->m_hWnd);
pT->DoEraseBackground(dc.m_hDC);
pT->DoPaint(dc.m_hDC);
}
return 0;
}
LRESULT OnFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_bPaintLabel)
this->Invalidate();
else
bHandled = FALSE;
return 0;
}
LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
if(((m_lpstrHyperLink != NULL) || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
{
::SetCursor(m_hCursor);
if(IsUnderlineHover())
{
if(!m_bHover)
{
m_bHover = true;
this->InvalidateRect(&m_rcLink);
this->UpdateWindow();
StartTrackMouseLeave();
}
}
}
else
{
if(IsUnderlineHover())
{
if(m_bHover)
{
m_bHover = false;
this->InvalidateRect(&m_rcLink);
this->UpdateWindow();
}
}
bHandled = FALSE;
}
return 0;
}
LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if(IsUnderlineHover() && m_bHover)
{
m_bHover = false;
this->InvalidateRect(&m_rcLink);
this->UpdateWindow();
}
return 0;
}
LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
if(::PtInRect(&m_rcLink, pt))
{
this->SetFocus();
this->SetCapture();
}
return 0;
}
LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
if(GetCapture() == this->m_hWnd)
{
ReleaseCapture();
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
if(::PtInRect(&m_rcLink, pt))
{
T* pT = static_cast<T*>(this);
pT->Navigate();
}
}
return 0;
}
LRESULT OnChar(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if((wParam == VK_RETURN) || (wParam == VK_SPACE))
{
T* pT = static_cast<T*>(this);
pT->Navigate();
}
return 0;
}
LRESULT OnGetDlgCode(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return DLGC_WANTCHARS;
}
LRESULT OnSetCursor(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
POINT pt = {};
GetCursorPos(&pt);
this->ScreenToClient(&pt);
if(((m_lpstrHyperLink != NULL) || IsCommandButton()) && ::PtInRect(&m_rcLink, pt))
{
return TRUE;
}
bHandled = FALSE;
return FALSE;
}
LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
this->Invalidate();
this->UpdateWindow();
return 0;
}
LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return (LRESULT)m_hFontNormal;
}
LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
if(m_bInternalNormalFont)
{
::DeleteObject(m_hFontNormal);
m_bInternalNormalFont = false;
}
bool bCreateLinkFont = m_bInternalLinkFont;
m_hFontNormal = (HFONT)wParam;
if(bCreateLinkFont || IsAutoCreateLinkFont())
CreateLinkFontFromNormal();
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
if((BOOL)lParam)
{
this->Invalidate();
this->UpdateWindow();
}
return 0;
}
LRESULT OnUpdateUiState(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// If the control is subclassed or superclassed, this message can cause
// repainting without WM_PAINT. We don't use this state, so just do nothing.
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
pT->Invalidate();
return 0;
}
// Implementation
void Init()
{
ATLASSERT(::IsWindow(this->m_hWnd));
// Check if we should paint a label
const int cchBuff = 8;
TCHAR szBuffer[cchBuff] = {};
if(::GetClassName(this->m_hWnd, szBuffer, cchBuff))
{
if(lstrcmpi(szBuffer, _T("static")) == 0)
{
this->ModifyStyle(0, SS_NOTIFY); // we need this
DWORD dwStyle = this->GetStyle() & 0x000000FF;
if((dwStyle == SS_ICON) || (dwStyle == SS_BLACKRECT) || (dwStyle == SS_GRAYRECT) ||
(dwStyle == SS_WHITERECT) || (dwStyle == SS_BLACKFRAME) || (dwStyle == SS_GRAYFRAME) ||
(dwStyle == SS_WHITEFRAME) || (dwStyle == SS_OWNERDRAW) ||
(dwStyle == SS_BITMAP) || (dwStyle == SS_ENHMETAFILE))
m_bPaintLabel = false;
}
}
// create or load a cursor
m_hCursor = ::LoadCursor(NULL, IDC_HAND);
ATLASSERT(m_hCursor != NULL);
// set fonts
if(m_bPaintLabel)
{
if(m_hFontNormal == NULL)
{
m_hFontNormal = AtlCreateControlFont();
m_bInternalNormalFont = true;
}
if(m_hFontLink == NULL)
CreateLinkFontFromNormal();
}
// create a tool tip
m_tip.Create(this->m_hWnd);
ATLASSERT(m_tip.IsWindow());
// set label (defaults to window text)
if(m_lpstrLabel == NULL)
{
int nLen = this->GetWindowTextLength();
if(nLen > 0)
{
ATLTRY(m_lpstrLabel = new TCHAR[nLen + 1]);
if(m_lpstrLabel != NULL)
ATLVERIFY(this->GetWindowText(m_lpstrLabel, nLen + 1) > 0);
}
}
T* pT = static_cast<T*>(this);
pT->CalcLabelRect();
// set hyperlink (defaults to label), or just activate tool tip if already set
if((m_lpstrHyperLink == NULL) && !IsCommandButton())
{
if(m_lpstrLabel != NULL)
SetHyperLink(m_lpstrLabel);
}
else
{
m_tip.Activate(TRUE);
m_tip.AddTool(this->m_hWnd, m_lpstrHyperLink, &m_rcLink, 1);
}
// set link colors
if(m_bPaintLabel)
{
ATL::CRegKey rk;
LONG lRet = rk.Open(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Internet Explorer\\Settings"));
if(lRet == ERROR_SUCCESS)
{
const int cchValue = 12;
TCHAR szValue[cchValue] = {};
ULONG ulCount = cchValue;
lRet = rk.QueryStringValue(_T("Anchor Color"), szValue, &ulCount);
if(lRet == ERROR_SUCCESS)
{
COLORREF clr = pT->_ParseColorString(szValue);
ATLASSERT(clr != CLR_INVALID);
if(clr != CLR_INVALID)
m_clrLink = clr;
}
ulCount = cchValue;
lRet = rk.QueryStringValue(_T("Anchor Color Visited"), szValue, &ulCount);
if(lRet == ERROR_SUCCESS)
{
COLORREF clr = pT->_ParseColorString(szValue);
ATLASSERT(clr != CLR_INVALID);
if(clr != CLR_INVALID)
m_clrVisited = clr;
}
}
}
}
static COLORREF _ParseColorString(LPTSTR lpstr)
{
int c[3] = { -1, -1, -1 };
LPTSTR p = NULL;
for(int i = 0; i < 2; i++)
{
for(p = lpstr; *p != _T('\0'); p = ::CharNext(p))
{
if(*p == _T(','))
{
*p = _T('\0');
c[i] = _ttoi(lpstr);
lpstr = &p[1];
break;
}
}
if(c[i] == -1)
return CLR_INVALID;
}
if(*lpstr == _T('\0'))
return CLR_INVALID;
c[2] = _ttoi(lpstr);
return RGB(c[0], c[1], c[2]);
}
bool CalcLabelRect()
{
if(!::IsWindow(this->m_hWnd))
return false;
if((m_lpstrLabel == NULL) && (m_lpstrHyperLink == NULL))
return false;
CClientDC dc(this->m_hWnd);
RECT rcClient = {};
this->GetClientRect(&rcClient);
m_rcLink = rcClient;
if(!m_bPaintLabel)
return true;
if(IsUsingTags())
{
// find tags and label parts
LPTSTR lpstrLeft = NULL;
int cchLeft = 0;
LPTSTR lpstrLink = NULL;
int cchLink = 0;
LPTSTR lpstrRight = NULL;
int cchRight = 0;
T* pT = static_cast<T*>(this);
pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
ATLASSERT(lpstrLink != NULL);
ATLASSERT(cchLink > 0);
// get label part rects
HFONT hFontOld = dc.SelectFont(m_hFontNormal);
UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
RECT rcLeft = rcClient;
if(lpstrLeft != NULL)
dc.DrawText(lpstrLeft, cchLeft, &rcLeft, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(m_hFontLink);
RECT rcLink = rcClient;
if(lpstrLeft != NULL)
rcLink.left = rcLeft.right;
dc.DrawText(lpstrLink, cchLink, &rcLink, DT_LEFT | uFormat | DT_CALCRECT);
dc.SelectFont(hFontOld);
m_rcLink = rcLink;
}
else
{
HFONT hOldFont = NULL;
if(m_hFontLink != NULL)
hOldFont = dc.SelectFont(m_hFontLink);
LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
DWORD dwStyle = this->GetStyle();
UINT uFormat = DT_LEFT;
if (dwStyle & SS_CENTER)
uFormat = DT_CENTER;
else if (dwStyle & SS_RIGHT)
uFormat = DT_RIGHT;
uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
dc.DrawText(lpstrText, -1, &m_rcLink, uFormat | DT_CALCRECT);
if(m_hFontLink != NULL)
dc.SelectFont(hOldFont);
if (dwStyle & SS_CENTER)
{
int dx = (rcClient.right - m_rcLink.right) / 2;
::OffsetRect(&m_rcLink, dx, 0);
}
else if (dwStyle & SS_RIGHT)
{
int dx = rcClient.right - m_rcLink.right;
::OffsetRect(&m_rcLink, dx, 0);
}
}
return true;
}
void CalcLabelParts(LPTSTR& lpstrLeft, int& cchLeft, LPTSTR& lpstrLink, int& cchLink, LPTSTR& lpstrRight, int& cchRight) const
{
lpstrLeft = NULL;
cchLeft = 0;
lpstrLink = NULL;
cchLink = 0;
lpstrRight = NULL;
cchRight = 0;
LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
int cchText = lstrlen(lpstrText);
bool bOutsideLink = true;
for(int i = 0; i < cchText; i++)
{
if(lpstrText[i] != _T('<'))
continue;
if(bOutsideLink)
{
if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 3, _T("<A>"), 3) == CSTR_EQUAL)
{
if(i > 0)
{
lpstrLeft = lpstrText;
cchLeft = i;
}
lpstrLink = &lpstrText[i + 3];
bOutsideLink = false;
}
}
else
{
if(::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, &lpstrText[i], 4, _T("</A>"), 4) == CSTR_EQUAL)
{
cchLink = i - 3 - cchLeft;
if(lpstrText[i + 4] != 0)
{
lpstrRight = &lpstrText[i + 4];
cchRight = cchText - (i + 4);
break;
}
}
}
}
}
void DoEraseBackground(CDCHandle dc)
{
HBRUSH hBrush = (HBRUSH)this->GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)dc.m_hDC, (LPARAM)this->m_hWnd);
if(hBrush != NULL)
{
RECT rect = {};
this->GetClientRect(&rect);
dc.FillRect(&rect, hBrush);
}
}
void DoPaint(CDCHandle dc)
{
if(IsUsingTags())
{
// find tags and label parts
LPTSTR lpstrLeft = NULL;
int cchLeft = 0;
LPTSTR lpstrLink = NULL;
int cchLink = 0;
LPTSTR lpstrRight = NULL;
int cchRight = 0;
T* pT = static_cast<T*>(this);
pT->CalcLabelParts(lpstrLeft, cchLeft, lpstrLink, cchLink, lpstrRight, cchRight);
// get label part rects
RECT rcClient = {};
this->GetClientRect(&rcClient);
dc.SetBkMode(TRANSPARENT);
HFONT hFontOld = dc.SelectFont(m_hFontNormal);
UINT uFormat = IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
if(lpstrLeft != NULL)
dc.DrawText(lpstrLeft, cchLeft, &rcClient, DT_LEFT | uFormat);
COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));
if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
dc.SelectFont(m_hFontLink);
else
dc.SelectFont(m_hFontNormal);
dc.DrawText(lpstrLink, cchLink, &m_rcLink, DT_LEFT | uFormat);
dc.SetTextColor(clrOld);
dc.SelectFont(m_hFontNormal);
if(lpstrRight != NULL)
{
RECT rcRight = { m_rcLink.right, m_rcLink.top, rcClient.right, rcClient.bottom };
dc.DrawText(lpstrRight, cchRight, &rcRight, DT_LEFT | uFormat);
}
if(GetFocus() == this->m_hWnd)
dc.DrawFocusRect(&m_rcLink);
dc.SelectFont(hFontOld);
}
else
{
dc.SetBkMode(TRANSPARENT);
COLORREF clrOld = dc.SetTextColor(this->IsWindowEnabled() ? (m_bVisited ? m_clrVisited : m_clrLink) : (::GetSysColor(COLOR_GRAYTEXT)));
HFONT hFontOld = NULL;
if((m_hFontLink != NULL) && (!IsUnderlineHover() || (IsUnderlineHover() && m_bHover)))
hFontOld = dc.SelectFont(m_hFontLink);
else
hFontOld = dc.SelectFont(m_hFontNormal);
LPTSTR lpstrText = (m_lpstrLabel != NULL) ? m_lpstrLabel : m_lpstrHyperLink;
DWORD dwStyle = this->GetStyle();
UINT uFormat = DT_LEFT;
if (dwStyle & SS_CENTER)
uFormat = DT_CENTER;
else if (dwStyle & SS_RIGHT)
uFormat = DT_RIGHT;
uFormat |= IsSingleLine() ? DT_SINGLELINE : DT_WORDBREAK;
dc.DrawText(lpstrText, -1, &m_rcLink, uFormat);
if(GetFocus() == this->m_hWnd)
dc.DrawFocusRect(&m_rcLink);
dc.SetTextColor(clrOld);
dc.SelectFont(hFontOld);
}
}
BOOL StartTrackMouseLeave()
{
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = this->m_hWnd;
return ::TrackMouseEvent(&tme);
}
// Implementation helpers
bool IsUnderlined() const
{
return ((m_dwExtendedStyle & (HLINK_NOTUNDERLINED | HLINK_UNDERLINEHOVER)) == 0);
}
bool IsNotUnderlined() const
{
return ((m_dwExtendedStyle & HLINK_NOTUNDERLINED) != 0);
}
bool IsUnderlineHover() const
{
return ((m_dwExtendedStyle & HLINK_UNDERLINEHOVER) != 0);
}
bool IsCommandButton() const
{
return ((m_dwExtendedStyle & HLINK_COMMANDBUTTON) != 0);
}
bool IsNotifyButton() const
{
return ((m_dwExtendedStyle & HLINK_NOTIFYBUTTON) == HLINK_NOTIFYBUTTON);
}
bool IsUsingTags() const
{
return ((m_dwExtendedStyle & HLINK_USETAGS) != 0);
}
bool IsUsingTagsBold() const
{
return ((m_dwExtendedStyle & HLINK_USETAGSBOLD) == HLINK_USETAGSBOLD);
}
bool IsUsingToolTip() const
{
return ((m_dwExtendedStyle & HLINK_NOTOOLTIP) == 0);
}
bool IsAutoCreateLinkFont() const
{
return ((m_dwExtendedStyle & HLINK_AUTOCREATELINKFONT) == HLINK_AUTOCREATELINKFONT);
}
bool IsSingleLine() const
{
return ((m_dwExtendedStyle & HLINK_SINGLELINE) == HLINK_SINGLELINE);
}
};
class CHyperLink : public CHyperLinkImpl<CHyperLink>
{
public:
DECLARE_WND_CLASS(_T("WTL_HyperLink"))
};
///////////////////////////////////////////////////////////////////////////////
// CWaitCursor - displays a wait cursor
class CWaitCursor
{
public:
// Data
HCURSOR m_hWaitCursor;
HCURSOR m_hOldCursor;
bool m_bInUse;
// Constructor/destructor
CWaitCursor(bool bSet = true, LPCTSTR lpstrCursor = IDC_WAIT, bool bSys = true) : m_hOldCursor(NULL), m_bInUse(false)
{
HINSTANCE hInstance = bSys ? NULL : ModuleHelper::GetResourceInstance();
m_hWaitCursor = ::LoadCursor(hInstance, lpstrCursor);
ATLASSERT(m_hWaitCursor != NULL);
if(bSet)
Set();
}
~CWaitCursor()
{
Restore();
}
// Methods
bool Set()
{
if(m_bInUse)
return false;
m_hOldCursor = ::SetCursor(m_hWaitCursor);
m_bInUse = true;
return true;
}
bool Restore()
{
if(!m_bInUse)
return false;
::SetCursor(m_hOldCursor);
m_bInUse = false;
return true;
}
};
///////////////////////////////////////////////////////////////////////////////
// CCustomWaitCursor - for custom and animated cursors
class CCustomWaitCursor : public CWaitCursor
{
public:
// Constructor/destructor
CCustomWaitCursor(ATL::_U_STRINGorID cursor, bool bSet = true, HINSTANCE hInstance = NULL) :
CWaitCursor(false, IDC_WAIT, true)
{
if(hInstance == NULL)
hInstance = ModuleHelper::GetResourceInstance();
m_hWaitCursor = (HCURSOR)::LoadImage(hInstance, cursor.m_lpstr, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE);
if(bSet)
Set();
}
~CCustomWaitCursor()
{
Restore();
::DestroyCursor(m_hWaitCursor);
}
};
///////////////////////////////////////////////////////////////////////////////
// CMultiPaneStatusBarCtrl - Status Bar with multiple panes
template <class T, class TBase = CStatusBarCtrl>
class ATL_NO_VTABLE CMultiPaneStatusBarCtrlImpl : public ATL::CWindowImpl< T, TBase >
{
public:
DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
// Data
enum { m_cxPaneMargin = 3 };
int m_nPanes;
int* m_pPane;
// Constructor/destructor
CMultiPaneStatusBarCtrlImpl() : m_nPanes(0), m_pPane(NULL)
{ }
~CMultiPaneStatusBarCtrlImpl()
{
delete [] m_pPane;
}
// Methods
HWND Create(HWND hWndParent, LPCTSTR lpstrText, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
{
return ATL::CWindowImpl< T, TBase >::Create(hWndParent, this->rcDefault, lpstrText, dwStyle, 0, nID);
}
HWND Create(HWND hWndParent, UINT nTextID = ATL_IDS_IDLEMESSAGE, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP, UINT nID = ATL_IDW_STATUS_BAR)
{
const int cchMax = 128; // max text length is 127 for status bars (+1 for null)
TCHAR szText[cchMax] = {};
::LoadString(ModuleHelper::GetResourceInstance(), nTextID, szText, cchMax);
return Create(hWndParent, szText, dwStyle, nID);
}
BOOL SetPanes(int* pPanes, int nPanes, bool bSetText = true)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(nPanes > 0);
m_nPanes = nPanes;
delete [] m_pPane;
m_pPane = NULL;
ATLTRY(m_pPane = new int[nPanes]);
ATLASSERT(m_pPane != NULL);
if(m_pPane == NULL)
return FALSE;
ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
int* pPanesPos = buff.Allocate(nPanes);
ATLASSERT(pPanesPos != NULL);
if(pPanesPos == NULL)
return FALSE;
ATL::Checked::memcpy_s(m_pPane, nPanes * sizeof(int), pPanes, nPanes * sizeof(int));
// get status bar DC and set font
CClientDC dc(this->m_hWnd);
HFONT hOldFont = dc.SelectFont(this->GetFont());
// get status bar borders
int arrBorders[3] = {};
this->GetBorders(arrBorders);
const int cchBuff = 128;
TCHAR szBuff[cchBuff] = {};
int cxLeft = arrBorders[0];
// calculate right edge of each part
for(int i = 0; i < nPanes; i++)
{
if(pPanes[i] == ID_DEFAULT_PANE)
{
// make very large, will be resized later
pPanesPos[i] = INT_MAX / 2;
}
else
{
::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
SIZE size = {};
dc.GetTextExtent(szBuff, lstrlen(szBuff), &size);
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
pPanesPos[i] = cxLeft + size.cx + arrBorders[2] + 2 * pT->m_cxPaneMargin;
}
cxLeft = pPanesPos[i];
}
BOOL bRet = this->SetParts(nPanes, pPanesPos);
if(bRet && bSetText)
{
for(int i = 0; i < nPanes; i++)
{
if(pPanes[i] != ID_DEFAULT_PANE)
{
::LoadString(ModuleHelper::GetResourceInstance(), pPanes[i], szBuff, cchBuff);
SetPaneText(m_pPane[i], szBuff);
}
}
}
dc.SelectFont(hOldFont);
return bRet;
}
bool GetPaneTextLength(int nPaneID, int* pcchLength = NULL, int* pnType = NULL) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return false;
int nLength = this->GetTextLength(nIndex, pnType);
if(pcchLength != NULL)
*pcchLength = nLength;
return true;
}
BOOL GetPaneText(int nPaneID, LPTSTR lpstrText, int* pcchLength = NULL, int* pnType = NULL) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
int nLength = this->GetText(nIndex, lpstrText, pnType);
if(pcchLength != NULL)
*pcchLength = nLength;
return TRUE;
}
#ifdef __ATLSTR_H__
BOOL GetPaneText(int nPaneID, ATL::CString& strText, int* pcchLength = NULL, int* pnType = NULL) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
int nLength = this->GetText(nIndex, strText, pnType);
if(pcchLength != NULL)
*pcchLength = nLength;
return TRUE;
}
#endif // __ATLSTR_H__
BOOL SetPaneText(int nPaneID, LPCTSTR lpstrText, int nType = 0)
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
return this->SetText(nIndex, lpstrText, nType);
}
BOOL GetPaneRect(int nPaneID, LPRECT lpRect) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
return this->GetRect(nIndex, lpRect);
}
BOOL SetPaneWidth(int nPaneID, int cxWidth)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(nPaneID != ID_DEFAULT_PANE); // Can't resize this one
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
// get pane positions
ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
int* pPanesPos = buff.Allocate(m_nPanes);
if(pPanesPos == NULL)
return FALSE;
this->GetParts(m_nPanes, pPanesPos);
// calculate offset
int cxPaneWidth = pPanesPos[nIndex] - ((nIndex == 0) ? 0 : pPanesPos[nIndex - 1]);
int cxOff = cxWidth - cxPaneWidth;
// find variable width pane
int nDef = m_nPanes;
for(int i = 0; i < m_nPanes; i++)
{
if(m_pPane[i] == ID_DEFAULT_PANE)
{
nDef = i;
break;
}
}
// resize
if(nIndex < nDef) // before default pane
{
for(int i = nIndex; i < nDef; i++)
pPanesPos[i] += cxOff;
}
else // after default one
{
for(int i = nDef; i < nIndex; i++)
pPanesPos[i] -= cxOff;
}
// set pane postions
return this->SetParts(m_nPanes, pPanesPos);
}
BOOL GetPaneTipText(int nPaneID, LPTSTR lpstrText, int nSize) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
this->GetTipText(nIndex, lpstrText, nSize);
return TRUE;
}
BOOL SetPaneTipText(int nPaneID, LPCTSTR lpstrText)
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
this->SetTipText(nIndex, lpstrText);
return TRUE;
}
BOOL GetPaneIcon(int nPaneID, HICON& hIcon) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
hIcon = this->GetIcon(nIndex);
return TRUE;
}
BOOL SetPaneIcon(int nPaneID, HICON hIcon)
{
ATLASSERT(::IsWindow(this->m_hWnd));
int nIndex = GetPaneIndexFromID(nPaneID);
if(nIndex == -1)
return FALSE;
return this->SetIcon(nIndex, hIcon);
}
// Message map and handlers
BEGIN_MSG_MAP(CMultiPaneStatusBarCtrlImpl< T >)
MESSAGE_HANDLER(WM_SIZE, OnSize)
END_MSG_MAP()
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
LRESULT lRet = this->DefWindowProc(uMsg, wParam, lParam);
if((wParam != SIZE_MINIMIZED) && (m_nPanes > 0))
{
T* pT = static_cast<T*>(this);
pT->UpdatePanesLayout();
}
return lRet;
}
// Implementation
BOOL UpdatePanesLayout()
{
// get pane positions
ATL::CTempBuffer<int, _WTL_STACK_ALLOC_THRESHOLD> buff;
int* pPanesPos = buff.Allocate(m_nPanes);
ATLASSERT(pPanesPos != NULL);
if(pPanesPos == NULL)
return FALSE;
int nRet = this->GetParts(m_nPanes, pPanesPos);
ATLASSERT(nRet == m_nPanes);
if(nRet != m_nPanes)
return FALSE;
// calculate offset
RECT rcClient = {};
this->GetClientRect(&rcClient);
int cxOff = rcClient.right - pPanesPos[m_nPanes - 1];
// Move panes left if size grip box is present
if((this->GetStyle() & SBARS_SIZEGRIP) != 0)
cxOff -= ::GetSystemMetrics(SM_CXVSCROLL) + ::GetSystemMetrics(SM_CXEDGE);
// find variable width pane
int i;
for(i = 0; i < m_nPanes; i++)
{
if(m_pPane[i] == ID_DEFAULT_PANE)
break;
}
// resize all panes from the variable one to the right
if((i < m_nPanes) && (pPanesPos[i] + cxOff) > ((i == 0) ? 0 : pPanesPos[i - 1]))
{
for(; i < m_nPanes; i++)
pPanesPos[i] += cxOff;
}
// set pane postions
return this->SetParts(m_nPanes, pPanesPos);
}
int GetPaneIndexFromID(int nPaneID) const
{
for(int i = 0; i < m_nPanes; i++)
{
if(m_pPane[i] == nPaneID)
return i;
}
return -1; // not found
}
};
class CMultiPaneStatusBarCtrl : public CMultiPaneStatusBarCtrlImpl<CMultiPaneStatusBarCtrl>
{
public:
DECLARE_WND_SUPERCLASS(_T("WTL_MultiPaneStatusBar"), GetWndClassName())
};
///////////////////////////////////////////////////////////////////////////////
// CPaneContainer - provides header with title and close button for panes
// pane container extended styles
#define PANECNT_NOCLOSEBUTTON 0x00000001
#define PANECNT_VERTICAL 0x00000002
#define PANECNT_FLATBORDER 0x00000004
#define PANECNT_NOBORDER 0x00000008
#define PANECNT_DIVIDER 0x00000010
#define PANECNT_GRADIENT 0x00000020
template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CPaneContainerImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >, public CCustomDraw< T >
{
public:
DECLARE_WND_CLASS_EX2(NULL, T, 0, -1)
// Constants
enum
{
m_cxyBorder = 2,
m_cxyTextOffset = 4,
m_cxyBtnOffset = 1,
m_cchTitle = 80,
m_cxImageTB = 13,
m_cyImageTB = 11,
m_cxyBtnAddTB = 7,
m_cxToolBar = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + m_cxyBtnOffset,
m_xBtnImageLeft = 6,
m_yBtnImageTop = 5,
m_xBtnImageRight = 12,
m_yBtnImageBottom = 11,
m_nCloseBtnID = ID_PANE_CLOSE
};
// Data members
CToolBarCtrl m_tb;
ATL::CWindow m_wndClient;
int m_cxyHeader;
TCHAR m_szTitle[m_cchTitle];
DWORD m_dwExtendedStyle; // Pane container specific extended styles
HFONT m_hFont;
bool m_bInternalFont;
// Constructor
CPaneContainerImpl() : m_cxyHeader(0), m_dwExtendedStyle(0), m_hFont(NULL), m_bInternalFont(false)
{
m_szTitle[0] = 0;
}
// Attributes
DWORD GetPaneContainerExtendedStyle() const
{
return m_dwExtendedStyle;
}
DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
{
DWORD dwPrevStyle = m_dwExtendedStyle;
if(dwMask == 0)
m_dwExtendedStyle = dwExtendedStyle;
else
m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
if(this->m_hWnd != NULL)
{
T* pT = static_cast<T*>(this);
bool bUpdate = false;
if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) != 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0)) // add close button
{
pT->CreateCloseButton();
bUpdate = true;
}
else if(((dwPrevStyle & PANECNT_NOCLOSEBUTTON) == 0) && ((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) != 0)) // remove close button
{
pT->DestroyCloseButton();
bUpdate = true;
}
if((dwPrevStyle & PANECNT_VERTICAL) != (m_dwExtendedStyle & PANECNT_VERTICAL)) // change orientation
{
pT->CalcSize();
bUpdate = true;
}
if((dwPrevStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER)) !=
(m_dwExtendedStyle & (PANECNT_FLATBORDER | PANECNT_NOBORDER))) // change border
{
bUpdate = true;
}
if((dwPrevStyle & PANECNT_GRADIENT) != (m_dwExtendedStyle & PANECNT_GRADIENT)) // change background
{
bUpdate = true;
}
if(bUpdate)
pT->UpdateLayout();
}
return dwPrevStyle;
}
HWND GetClient() const
{
return m_wndClient;
}
HWND SetClient(HWND hWndClient)
{
HWND hWndOldClient = m_wndClient;
m_wndClient = hWndClient;
if(this->m_hWnd != NULL)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
return hWndOldClient;
}
BOOL GetTitle(LPTSTR lpstrTitle, int cchLength) const
{
ATLASSERT(lpstrTitle != NULL);
errno_t nRet = ATL::Checked::tcsncpy_s(lpstrTitle, cchLength, m_szTitle, _TRUNCATE);
return ((nRet == 0) || (nRet == STRUNCATE));
}
BOOL SetTitle(LPCTSTR lpstrTitle)
{
ATLASSERT(lpstrTitle != NULL);
errno_t nRet = ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
bool bRet = ((nRet == 0) || (nRet == STRUNCATE));
if(bRet && (this->m_hWnd != NULL))
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
return bRet;
}
int GetTitleLength() const
{
return lstrlen(m_szTitle);
}
// Methods
HWND Create(HWND hWndParent, LPCTSTR lpstrTitle = NULL, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
{
if(lpstrTitle != NULL)
ATL::Checked::tcsncpy_s(m_szTitle, m_cchTitle, lpstrTitle, _TRUNCATE);
return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
}
HWND Create(HWND hWndParent, UINT uTitleID, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
{
if(uTitleID != 0U)
::LoadString(ModuleHelper::GetResourceInstance(), uTitleID, m_szTitle, m_cchTitle);
return ATL::CWindowImpl< T, TBase, TWinTraits >::Create(hWndParent, this->rcDefault, NULL, dwStyle, dwExStyle, nID, lpCreateParam);
}
BOOL SubclassWindow(HWND hWnd)
{
BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
if(bRet != FALSE)
{
T* pT = static_cast<T*>(this);
pT->Init();
RECT rect = {};
this->GetClientRect(&rect);
pT->UpdateLayout(rect.right, rect.bottom);
}
return bRet;
}
BOOL EnableCloseButton(BOOL bEnable)
{
ATLASSERT(::IsWindow(this->m_hWnd));
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
return (m_tb.m_hWnd != NULL) ? m_tb.EnableButton(pT->m_nCloseBtnID, bEnable) : FALSE;
}
void UpdateLayout()
{
RECT rcClient = {};
this->GetClientRect(&rcClient);
T* pT = static_cast<T*>(this);
pT->UpdateLayout(rcClient.right, rcClient.bottom);
}
// Message map and handlers
BEGIN_MSG_MAP(CPaneContainerImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
MESSAGE_HANDLER(WM_COMMAND, OnCommand)
FORWARD_NOTIFICATIONS()
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->Init();
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if(m_bInternalFont)
{
::DeleteObject(m_hFont);
m_hFont = NULL;
m_bInternalFont = false;
}
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
}
LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if(m_wndClient.m_hWnd != NULL)
m_wndClient.SetFocus();
return 0;
}
LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return (LRESULT)m_hFont;
}
LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
if(m_bInternalFont)
{
::DeleteObject(m_hFont);
m_bInternalFont = false;
}
m_hFont = (HFONT)wParam;
T* pT = static_cast<T*>(this);
pT->CalcSize();
if((BOOL)lParam != FALSE)
pT->UpdateLayout();
return 0;
}
LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->DrawPaneTitleBackground((HDC)wParam);
return 1;
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
if(wParam != NULL)
{
pT->DrawPaneTitle((HDC)wParam);
if(m_wndClient.m_hWnd == NULL) // no client window
pT->DrawPane((HDC)wParam);
}
else
{
CPaintDC dc(this->m_hWnd);
pT->DrawPaneTitle(dc.m_hDC);
if(m_wndClient.m_hWnd == NULL) // no client window
pT->DrawPane(dc.m_hDC);
}
return 0;
}
LRESULT OnNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(m_tb.m_hWnd == NULL)
{
bHandled = FALSE;
return 1;
}
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
LPNMHDR lpnmh = (LPNMHDR)lParam;
LRESULT lRet = 0;
// pass toolbar custom draw notifications to the base class
if((lpnmh->code == NM_CUSTOMDRAW) && (lpnmh->hwndFrom == m_tb.m_hWnd))
lRet = CCustomDraw< T >::OnCustomDraw(0, lpnmh, bHandled);
// tooltip notifications come with the tooltip window handle and button ID,
// pass them to the parent if we don't handle them
else if((lpnmh->code == TTN_GETDISPINFO) && (lpnmh->idFrom == pT->m_nCloseBtnID))
bHandled = pT->GetToolTipText(lpnmh);
// only let notifications not from the toolbar go to the parent
else if((lpnmh->hwndFrom != m_tb.m_hWnd) && (lpnmh->idFrom != pT->m_nCloseBtnID))
bHandled = FALSE;
return lRet;
}
LRESULT OnCommand(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// if command comes from the close button, substitute HWND of the pane container instead
if((m_tb.m_hWnd != NULL) && ((HWND)lParam == m_tb.m_hWnd))
return this->GetParent().SendMessage(WM_COMMAND, wParam, (LPARAM)this->m_hWnd);
bHandled = FALSE;
return 1;
}
// Custom draw overrides
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
{
return CDRF_NOTIFYITEMDRAW; // we need per-item notifications
}
DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
{
return CDRF_NOTIFYPOSTPAINT;
}
DWORD OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
CDCHandle dc = lpNMCustomDraw->hdc;
RECT& rc = lpNMCustomDraw->rc;
RECT rcImage = { m_xBtnImageLeft, m_yBtnImageTop, m_xBtnImageRight + 1, m_yBtnImageBottom + 1 };
::OffsetRect(&rcImage, rc.left, rc.top);
T* pT = static_cast<T*>(this);
if((lpNMCustomDraw->uItemState & CDIS_DISABLED) != 0)
{
RECT rcShadow = rcImage;
::OffsetRect(&rcShadow, 1, 1);
CPen pen1;
pen1.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DHILIGHT));
pT->DrawButtonImage(dc, rcShadow, pen1);
CPen pen2;
pen2.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_3DSHADOW));
pT->DrawButtonImage(dc, rcImage, pen2);
}
else
{
if((lpNMCustomDraw->uItemState & CDIS_SELECTED) != 0)
::OffsetRect(&rcImage, 1, 1);
CPen pen;
pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT));
pT->DrawButtonImage(dc, rcImage, pen);
}
return CDRF_DODEFAULT; // continue with the default item painting
}
// Implementation - overrideable methods
void Init()
{
if(m_hFont == NULL)
{
// The same as AtlCreateControlFont() for horizontal pane
LOGFONT lf = {};
ATLVERIFY(::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0) != FALSE);
if(IsVertical())
lf.lfEscapement = 900; // 90 degrees
m_hFont = ::CreateFontIndirect(&lf);
m_bInternalFont = true;
}
T* pT = static_cast<T*>(this);
pT->CalcSize();
if((m_dwExtendedStyle & PANECNT_NOCLOSEBUTTON) == 0)
pT->CreateCloseButton();
}
void UpdateLayout(int cxWidth, int cyHeight)
{
ATLASSERT(::IsWindow(this->m_hWnd));
RECT rect = {};
if(IsVertical())
{
::SetRect(&rect, 0, 0, m_cxyHeader, cyHeight);
if(m_tb.m_hWnd != NULL)
m_tb.SetWindowPos(NULL, m_cxyBorder, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
if(m_wndClient.m_hWnd != NULL)
m_wndClient.SetWindowPos(NULL, m_cxyHeader, 0, cxWidth - m_cxyHeader, cyHeight, SWP_NOZORDER);
else
rect.right = cxWidth;
}
else
{
::SetRect(&rect, 0, 0, cxWidth, m_cxyHeader);
if(m_tb.m_hWnd != NULL)
m_tb.SetWindowPos(NULL, rect.right - m_cxToolBar, m_cxyBorder + m_cxyBtnOffset, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
if(m_wndClient.m_hWnd != NULL)
m_wndClient.SetWindowPos(NULL, 0, m_cxyHeader, cxWidth, cyHeight - m_cxyHeader, SWP_NOZORDER);
else
rect.bottom = cyHeight;
}
this->InvalidateRect(&rect);
}
void CreateCloseButton()
{
ATLASSERT(m_tb.m_hWnd == NULL);
// create toolbar for the "x" button
m_tb.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CCS_NODIVIDER | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NOMOVEY | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, 0);
ATLASSERT(m_tb.IsWindow());
if(m_tb.m_hWnd != NULL)
{
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
m_tb.SetButtonStructSize();
TBBUTTON tbbtn = {};
tbbtn.idCommand = pT->m_nCloseBtnID;
tbbtn.fsState = TBSTATE_ENABLED;
tbbtn.fsStyle = BTNS_BUTTON;
m_tb.AddButtons(1, &tbbtn);
m_tb.SetBitmapSize(m_cxImageTB, m_cyImageTB);
m_tb.SetButtonSize(m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB);
if(IsVertical())
m_tb.SetWindowPos(NULL, m_cxyBorder + m_cxyBtnOffset, m_cxyBorder + m_cxyBtnOffset, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOACTIVATE);
else
m_tb.SetWindowPos(NULL, 0, 0, m_cxImageTB + m_cxyBtnAddTB, m_cyImageTB + m_cxyBtnAddTB + 1, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
}
}
void DestroyCloseButton()
{
if(m_tb.m_hWnd != NULL)
m_tb.DestroyWindow();
}
void CalcSize()
{
T* pT = static_cast<T*>(this);
CFontHandle font = pT->GetTitleFont();
if(font.IsNull())
font = (HFONT)::GetStockObject(SYSTEM_FONT);
LOGFONT lf = {};
font.GetLogFont(lf);
if(IsVertical())
{
m_cxyHeader = m_cxImageTB + m_cxyBtnAddTB + m_cxyBorder + 1;
}
else
{
int cyFont = abs(lf.lfHeight) + m_cxyBorder + 2 * m_cxyTextOffset;
int cyBtn = m_cyImageTB + m_cxyBtnAddTB + m_cxyBorder + 2 * m_cxyBtnOffset + 1;
m_cxyHeader = __max(cyFont, cyBtn);
}
}
HFONT GetTitleFont() const
{
return m_hFont;
}
BOOL GetToolTipText(LPNMHDR /*lpnmh*/)
{
return FALSE;
}
void DrawPaneTitle(CDCHandle dc)
{
RECT rect = {};
this->GetClientRect(&rect);
UINT uBorder = BF_LEFT | BF_TOP | BF_ADJUST;
if(IsVertical())
{
rect.right = rect.left + m_cxyHeader;
uBorder |= BF_BOTTOM;
}
else
{
rect.bottom = rect.top + m_cxyHeader;
uBorder |= BF_RIGHT;
}
if((m_dwExtendedStyle & PANECNT_NOBORDER) == 0)
{
if((m_dwExtendedStyle & PANECNT_FLATBORDER) != 0)
uBorder |= BF_FLAT;
dc.DrawEdge(&rect, EDGE_ETCHED, uBorder);
}
if((m_dwExtendedStyle & PANECNT_DIVIDER) != 0)
{
uBorder = BF_FLAT | BF_ADJUST | (IsVertical() ? BF_RIGHT : BF_BOTTOM);
dc.DrawEdge(&rect, BDR_SUNKENOUTER, uBorder);
}
// draw title text
dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
dc.SetBkMode(TRANSPARENT);
T* pT = static_cast<T*>(this);
HFONT hFontOld = dc.SelectFont(pT->GetTitleFont());
if(IsVertical())
{
rect.top += m_cxyTextOffset;
rect.bottom -= m_cxyTextOffset;
if(m_tb.m_hWnd != NULL)
rect.top += m_cxToolBar;;
RECT rcCalc = { rect.left, rect.bottom, rect.right, rect.top };
int cxFont = dc.DrawText(m_szTitle, -1, &rcCalc, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS | DT_CALCRECT);
RECT rcText = {};
rcText.left = (rect.right - rect.left - cxFont) / 2;
rcText.right = rcText.left + (rect.bottom - rect.top);
rcText.top = rect.bottom;
rcText.bottom = rect.top;
dc.DrawText(m_szTitle, -1, &rcText, DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS);
}
else
{
rect.left += m_cxyTextOffset;
rect.right -= m_cxyTextOffset;
if(m_tb.m_hWnd != NULL)
rect.right -= m_cxToolBar;;
dc.DrawText(m_szTitle, -1, &rect, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
}
dc.SelectFont(hFontOld);
}
void DrawPaneTitleBackground(CDCHandle dc)
{
RECT rect = {};
this->GetClientRect(&rect);
if(IsVertical())
rect.right = m_cxyHeader;
else
rect.bottom = m_cxyHeader;
if((m_dwExtendedStyle & PANECNT_GRADIENT) != 0)
dc.GradientFillRect(rect, ::GetSysColor(COLOR_WINDOW), ::GetSysColor(COLOR_3DFACE), IsVertical());
else
dc.FillRect(&rect, COLOR_3DFACE);
}
// called only if pane is empty
void DrawPane(CDCHandle dc)
{
RECT rect = {};
this->GetClientRect(&rect);
if(IsVertical())
rect.left += m_cxyHeader;
else
rect.top += m_cxyHeader;
if((this->GetExStyle() & WS_EX_CLIENTEDGE) == 0)
dc.DrawEdge(&rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
dc.FillRect(&rect, COLOR_APPWORKSPACE);
}
// drawing helper - draws "x" button image
void DrawButtonImage(CDCHandle dc, RECT& rcImage, HPEN hPen)
{
HPEN hPenOld = dc.SelectPen(hPen);
dc.MoveTo(rcImage.left, rcImage.top);
dc.LineTo(rcImage.right, rcImage.bottom);
dc.MoveTo(rcImage.left + 1, rcImage.top);
dc.LineTo(rcImage.right + 1, rcImage.bottom);
dc.MoveTo(rcImage.left, rcImage.bottom - 1);
dc.LineTo(rcImage.right, rcImage.top - 1);
dc.MoveTo(rcImage.left + 1, rcImage.bottom - 1);
dc.LineTo(rcImage.right + 1, rcImage.top - 1);
dc.SelectPen(hPenOld);
}
bool IsVertical() const
{
return ((m_dwExtendedStyle & PANECNT_VERTICAL) != 0);
}
};
class CPaneContainer : public CPaneContainerImpl<CPaneContainer>
{
public:
DECLARE_WND_CLASS_EX(_T("WTL_PaneContainer"), 0, -1)
};
///////////////////////////////////////////////////////////////////////////////
// CSortListViewCtrl - implements sorting for a listview control
// sort listview extended styles
#define SORTLV_USESHELLBITMAPS 0x00000001
// Notification sent to parent when sort column is changed by user clicking header.
#define SLVN_SORTCHANGED LVN_LAST
// A LPNMSORTLISTVIEW is sent with the SLVN_SORTCHANGED notification
typedef struct tagNMSORTLISTVIEW
{
NMHDR hdr;
int iNewSortColumn;
int iOldSortColumn;
} NMSORTLISTVIEW, *LPNMSORTLISTVIEW;
// Column sort types. Can be set on a per-column basis with the SetColumnSortType method.
enum
{
LVCOLSORT_NONE,
LVCOLSORT_TEXT, // default
LVCOLSORT_TEXTNOCASE,
LVCOLSORT_LONG,
LVCOLSORT_DOUBLE,
LVCOLSORT_DECIMAL,
LVCOLSORT_DATETIME,
LVCOLSORT_DATE,
LVCOLSORT_TIME,
LVCOLSORT_CUSTOM,
LVCOLSORT_LAST = LVCOLSORT_CUSTOM
};
template <class T>
class CSortListViewImpl
{
public:
enum
{
m_cchCmpTextMax = 32, // overrideable
m_cxSortImage = 16,
m_cySortImage = 15,
m_cxSortArrow = 11,
m_cySortArrow = 6,
m_iSortUp = 0, // index of sort bitmaps
m_iSortDown = 1,
m_nShellSortUpID = 133
};
// passed to LVCompare functions as lParam1 and lParam2
struct LVCompareParam
{
int iItem;
DWORD_PTR dwItemData;
union
{
long lValue;
double dblValue;
DECIMAL decValue;
LPCTSTR pszValue;
};
};
// passed to LVCompare functions as the lParamSort parameter
struct LVSortInfo
{
T* pT;
int iSortCol;
bool bDescending;
};
bool m_bSortDescending;
bool m_bCommCtrl6;
int m_iSortColumn;
CBitmap m_bmSort[2];
int m_fmtOldSortCol;
HBITMAP m_hbmOldSortCol;
DWORD m_dwSortLVExtendedStyle;
ATL::CSimpleArray<WORD> m_arrColSortType;
bool m_bUseWaitCursor;
CSortListViewImpl() :
m_bSortDescending(false),
m_bCommCtrl6(false),
m_iSortColumn(-1),
m_fmtOldSortCol(0),
m_hbmOldSortCol(NULL),
m_dwSortLVExtendedStyle(SORTLV_USESHELLBITMAPS),
m_bUseWaitCursor(true)
{
DWORD dwMajor = 0;
DWORD dwMinor = 0;
HRESULT hRet = ATL::AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
m_bCommCtrl6 = SUCCEEDED(hRet) && (dwMajor >= 6);
}
// Attributes
void SetSortColumn(int iCol)
{
T* pT = static_cast<T*>(this);
ATLASSERT(::IsWindow(pT->m_hWnd));
CHeaderCtrl header = pT->GetHeader();
ATLASSERT(header.m_hWnd != NULL);
ATLASSERT((iCol >= -1) && (iCol < m_arrColSortType.GetSize()));
int iOldSortCol = m_iSortColumn;
m_iSortColumn = iCol;
if(m_bCommCtrl6)
{
const int nMask = HDF_SORTUP | HDF_SORTDOWN;
HDITEM hditem = { HDI_FORMAT };
if((iOldSortCol != iCol) && (iOldSortCol >= 0) && header.GetItem(iOldSortCol, &hditem))
{
hditem.fmt &= ~nMask;
header.SetItem(iOldSortCol, &hditem);
}
if((iCol >= 0) && header.GetItem(iCol, &hditem))
{
hditem.fmt &= ~nMask;
hditem.fmt |= m_bSortDescending ? HDF_SORTDOWN : HDF_SORTUP;
header.SetItem(iCol, &hditem);
}
return;
}
if(m_bmSort[m_iSortUp].IsNull())
pT->CreateSortBitmaps();
// restore previous sort column's bitmap, if any, and format
HDITEM hditem = { HDI_BITMAP | HDI_FORMAT };
if((iOldSortCol != iCol) && (iOldSortCol >= 0))
{
hditem.hbm = m_hbmOldSortCol;
hditem.fmt = m_fmtOldSortCol;
header.SetItem(iOldSortCol, &hditem);
}
// save new sort column's bitmap and format, and add our sort bitmap
if((iCol >= 0) && header.GetItem(iCol, &hditem))
{
if(iOldSortCol != iCol)
{
m_fmtOldSortCol = hditem.fmt;
m_hbmOldSortCol = hditem.hbm;
}
hditem.fmt &= ~HDF_IMAGE;
hditem.fmt |= HDF_BITMAP | HDF_BITMAP_ON_RIGHT;
int i = m_bSortDescending ? m_iSortDown : m_iSortUp;
hditem.hbm = m_bmSort[i];
header.SetItem(iCol, &hditem);
}
}
int GetSortColumn() const
{
return m_iSortColumn;
}
void SetColumnSortType(int iCol, WORD wType)
{
ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
ATLASSERT((wType >= LVCOLSORT_NONE) && (wType <= LVCOLSORT_LAST));
m_arrColSortType[iCol] = wType;
}
WORD GetColumnSortType(int iCol) const
{
ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
return m_arrColSortType[iCol];
}
int GetColumnCount() const
{
const T* pT = static_cast<const T*>(this);
ATLASSERT(::IsWindow(pT->m_hWnd));
CHeaderCtrl header = pT->GetHeader();
return header.m_hWnd != NULL ? header.GetItemCount() : 0;
}
bool IsSortDescending() const
{
return m_bSortDescending;
}
DWORD GetSortListViewExtendedStyle() const
{
return m_dwSortLVExtendedStyle;
}
DWORD SetSortListViewExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
{
DWORD dwPrevStyle = m_dwSortLVExtendedStyle;
if(dwMask == 0)
m_dwSortLVExtendedStyle = dwExtendedStyle;
else
m_dwSortLVExtendedStyle = (m_dwSortLVExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
return dwPrevStyle;
}
// Operations
bool DoSortItems(int iCol, bool bDescending = false)
{
T* pT = static_cast<T*>(this);
ATLASSERT(::IsWindow(pT->m_hWnd));
ATLASSERT((iCol >= 0) && (iCol < m_arrColSortType.GetSize()));
WORD wType = m_arrColSortType[iCol];
if(wType == LVCOLSORT_NONE)
return false;
int nCount = pT->GetItemCount();
if(nCount < 2)
{
m_bSortDescending = bDescending;
SetSortColumn(iCol);
return true;
}
CWaitCursor waitCursor(false);
if(m_bUseWaitCursor)
waitCursor.Set();
LVCompareParam* pParam = NULL;
ATLTRY(pParam = new LVCompareParam[nCount]);
PFNLVCOMPARE pFunc = NULL;
TCHAR pszTemp[pT->m_cchCmpTextMax] = {};
bool bStrValue = false;
switch(wType)
{
case LVCOLSORT_TEXT:
pFunc = (PFNLVCOMPARE)pT->LVCompareText;
case LVCOLSORT_TEXTNOCASE:
if(pFunc == NULL)
pFunc = (PFNLVCOMPARE)pT->LVCompareTextNoCase;
case LVCOLSORT_CUSTOM:
{
if(pFunc == NULL)
pFunc = (PFNLVCOMPARE)pT->LVCompareCustom;
for(int i = 0; i < nCount; i++)
{
pParam[i].iItem = i;
pParam[i].dwItemData = pT->GetItemData(i);
pParam[i].pszValue = new TCHAR[pT->m_cchCmpTextMax];
pT->GetItemText(i, iCol, (LPTSTR)pParam[i].pszValue, pT->m_cchCmpTextMax);
pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
}
bStrValue = true;
}
break;
case LVCOLSORT_LONG:
{
pFunc = (PFNLVCOMPARE)pT->LVCompareLong;
for(int i = 0; i < nCount; i++)
{
pParam[i].iItem = i;
pParam[i].dwItemData = pT->GetItemData(i);
pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
pParam[i].lValue = pT->StrToLong(pszTemp);
pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
}
}
break;
case LVCOLSORT_DOUBLE:
{
pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
for(int i = 0; i < nCount; i++)
{
pParam[i].iItem = i;
pParam[i].dwItemData = pT->GetItemData(i);
pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
pParam[i].dblValue = pT->StrToDouble(pszTemp);
pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
}
}
break;
case LVCOLSORT_DECIMAL:
{
pFunc = (PFNLVCOMPARE)pT->LVCompareDecimal;
for(int i = 0; i < nCount; i++)
{
pParam[i].iItem = i;
pParam[i].dwItemData = pT->GetItemData(i);
pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
pT->StrToDecimal(pszTemp, &pParam[i].decValue);
pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
}
}
break;
case LVCOLSORT_DATETIME:
case LVCOLSORT_DATE:
case LVCOLSORT_TIME:
{
pFunc = (PFNLVCOMPARE)pT->LVCompareDouble;
DWORD dwFlags = LOCALE_NOUSEROVERRIDE;
if(wType == LVCOLSORT_DATE)
dwFlags |= VAR_DATEVALUEONLY;
else if(wType == LVCOLSORT_TIME)
dwFlags |= VAR_TIMEVALUEONLY;
for(int i = 0; i < nCount; i++)
{
pParam[i].iItem = i;
pParam[i].dwItemData = pT->GetItemData(i);
pT->GetItemText(i, iCol, pszTemp, pT->m_cchCmpTextMax);
pParam[i].dblValue = pT->DateStrToDouble(pszTemp, dwFlags);
pT->SetItemData(i, (DWORD_PTR)&pParam[i]);
}
}
break;
default:
ATLTRACE2(atlTraceUI, 0, _T("Unknown value for sort type in CSortListViewImpl::DoSortItems()\n"));
break;
} // switch(wType)
ATLASSERT(pFunc != NULL);
LVSortInfo lvsi = { pT, iCol, bDescending };
bool bRet = ((BOOL)pT->DefWindowProc(LVM_SORTITEMS, (WPARAM)&lvsi, (LPARAM)pFunc) != FALSE);
for(int i = 0; i < nCount; i++)
{
DWORD_PTR dwItemData = pT->GetItemData(i);
LVCompareParam* p = (LVCompareParam*)dwItemData;
ATLASSERT(p != NULL);
if(bStrValue)
delete [] (TCHAR*)p->pszValue;
pT->SetItemData(i, p->dwItemData);
}
delete [] pParam;
if(bRet)
{
m_bSortDescending = bDescending;
SetSortColumn(iCol);
}
if(m_bUseWaitCursor)
waitCursor.Restore();
return bRet;
}
void CreateSortBitmaps()
{
if((m_dwSortLVExtendedStyle & SORTLV_USESHELLBITMAPS) != 0)
{
bool bFree = false;
LPCTSTR pszModule = _T("shell32.dll");
HINSTANCE hShell = ::GetModuleHandle(pszModule);
if (hShell == NULL)
{
hShell = ::LoadLibrary(pszModule);
bFree = true;
}
if (hShell != NULL)
{
bool bSuccess = true;
for(int i = m_iSortUp; i <= m_iSortDown; i++)
{
if(!m_bmSort[i].IsNull())
m_bmSort[i].DeleteObject();
m_bmSort[i] = (HBITMAP)::LoadImage(hShell, MAKEINTRESOURCE(m_nShellSortUpID + i),
IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS);
if(m_bmSort[i].IsNull())
{
bSuccess = false;
break;
}
}
if(bFree)
::FreeLibrary(hShell);
if(bSuccess)
return;
}
}
T* pT = static_cast<T*>(this);
for(int i = m_iSortUp; i <= m_iSortDown; i++)
{
if(!m_bmSort[i].IsNull())
m_bmSort[i].DeleteObject();
CDC dcMem;
CClientDC dc(::GetDesktopWindow());
dcMem.CreateCompatibleDC(dc.m_hDC);
m_bmSort[i].CreateCompatibleBitmap(dc.m_hDC, m_cxSortImage, m_cySortImage);
HBITMAP hbmOld = dcMem.SelectBitmap(m_bmSort[i]);
RECT rc = { 0, 0, m_cxSortImage, m_cySortImage };
pT->DrawSortBitmap(dcMem.m_hDC, i, &rc);
dcMem.SelectBitmap(hbmOld);
dcMem.DeleteDC();
}
}
void NotifyParentSortChanged(int iNewSortCol, int iOldSortCol)
{
T* pT = static_cast<T*>(this);
int nID = pT->GetDlgCtrlID();
NMSORTLISTVIEW nm = { { pT->m_hWnd, (UINT_PTR)nID, SLVN_SORTCHANGED }, iNewSortCol, iOldSortCol };
::SendMessage(pT->GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nm);
}
// Overrideables
int CompareItemsCustom(LVCompareParam* /*pItem1*/, LVCompareParam* /*pItem2*/, int /*iSortCol*/)
{
// pItem1 and pItem2 contain valid iItem, dwItemData, and pszValue members.
// If item1 > item2 return 1, if item1 < item2 return -1, else return 0.
return 0;
}
void DrawSortBitmap(CDCHandle dc, int iBitmap, LPRECT prc)
{
dc.FillRect(prc, ::GetSysColorBrush(COLOR_BTNFACE));
HBRUSH hbrOld = dc.SelectBrush(::GetSysColorBrush(COLOR_BTNSHADOW));
CPen pen;
pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNSHADOW));
HPEN hpenOld = dc.SelectPen(pen);
POINT ptOrg = { (m_cxSortImage - m_cxSortArrow) / 2, (m_cySortImage - m_cySortArrow) / 2 };
if(iBitmap == m_iSortUp)
{
POINT pts[3] =
{
{ ptOrg.x + m_cxSortArrow / 2, ptOrg.y },
{ ptOrg.x, ptOrg.y + m_cySortArrow - 1 },
{ ptOrg.x + m_cxSortArrow - 1, ptOrg.y + m_cySortArrow - 1 }
};
dc.Polygon(pts, 3);
}
else
{
POINT pts[3] =
{
{ ptOrg.x, ptOrg.y },
{ ptOrg.x + m_cxSortArrow / 2, ptOrg.y + m_cySortArrow - 1 },
{ ptOrg.x + m_cxSortArrow - 1, ptOrg.y }
};
dc.Polygon(pts, 3);
}
dc.SelectBrush(hbrOld);
dc.SelectPen(hpenOld);
}
double DateStrToDouble(LPCTSTR lpstr, DWORD dwFlags)
{
ATLASSERT(lpstr != NULL);
if((lpstr == NULL) || (lpstr[0] == _T('\0')))
return 0;
USES_CONVERSION;
HRESULT hRet = E_FAIL;
DATE dRet = 0;
if (FAILED(hRet = ::VarDateFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, dwFlags, &dRet)))
{
ATLTRACE2(atlTraceUI, 0, _T("VarDateFromStr failed with result of 0x%8.8X\n"), hRet);
dRet = 0;
}
return dRet;
}
long StrToLong(LPCTSTR lpstr)
{
ATLASSERT(lpstr != NULL);
if((lpstr == NULL) || (lpstr[0] == _T('\0')))
return 0;
USES_CONVERSION;
HRESULT hRet = E_FAIL;
long lRet = 0;
if (FAILED(hRet = ::VarI4FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &lRet)))
{
ATLTRACE2(atlTraceUI, 0, _T("VarI4FromStr failed with result of 0x%8.8X\n"), hRet);
lRet = 0;
}
return lRet;
}
double StrToDouble(LPCTSTR lpstr)
{
ATLASSERT(lpstr != NULL);
if((lpstr == NULL) || (lpstr[0] == _T('\0')))
return 0;
USES_CONVERSION;
HRESULT hRet = E_FAIL;
double dblRet = 0;
if (FAILED(hRet = ::VarR8FromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &dblRet)))
{
ATLTRACE2(atlTraceUI, 0, _T("VarR8FromStr failed with result of 0x%8.8X\n"), hRet);
dblRet = 0;
}
return dblRet;
}
bool StrToDecimal(LPCTSTR lpstr, DECIMAL* pDecimal)
{
ATLASSERT(lpstr != NULL);
ATLASSERT(pDecimal != NULL);
if((lpstr == NULL) || (pDecimal == NULL))
return false;
USES_CONVERSION;
HRESULT hRet = E_FAIL;
if (FAILED(hRet = ::VarDecFromStr((LPOLESTR)T2COLE(lpstr), LANG_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, pDecimal)))
{
ATLTRACE2(atlTraceUI, 0, _T("VarDecFromStr failed with result of 0x%8.8X\n"), hRet);
pDecimal->Lo64 = 0;
pDecimal->Hi32 = 0;
pDecimal->signscale = 0;
return false;
}
return true;
}
// Overrideable PFNLVCOMPARE functions
static int CALLBACK LVCompareText(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = lstrcmp(pParam1->pszValue, pParam2->pszValue);
return pInfo->bDescending ? -nRet : nRet;
}
static int CALLBACK LVCompareTextNoCase(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = lstrcmpi(pParam1->pszValue, pParam2->pszValue);
return pInfo->bDescending ? -nRet : nRet;
}
static int CALLBACK LVCompareLong(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = 0;
if(pParam1->lValue > pParam2->lValue)
nRet = 1;
else if(pParam1->lValue < pParam2->lValue)
nRet = -1;
return pInfo->bDescending ? -nRet : nRet;
}
static int CALLBACK LVCompareDouble(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = 0;
if(pParam1->dblValue > pParam2->dblValue)
nRet = 1;
else if(pParam1->dblValue < pParam2->dblValue)
nRet = -1;
return pInfo->bDescending ? -nRet : nRet;
}
static int CALLBACK LVCompareCustom(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = pInfo->pT->CompareItemsCustom(pParam1, pParam2, pInfo->iSortCol);
return pInfo->bDescending ? -nRet : nRet;
}
static int CALLBACK LVCompareDecimal(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
ATLASSERT((lParam1 != NULL) && (lParam2 != NULL) && (lParamSort != NULL));
LVCompareParam* pParam1 = (LVCompareParam*)lParam1;
LVCompareParam* pParam2 = (LVCompareParam*)lParam2;
LVSortInfo* pInfo = (LVSortInfo*)lParamSort;
int nRet = (int)::VarDecCmp(&pParam1->decValue, &pParam2->decValue);
nRet--;
return pInfo->bDescending ? -nRet : nRet;
}
BEGIN_MSG_MAP(CSortListViewImpl)
MESSAGE_HANDLER(LVM_INSERTCOLUMN, OnInsertColumn)
MESSAGE_HANDLER(LVM_DELETECOLUMN, OnDeleteColumn)
NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, OnHeaderItemClick)
NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, OnHeaderItemClick)
MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
END_MSG_MAP()
LRESULT OnInsertColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
if(lRet == -1)
return -1;
WORD wType = 0;
m_arrColSortType.Add(wType);
int nCount = m_arrColSortType.GetSize();
ATLASSERT(nCount == GetColumnCount());
for(int i = nCount - 1; i > lRet; i--)
m_arrColSortType[i] = m_arrColSortType[i - 1];
m_arrColSortType[(int)lRet] = LVCOLSORT_TEXT;
if(lRet <= m_iSortColumn)
m_iSortColumn++;
return lRet;
}
LRESULT OnDeleteColumn(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
LRESULT lRet = pT->DefWindowProc(uMsg, wParam, lParam);
if(lRet == 0)
return 0;
int iCol = (int)wParam;
if(m_iSortColumn == iCol)
m_iSortColumn = -1;
else if(m_iSortColumn > iCol)
m_iSortColumn--;
m_arrColSortType.RemoveAt(iCol);
return lRet;
}
LRESULT OnHeaderItemClick(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
{
LPNMHEADER p = (LPNMHEADER)pnmh;
if(p->iButton == 0)
{
int iOld = m_iSortColumn;
bool bDescending = (m_iSortColumn == p->iItem) ? !m_bSortDescending : false;
if(DoSortItems(p->iItem, bDescending))
NotifyParentSortChanged(p->iItem, iOld);
}
bHandled = FALSE;
return 0;
}
LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam == SPI_SETNONCLIENTMETRICS)
GetSystemSettings();
bHandled = FALSE;
return 0;
}
void GetSystemSettings()
{
if(!m_bCommCtrl6 && !m_bmSort[m_iSortUp].IsNull())
{
T* pT = static_cast<T*>(this);
pT->CreateSortBitmaps();
if(m_iSortColumn != -1)
SetSortColumn(m_iSortColumn);
}
}
};
typedef ATL::CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_REPORT | LVS_SHOWSELALWAYS , WS_EX_CLIENTEDGE> CSortListViewCtrlTraits;
template <class T, class TBase = CListViewCtrl, class TWinTraits = CSortListViewCtrlTraits>
class ATL_NO_VTABLE CSortListViewCtrlImpl: public ATL::CWindowImpl<T, TBase, TWinTraits>, public CSortListViewImpl<T>
{
public:
DECLARE_WND_SUPERCLASS2(NULL, T, TBase::GetWndClassName())
bool SortItems(int iCol, bool bDescending = false)
{
return this->DoSortItems(iCol, bDescending);
}
BEGIN_MSG_MAP(CSortListViewCtrlImpl)
MESSAGE_HANDLER(LVM_INSERTCOLUMN, CSortListViewImpl<T>::OnInsertColumn)
MESSAGE_HANDLER(LVM_DELETECOLUMN, CSortListViewImpl<T>::OnDeleteColumn)
NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, CSortListViewImpl<T>::OnHeaderItemClick)
NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, CSortListViewImpl<T>::OnHeaderItemClick)
MESSAGE_HANDLER(WM_SETTINGCHANGE, CSortListViewImpl<T>::OnSettingChange)
END_MSG_MAP()
};
class CSortListViewCtrl : public CSortListViewCtrlImpl<CSortListViewCtrl>
{
public:
DECLARE_WND_SUPERCLASS(_T("WTL_SortListViewCtrl"), GetWndClassName())
};
///////////////////////////////////////////////////////////////////////////////
// CTabView - implements tab view window
// TabView Notifications
#define TBVN_PAGEACTIVATED (0U-741)
#define TBVN_CONTEXTMENU (0U-742)
// Notification data for TBVN_CONTEXTMENU
struct TBVCONTEXTMENUINFO
{
NMHDR hdr;
POINT pt;
};
typedef TBVCONTEXTMENUINFO* LPTBVCONTEXTMENUINFO;
template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CTabViewImpl : public ATL::CWindowImpl< T, TBase, TWinTraits >
{
public:
DECLARE_WND_CLASS_EX2(NULL, T, 0, COLOR_APPWORKSPACE)
// Declarations and enums
struct TABVIEWPAGE
{
HWND hWnd;
LPTSTR lpstrTitle;
LPVOID pData;
};
struct TCITEMEXTRA
{
TCITEMHEADER tciheader;
TABVIEWPAGE tvpage;
operator LPTCITEM() { return (LPTCITEM)this; }
};
enum
{
m_nTabID = 1313,
m_cxMoveMark = 6,
m_cyMoveMark = 3,
m_nMenuItemsMax = (ID_WINDOW_TABLAST - ID_WINDOW_TABFIRST + 1)
};
enum { _nAutoScrollTimerID = 4321 };
enum AutoScroll
{
_AUTOSCROLL_NONE = 0,
_AUTOSCROLL_LEFT = -1,
_AUTOSCROLL_RIGHT = 1
};
// Data members
ATL::CContainedWindowT<CTabCtrl> m_tab;
int m_cyTabHeight;
int m_nActivePage;
int m_nInsertItem;
POINT m_ptStartDrag;
CMenuHandle m_menu;
int m_cchTabTextLength;
int m_nMenuItemsCount;
ATL::CWindow m_wndTitleBar;
LPTSTR m_lpstrTitleBarBase;
int m_cchTitleBarLength;
CImageList m_ilDrag;
AutoScroll m_AutoScroll;
CUpDownCtrl m_ud;
bool m_bDestroyPageOnRemove:1;
bool m_bDestroyImageList:1;
bool m_bActivePageMenuItem:1;
bool m_bActiveAsDefaultMenuItem:1;
bool m_bEmptyMenuItem:1;
bool m_bWindowsMenuItem:1;
bool m_bNoTabDrag:1;
bool m_bNoTabDragAutoScroll:1;
// internal
bool m_bTabCapture:1;
bool m_bTabDrag:1;
bool m_bInternalFont:1;
// Constructor/destructor
CTabViewImpl() :
m_tab(this, 1),
m_cyTabHeight(0),
m_nActivePage(-1),
m_nInsertItem(-1),
m_cchTabTextLength(30),
m_nMenuItemsCount(10),
m_lpstrTitleBarBase(NULL),
m_cchTitleBarLength(100),
m_AutoScroll(_AUTOSCROLL_NONE),
m_bDestroyPageOnRemove(true),
m_bDestroyImageList(true),
m_bActivePageMenuItem(true),
m_bActiveAsDefaultMenuItem(false),
m_bEmptyMenuItem(false),
m_bWindowsMenuItem(false),
m_bNoTabDrag(false),
m_bNoTabDragAutoScroll(false),
m_bTabCapture(false),
m_bTabDrag(false),
m_bInternalFont(false)
{
m_ptStartDrag.x = 0;
m_ptStartDrag.y = 0;
}
~CTabViewImpl()
{
delete [] m_lpstrTitleBarBase;
}
// Message filter function - to be called from PreTranslateMessage of the main window
BOOL PreTranslateMessage(MSG* pMsg)
{
if(this->IsWindow() == FALSE)
return FALSE;
BOOL bRet = FALSE;
// Check for TabView built-in accelerators (Ctrl+Tab/Ctrl+Shift+Tab - next/previous page)
int nCount = GetPageCount();
if(nCount > 0)
{
bool bControl = (::GetKeyState(VK_CONTROL) < 0);
if((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_TAB) && bControl)
{
if(nCount > 1)
{
int nPage = m_nActivePage;
bool bShift = (::GetKeyState(VK_SHIFT) < 0);
if(bShift)
nPage = (nPage > 0) ? (nPage - 1) : (nCount - 1);
else
nPage = ((nPage >= 0) && (nPage < (nCount - 1))) ? (nPage + 1) : 0;
SetActivePage(nPage);
T* pT = static_cast<T*>(this);
pT->OnPageActivated(m_nActivePage);
}
bRet = TRUE;
}
}
// If we are doing drag-drop, check for Escape key that cancels it
if(bRet == FALSE)
{
if(m_bTabCapture && (pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
{
::ReleaseCapture();
bRet = TRUE;
}
}
// Pass the message to the active page
if(bRet == FALSE)
{
if(m_nActivePage != -1)
bRet = (BOOL)::SendMessage(GetPageHWND(m_nActivePage), WM_FORWARDMSG, 0, (LPARAM)pMsg);
}
return bRet;
}
// Attributes
int GetPageCount() const
{
ATLASSERT(::IsWindow(this->m_hWnd));
return m_tab.GetItemCount();
}
int GetActivePage() const
{
return m_nActivePage;
}
void SetActivePage(int nPage)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
T* pT = static_cast<T*>(this);
this->SetRedraw(FALSE);
if(m_nActivePage != -1)
::ShowWindow(GetPageHWND(m_nActivePage), SW_HIDE);
m_nActivePage = nPage;
m_tab.SetCurSel(m_nActivePage);
::ShowWindow(GetPageHWND(m_nActivePage), SW_SHOW);
pT->UpdateLayout();
this->SetRedraw(TRUE);
this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
if(::GetFocus() != m_tab.m_hWnd)
::SetFocus(GetPageHWND(m_nActivePage));
pT->UpdateTitleBar();
pT->UpdateMenu();
}
HIMAGELIST GetImageList() const
{
ATLASSERT(::IsWindow(this->m_hWnd));
return m_tab.GetImageList();
}
HIMAGELIST SetImageList(HIMAGELIST hImageList)
{
ATLASSERT(::IsWindow(this->m_hWnd));
return m_tab.SetImageList(hImageList);
}
void SetWindowMenu(HMENU hMenu)
{
ATLASSERT(::IsWindow(this->m_hWnd));
m_menu = hMenu;
T* pT = static_cast<T*>(this);
pT->UpdateMenu();
}
void SetTitleBarWindow(HWND hWnd)
{
ATLASSERT(::IsWindow(this->m_hWnd));
delete [] m_lpstrTitleBarBase;
m_lpstrTitleBarBase = NULL;
m_wndTitleBar = hWnd;
if(hWnd == NULL)
return;
int cchLen = m_wndTitleBar.GetWindowTextLength() + 1;
ATLTRY(m_lpstrTitleBarBase = new TCHAR[cchLen]);
if(m_lpstrTitleBarBase != NULL)
{
m_wndTitleBar.GetWindowText(m_lpstrTitleBarBase, cchLen);
T* pT = static_cast<T*>(this);
pT->UpdateTitleBar();
}
}
// Page attributes
HWND GetPageHWND(int nPage) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_PARAM;
m_tab.GetItem(nPage, tcix);
return tcix.tvpage.hWnd;
}
LPCTSTR GetPageTitle(int nPage) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_PARAM;
if(m_tab.GetItem(nPage, tcix) == FALSE)
return NULL;
return tcix.tvpage.lpstrTitle;
}
bool SetPageTitle(int nPage, LPCTSTR lpstrTitle)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
T* pT = static_cast<T*>(this);
int cchBuff = lstrlen(lpstrTitle) + 1;
LPTSTR lpstrBuff = NULL;
ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
if(lpstrBuff == NULL)
return false;
ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_PARAM;
if(m_tab.GetItem(nPage, tcix) == FALSE)
return false;
ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
if(lpstrTabText == NULL)
return false;
delete [] tcix.tvpage.lpstrTitle;
pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);
tcix.tciheader.mask = TCIF_TEXT | TCIF_PARAM;
tcix.tciheader.pszText = lpstrTabText;
tcix.tvpage.lpstrTitle = lpstrBuff;
if(m_tab.SetItem(nPage, tcix) == FALSE)
return false;
pT->UpdateTitleBar();
pT->UpdateMenu();
return true;
}
LPVOID GetPageData(int nPage) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_PARAM;
m_tab.GetItem(nPage, tcix);
return tcix.tvpage.pData;
}
LPVOID SetPageData(int nPage, LPVOID pData)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_PARAM;
m_tab.GetItem(nPage, tcix);
LPVOID pDataOld = tcix.tvpage.pData;
tcix.tvpage.pData = pData;
m_tab.SetItem(nPage, tcix);
return pDataOld;
}
int GetPageImage(int nPage) const
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_IMAGE;
m_tab.GetItem(nPage, tcix);
return tcix.tciheader.iImage;
}
int SetPageImage(int nPage, int nImage)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_IMAGE;
m_tab.GetItem(nPage, tcix);
int nImageOld = tcix.tciheader.iImage;
tcix.tciheader.iImage = nImage;
m_tab.SetItem(nPage, tcix);
return nImageOld;
}
// Operations
bool AddPage(HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
{
return InsertPage(GetPageCount(), hWndView, lpstrTitle, nImage, pData);
}
bool InsertPage(int nPage, HWND hWndView, LPCTSTR lpstrTitle, int nImage = -1, LPVOID pData = NULL)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT((nPage == GetPageCount()) || IsValidPageIndex(nPage));
T* pT = static_cast<T*>(this);
int cchBuff = lstrlen(lpstrTitle) + 1;
LPTSTR lpstrBuff = NULL;
ATLTRY(lpstrBuff = new TCHAR[cchBuff]);
if(lpstrBuff == NULL)
return false;
ATL::Checked::tcscpy_s(lpstrBuff, cchBuff, lpstrTitle);
ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
if(lpstrTabText == NULL)
return false;
pT->ShortenTitle(lpstrTitle, lpstrTabText, m_cchTabTextLength + 1);
this->SetRedraw(FALSE);
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
tcix.tciheader.pszText = lpstrTabText;
tcix.tciheader.iImage = nImage;
tcix.tvpage.hWnd = hWndView;
tcix.tvpage.lpstrTitle = lpstrBuff;
tcix.tvpage.pData = pData;
int nItem = m_tab.InsertItem(nPage, tcix);
if(nItem == -1)
{
delete [] lpstrBuff;
this->SetRedraw(TRUE);
return false;
}
// adjust active page index, if inserted before it
if(nPage <= m_nActivePage)
m_nActivePage++;
SetActivePage(nItem);
pT->OnPageActivated(m_nActivePage);
if(GetPageCount() == 1)
pT->ShowTabControl(true);
pT->UpdateLayout();
this->SetRedraw(TRUE);
this->RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
return true;
}
void RemovePage(int nPage)
{
ATLASSERT(::IsWindow(this->m_hWnd));
ATLASSERT(IsValidPageIndex(nPage));
T* pT = static_cast<T*>(this);
this->SetRedraw(FALSE);
if(GetPageCount() == 1)
pT->ShowTabControl(false);
if(m_bDestroyPageOnRemove)
::DestroyWindow(GetPageHWND(nPage));
else
::ShowWindow(GetPageHWND(nPage), SW_HIDE);
LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(nPage);
delete [] lpstrTitle;
ATLVERIFY(m_tab.DeleteItem(nPage) != FALSE);
if(m_nActivePage == nPage)
{
m_nActivePage = -1;
if(nPage > 0)
{
SetActivePage(nPage - 1);
}
else if(GetPageCount() > 0)
{
SetActivePage(nPage);
}
else
{
this->SetRedraw(TRUE);
this->Invalidate();
this->UpdateWindow();
pT->UpdateTitleBar();
pT->UpdateMenu();
}
}
else
{
nPage = (nPage < m_nActivePage) ? (m_nActivePage - 1) : m_nActivePage;
m_nActivePage = -1;
SetActivePage(nPage);
}
pT->OnPageActivated(m_nActivePage);
}
void RemoveAllPages()
{
ATLASSERT(::IsWindow(this->m_hWnd));
if(GetPageCount() == 0)
return;
T* pT = static_cast<T*>(this);
this->SetRedraw(FALSE);
pT->ShowTabControl(false);
for(int i = 0; i < GetPageCount(); i++)
{
if(m_bDestroyPageOnRemove)
::DestroyWindow(GetPageHWND(i));
else
::ShowWindow(GetPageHWND(i), SW_HIDE);
LPTSTR lpstrTitle = (LPTSTR)GetPageTitle(i);
delete [] lpstrTitle;
}
m_tab.DeleteAllItems();
m_nActivePage = -1;
pT->OnPageActivated(m_nActivePage);
this->SetRedraw(TRUE);
this->Invalidate();
this->UpdateWindow();
pT->UpdateTitleBar();
pT->UpdateMenu();
}
int PageIndexFromHwnd(HWND hWnd) const
{
int nIndex = -1;
for(int i = 0; i < GetPageCount(); i++)
{
if(GetPageHWND(i) == hWnd)
{
nIndex = i;
break;
}
}
return nIndex;
}
void BuildWindowMenu(HMENU hMenu, int nMenuItemsCount = 10, bool bEmptyMenuItem = true, bool bWindowsMenuItem = true, bool bActivePageMenuItem = true, bool bActiveAsDefaultMenuItem = false)
{
ATLASSERT(::IsWindow(this->m_hWnd));
CMenuHandle menu = hMenu;
T* pT = static_cast<T*>(this);
(void)pT; // avoid level 4 warning
int nFirstPos = 0;
// Find first menu item in our range
for(nFirstPos = 0; nFirstPos < menu.GetMenuItemCount(); nFirstPos++)
{
UINT nID = menu.GetMenuItemID(nFirstPos);
if(((nID >= ID_WINDOW_TABFIRST) && (nID <= ID_WINDOW_TABLAST)) || (nID == ID_WINDOW_SHOWTABLIST))
break;
}
// Remove all menu items for tab pages
BOOL bRet = TRUE;
while(bRet != FALSE)
bRet = menu.DeleteMenu(nFirstPos, MF_BYPOSITION);
// Add separator if it's not already there
int nPageCount = GetPageCount();
if((bWindowsMenuItem || (nPageCount > 0)) && (nFirstPos > 0))
{
CMenuItemInfo mii;
mii.fMask = MIIM_TYPE;
menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
if((nFirstPos <= 0) || ((mii.fType & MFT_SEPARATOR) == 0))
{
menu.AppendMenu(MF_SEPARATOR);
nFirstPos++;
}
}
// Add menu items for all pages
if(nPageCount > 0)
{
// Append menu items for all pages
const int cchPrefix = 3; // 2 digits + space
nMenuItemsCount = __min(__min(nPageCount, nMenuItemsCount), (int)m_nMenuItemsMax);
ATLASSERT(nMenuItemsCount < 100); // 2 digits only
if(nMenuItemsCount >= 100)
nMenuItemsCount = 99;
for(int i = 0; i < nMenuItemsCount; i++)
{
LPCTSTR lpstrTitle = GetPageTitle(i);
int nLen = lstrlen(lpstrTitle);
ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
LPTSTR lpstrText = buff.Allocate(cchPrefix + nLen + 1);
ATLASSERT(lpstrText != NULL);
if(lpstrText != NULL)
{
LPCTSTR lpstrFormat = (i < 9) ? _T("&%i %s") : _T("%i %s");
_stprintf_s(lpstrText, cchPrefix + nLen + 1, lpstrFormat, i + 1, lpstrTitle);
menu.AppendMenu(MF_STRING, ID_WINDOW_TABFIRST + i, lpstrText);
}
}
// Mark active page
if(bActivePageMenuItem && (m_nActivePage != -1))
{
if(bActiveAsDefaultMenuItem)
{
menu.SetMenuDefaultItem((UINT)-1, TRUE);
menu.SetMenuDefaultItem(nFirstPos + m_nActivePage, TRUE);
}
else
{
menu.CheckMenuRadioItem(nFirstPos, nFirstPos + nMenuItemsCount, nFirstPos + m_nActivePage, MF_BYPOSITION);
}
}
}
else
{
if(bEmptyMenuItem)
{
menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_TABFIRST, pT->GetEmptyListText());
menu.EnableMenuItem(ID_WINDOW_TABFIRST, MF_GRAYED);
}
// Remove separator if nothing else is there
if(!bEmptyMenuItem && !bWindowsMenuItem && (nFirstPos > 0))
{
CMenuItemInfo mii;
mii.fMask = MIIM_TYPE;
menu.GetMenuItemInfo(nFirstPos - 1, TRUE, &mii);
if((mii.fType & MFT_SEPARATOR) != 0)
menu.DeleteMenu(nFirstPos - 1, MF_BYPOSITION);
}
}
// Add "Windows..." menu item
if(bWindowsMenuItem)
menu.AppendMenu(MF_BYPOSITION | MF_STRING, ID_WINDOW_SHOWTABLIST, pT->GetWindowsMenuItemText());
}
BOOL SubclassWindow(HWND hWnd)
{
BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
if(bRet != FALSE)
{
T* pT = static_cast<T*>(this);
pT->CreateTabControl();
pT->UpdateLayout();
}
return bRet;
}
// Message map and handlers
BEGIN_MSG_MAP(CTabViewImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER(WM_CONTEXTMENU, OnTabContextMenu)
NOTIFY_HANDLER(m_nTabID, TCN_SELCHANGE, OnTabChanged)
NOTIFY_ID_HANDLER(m_nTabID, OnTabNotification)
NOTIFY_CODE_HANDLER(TTN_GETDISPINFO, OnTabGetDispInfo)
FORWARD_NOTIFICATIONS()
ALT_MSG_MAP(1) // tab control
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnTabLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnTabLButtonUp)
MESSAGE_HANDLER(WM_CAPTURECHANGED, OnTabCaptureChanged)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnTabMouseMove)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->CreateTabControl();
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
RemoveAllPages();
if(m_bDestroyImageList)
{
CImageList il = m_tab.SetImageList(NULL);
if(il.m_hImageList != NULL)
il.Destroy();
}
if(m_bInternalFont)
{
HFONT hFont = m_tab.GetFont();
m_tab.SetFont(NULL, FALSE);
::DeleteObject(hFont);
m_bInternalFont = false;
}
m_ud.m_hWnd = NULL;
return 0;
}
LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
return 0;
}
LRESULT OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
if(m_nActivePage != -1)
::SetFocus(GetPageHWND(m_nActivePage));
return 0;
}
LRESULT OnGetFont(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return m_tab.SendMessage(WM_GETFONT);
}
LRESULT OnSetFont(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
if(m_bInternalFont)
{
HFONT hFont = m_tab.GetFont();
m_tab.SetFont(NULL, FALSE);
::DeleteObject(hFont);
m_bInternalFont = false;
}
m_tab.SendMessage(WM_SETFONT, wParam, lParam);
T* pT = static_cast<T*>(this);
m_cyTabHeight = pT->CalcTabHeight();
if((BOOL)lParam != FALSE)
pT->UpdateLayout();
return 0;
}
LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam == _nAutoScrollTimerID)
{
T* pT = static_cast<T*>(this);
pT->DoAutoScroll();
}
else
{
bHandled = FALSE;
}
return 0;
}
LRESULT OnTabContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
int nPage = m_nActivePage;
bool bAction = false;
if((HWND)wParam == m_tab.m_hWnd)
{
if((pt.x == -1) && (pt.y == -1)) // keyboard
{
RECT rect = {};
m_tab.GetItemRect(m_nActivePage, &rect);
pt.x = rect.left;
pt.y = rect.bottom;
m_tab.ClientToScreen(&pt);
bAction = true;
}
else if(::WindowFromPoint(pt) == m_tab.m_hWnd)
{
TCHITTESTINFO hti = {};
hti.pt = pt;
this->ScreenToClient(&hti.pt);
nPage = m_tab.HitTest(&hti);
bAction = true;
}
}
if(bAction)
{
T* pT = static_cast<T*>(this);
pT->OnContextMenu(nPage, pt);
}
else
{
bHandled = FALSE;
}
return 0;
}
LRESULT OnTabChanged(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
{
SetActivePage(m_tab.GetCurSel());
T* pT = static_cast<T*>(this);
pT->OnPageActivated(m_nActivePage);
return 0;
}
LRESULT OnTabNotification(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
{
// nothing to do - this just blocks all tab control
// notifications from being propagated further
return 0;
}
LRESULT OnTabGetDispInfo(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled)
{
LPNMTTDISPINFO pTTDI = (LPNMTTDISPINFO)pnmh;
if(pTTDI->hdr.hwndFrom == m_tab.GetTooltips())
{
T* pT = static_cast<T*>(this);
pT->UpdateTooltipText(pTTDI);
}
else
{
bHandled = FALSE;
}
return 0;
}
// Tab control message handlers
LRESULT OnTabLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(!m_bNoTabDrag && (m_tab.GetItemCount() > 1))
{
m_bTabCapture = true;
m_tab.SetCapture();
m_ptStartDrag.x = GET_X_LPARAM(lParam);
m_ptStartDrag.y = GET_Y_LPARAM(lParam);
}
bHandled = FALSE;
return 0;
}
LRESULT OnTabLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(m_bTabCapture)
{
if(m_bTabDrag)
{
T* pT = static_cast<T*>(this);
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
int nItem = pT->DragHitTest(pt);
if(nItem != -1)
MovePage(m_nActivePage, nItem);
}
::ReleaseCapture();
}
bHandled = FALSE;
return 0;
}
LRESULT OnTabCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_bTabCapture)
{
m_bTabCapture = false;
if(m_bTabDrag)
{
m_bTabDrag = false;
T* pT = static_cast<T*>(this);
if(!m_bNoTabDragAutoScroll)
pT->StartStopAutoScroll(-1);
pT->DrawMoveMark(-1);
m_ilDrag.DragLeave(GetDesktopWindow());
m_ilDrag.EndDrag();
m_ilDrag.Destroy();
m_ilDrag.m_hImageList = NULL;
}
}
bHandled = FALSE;
return 0;
}
LRESULT OnTabMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
if(m_bTabCapture)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
if(!m_bTabDrag)
{
if((abs(m_ptStartDrag.x - GET_X_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CXDRAG)) ||
(abs(m_ptStartDrag.y - GET_Y_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CYDRAG)))
{
T* pT = static_cast<T*>(this);
pT->GenerateDragImage(m_nActivePage);
int cxCursor = ::GetSystemMetrics(SM_CXCURSOR);
int cyCursor = ::GetSystemMetrics(SM_CYCURSOR);
m_ilDrag.BeginDrag(0, -(cxCursor / 2), -(cyCursor / 2));
POINT ptEnter = m_ptStartDrag;
m_tab.ClientToScreen(&ptEnter);
m_ilDrag.DragEnter(GetDesktopWindow(), ptEnter);
m_bTabDrag = true;
}
}
if(m_bTabDrag)
{
T* pT = static_cast<T*>(this);
int nItem = pT->DragHitTest(pt);
pT->SetMoveCursor(nItem != -1);
if(m_nInsertItem != nItem)
pT->DrawMoveMark(nItem);
if(!m_bNoTabDragAutoScroll)
pT->StartStopAutoScroll(pt.x);
m_ilDrag.DragShowNolock((nItem != -1) ? TRUE : FALSE);
m_tab.ClientToScreen(&pt);
m_ilDrag.DragMove(pt);
bHandled = TRUE;
}
}
return 0;
}
// Implementation helpers
bool IsValidPageIndex(int nPage) const
{
return ((nPage >= 0) && (nPage < GetPageCount()));
}
bool MovePage(int nMovePage, int nInsertBeforePage)
{
ATLASSERT(IsValidPageIndex(nMovePage));
ATLASSERT(IsValidPageIndex(nInsertBeforePage));
if(!IsValidPageIndex(nMovePage) || !IsValidPageIndex(nInsertBeforePage))
return false;
if(nMovePage == nInsertBeforePage)
return true; // nothing to do
ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
LPTSTR lpstrTabText = buff.Allocate(m_cchTabTextLength + 1);
if(lpstrTabText == NULL)
return false;
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM;
tcix.tciheader.pszText = lpstrTabText;
tcix.tciheader.cchTextMax = m_cchTabTextLength + 1;
BOOL bRet = m_tab.GetItem(nMovePage, tcix);
ATLASSERT(bRet != FALSE);
if(bRet == FALSE)
return false;
int nInsertItem = (nInsertBeforePage > nMovePage) ? nInsertBeforePage + 1 : nInsertBeforePage;
int nNewItem = m_tab.InsertItem(nInsertItem, tcix);
ATLASSERT(nNewItem == nInsertItem);
if(nNewItem != nInsertItem)
{
ATLVERIFY(m_tab.DeleteItem(nNewItem));
return false;
}
if(nMovePage > nInsertBeforePage)
ATLVERIFY(m_tab.DeleteItem(nMovePage + 1) != FALSE);
else if(nMovePage < nInsertBeforePage)
ATLVERIFY(m_tab.DeleteItem(nMovePage) != FALSE);
SetActivePage(nInsertBeforePage);
T* pT = static_cast<T*>(this);
pT->OnPageActivated(m_nActivePage);
return true;
}
// Implementation overrideables
bool CreateTabControl()
{
m_tab.Create(this->m_hWnd, this->rcDefault, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | TCS_TOOLTIPS, 0, m_nTabID);
ATLASSERT(m_tab.m_hWnd != NULL);
if(m_tab.m_hWnd == NULL)
return false;
m_tab.SetFont(AtlCreateControlFont());
m_bInternalFont = true;
m_tab.SetItemExtra(sizeof(TABVIEWPAGE));
T* pT = static_cast<T*>(this);
m_cyTabHeight = pT->CalcTabHeight();
return true;
}
int CalcTabHeight()
{
int nCount = m_tab.GetItemCount();
TCHAR szText[] = _T("NS");
TCITEMEXTRA tcix = {};
tcix.tciheader.mask = TCIF_TEXT;
tcix.tciheader.pszText = szText;
int nIndex = m_tab.InsertItem(nCount, tcix);
RECT rect = { 0, 0, 1000, 1000 };
m_tab.AdjustRect(FALSE, &rect);
RECT rcWnd = { 0, 0, 1000, rect.top };
::AdjustWindowRectEx(&rcWnd, m_tab.GetStyle(), FALSE, m_tab.GetExStyle());
int nHeight = rcWnd.bottom - rcWnd.top;
m_tab.DeleteItem(nIndex);
return nHeight;
}
void ShowTabControl(bool bShow)
{
m_tab.ShowWindow(bShow ? SW_SHOWNOACTIVATE : SW_HIDE);
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
void UpdateLayout()
{
RECT rect = {};
this->GetClientRect(&rect);
int cyOffset = 0;
if(m_tab.IsWindow() && ((m_tab.GetStyle() & WS_VISIBLE) != 0))
{
m_tab.SetWindowPos(NULL, 0, 0, rect.right - rect.left, m_cyTabHeight, SWP_NOZORDER);
cyOffset = m_cyTabHeight;
}
if(m_nActivePage != -1)
::SetWindowPos(GetPageHWND(m_nActivePage), NULL, 0, cyOffset, rect.right - rect.left, rect.bottom - rect.top - cyOffset, SWP_NOZORDER);
}
void UpdateMenu()
{
if(m_menu.m_hMenu != NULL)
BuildWindowMenu(m_menu, m_nMenuItemsCount, m_bEmptyMenuItem, m_bWindowsMenuItem, m_bActivePageMenuItem, m_bActiveAsDefaultMenuItem);
}
void UpdateTitleBar()
{
if(!m_wndTitleBar.IsWindow() || (m_lpstrTitleBarBase == NULL))
return; // nothing to do
if(m_nActivePage != -1)
{
T* pT = static_cast<T*>(this);
LPCTSTR lpstrTitle = pT->GetPageTitle(m_nActivePage);
LPCTSTR lpstrDivider = pT->GetTitleDividerText();
int cchBuffer = m_cchTitleBarLength + lstrlen(lpstrDivider) + lstrlen(m_lpstrTitleBarBase) + 1;
ATL::CTempBuffer<TCHAR, _WTL_STACK_ALLOC_THRESHOLD> buff;
LPTSTR lpstrPageTitle = buff.Allocate(cchBuffer);
ATLASSERT(lpstrPageTitle != NULL);
if(lpstrPageTitle != NULL)
{
pT->ShortenTitle(lpstrTitle, lpstrPageTitle, m_cchTitleBarLength + 1);
ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, lpstrDivider);
ATL::Checked::tcscat_s(lpstrPageTitle, cchBuffer, m_lpstrTitleBarBase);
}
else
{
lpstrPageTitle = m_lpstrTitleBarBase;
}
m_wndTitleBar.SetWindowText(lpstrPageTitle);
}
else
{
m_wndTitleBar.SetWindowText(m_lpstrTitleBarBase);
}
}
void DrawMoveMark(int nItem)
{
T* pT = static_cast<T*>(this);
if(m_nInsertItem != -1)
{
RECT rect = {};
pT->GetMoveMarkRect(rect);
m_tab.InvalidateRect(&rect);
}
m_nInsertItem = nItem;
if(m_nInsertItem != -1)
{
CClientDC dc(m_tab.m_hWnd);
RECT rect = {};
pT->GetMoveMarkRect(rect);
CPen pen;
pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_WINDOWTEXT));
CBrush brush;
brush.CreateSolidBrush(::GetSysColor(COLOR_WINDOWTEXT));
HPEN hPenOld = dc.SelectPen(pen);
HBRUSH hBrushOld = dc.SelectBrush(brush);
int x = rect.left;
int y = rect.top;
POINT ptsTop[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y + m_cyMoveMark } };
dc.Polygon(ptsTop, 3);
y = rect.bottom - 1;
POINT ptsBottom[3] = { { x, y }, { x + m_cxMoveMark, y }, { x + (m_cxMoveMark / 2), y - m_cyMoveMark } };
dc.Polygon(ptsBottom, 3);
dc.SelectPen(hPenOld);
dc.SelectBrush(hBrushOld);
}
}
void GetMoveMarkRect(RECT& rect) const
{
m_tab.GetClientRect(&rect);
RECT rcItem = {};
m_tab.GetItemRect(m_nInsertItem, &rcItem);
if(m_nInsertItem <= m_nActivePage)
{
rect.left = rcItem.left - m_cxMoveMark / 2 - 1;
rect.right = rcItem.left + m_cxMoveMark / 2;
}
else
{
rect.left = rcItem.right - m_cxMoveMark / 2 - 1;
rect.right = rcItem.right + m_cxMoveMark / 2;
}
}
void SetMoveCursor(bool bCanMove)
{
::SetCursor(::LoadCursor(NULL, bCanMove ? IDC_ARROW : IDC_NO));
}
void GenerateDragImage(int nItem)
{
ATLASSERT(IsValidPageIndex(nItem));
RECT rcItem = {};
m_tab.GetItemRect(nItem, &rcItem);
::InflateRect(&rcItem, 2, 2); // make bigger to cover selected item
ATLASSERT(m_ilDrag.m_hImageList == NULL);
m_ilDrag.Create(rcItem.right - rcItem.left, rcItem.bottom - rcItem.top, ILC_COLORDDB | ILC_MASK, 1, 1);
CClientDC dc(this->m_hWnd);
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
ATLASSERT(dcMem.m_hDC != NULL);
dcMem.SetViewportOrg(-rcItem.left, -rcItem.top);
CBitmap bmp;
bmp.CreateCompatibleBitmap(dc, rcItem.right - rcItem.left, rcItem.bottom - rcItem.top);
ATLASSERT(bmp.m_hBitmap != NULL);
HBITMAP hBmpOld = dcMem.SelectBitmap(bmp);
m_tab.SendMessage(WM_PRINTCLIENT, (WPARAM)dcMem.m_hDC);
dcMem.SelectBitmap(hBmpOld);
ATLVERIFY(m_ilDrag.Add(bmp.m_hBitmap, RGB(255, 0, 255)) != -1);
}
void ShortenTitle(LPCTSTR lpstrTitle, LPTSTR lpstrShortTitle, int cchShortTitle)
{
if(lstrlen(lpstrTitle) >= cchShortTitle)
{
LPCTSTR lpstrEllipsis = _T("...");
int cchEllipsis = lstrlen(lpstrEllipsis);
ATL::Checked::tcsncpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle, cchShortTitle - cchEllipsis - 1);
ATL::Checked::tcscat_s(lpstrShortTitle, cchShortTitle, lpstrEllipsis);
}
else
{
ATL::Checked::tcscpy_s(lpstrShortTitle, cchShortTitle, lpstrTitle);
}
}
void UpdateTooltipText(LPNMTTDISPINFO pTTDI)
{
ATLASSERT(pTTDI != NULL);
pTTDI->lpszText = (LPTSTR)GetPageTitle((int)pTTDI->hdr.idFrom);
}
int DragHitTest(POINT pt) const
{
RECT rect = {};
this->GetClientRect(&rect);
if(::PtInRect(&rect, pt) == FALSE)
return -1;
m_tab.GetClientRect(&rect);
TCHITTESTINFO hti = {};
hti.pt.x = pt.x;
hti.pt.y = rect.bottom / 2; // use middle to ignore
int nItem = m_tab.HitTest(&hti);
if(nItem == -1)
{
int nLast = m_tab.GetItemCount() - 1;
RECT rcItem = {};
m_tab.GetItemRect(nLast, &rcItem);
if(pt.x >= rcItem.right)
nItem = nLast;
}
return nItem;
}
void StartStopAutoScroll(int x)
{
AutoScroll scroll = _AUTOSCROLL_NONE;
if(x != -1)
{
RECT rect = {};
m_tab.GetClientRect(&rect);
int dx = ::GetSystemMetrics(SM_CXVSCROLL);
if((x >= 0) && (x < dx))
{
RECT rcItem = {};
m_tab.GetItemRect(0, &rcItem);
if(rcItem.left < rect.left)
scroll = _AUTOSCROLL_LEFT;
}
else if((x >= (rect.right - dx)) && (x < rect.right))
{
RECT rcItem = {};
m_tab.GetItemRect(m_tab.GetItemCount() - 1, &rcItem);
if(rcItem.right > rect.right)
scroll = _AUTOSCROLL_RIGHT;
}
}
if(scroll != _AUTOSCROLL_NONE)
{
if(m_ud.m_hWnd == NULL)
m_ud = m_tab.GetWindow(GW_CHILD);
if(m_AutoScroll != scroll)
{
m_AutoScroll = scroll;
this->SetTimer(_nAutoScrollTimerID, 300);
}
}
else
{
this->KillTimer(_nAutoScrollTimerID);
m_AutoScroll = _AUTOSCROLL_NONE;
}
}
void DoAutoScroll()
{
ATLASSERT(m_AutoScroll != _AUTOSCROLL_NONE);
int nMin = -1, nMax = -1;
m_ud.GetRange(nMin, nMax);
int nPos = m_ud.GetPos();
int nNewPos = -1;
if((m_AutoScroll == _AUTOSCROLL_LEFT) && (nPos > nMin))
nNewPos = nPos - 1;
else if((m_AutoScroll == _AUTOSCROLL_RIGHT) && (nPos < nMax))
nNewPos = nPos + 1;
if(nNewPos != -1)
{
m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, nNewPos));
m_tab.SendMessage(WM_HSCROLL, MAKEWPARAM(SB_ENDSCROLL, 0));
POINT pt = {};
::GetCursorPos(&pt);
m_tab.ScreenToClient(&pt);
m_tab.SendMessage(WM_MOUSEMOVE, NULL, MAKELPARAM(pt.x, pt.y));
}
}
// Text for menu items and title bar - override to provide different strings
static LPCTSTR GetEmptyListText()
{
return _T("(Empty)");
}
static LPCTSTR GetWindowsMenuItemText()
{
return _T("&Windows...");
}
static LPCTSTR GetTitleDividerText()
{
return _T(" - ");
}
// Notifications - override to provide different behavior
void OnPageActivated(int nPage)
{
NMHDR nmhdr = {};
nmhdr.hwndFrom = this->m_hWnd;
nmhdr.idFrom = nPage;
nmhdr.code = TBVN_PAGEACTIVATED;
this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr);
}
void OnContextMenu(int nPage, POINT pt)
{
TBVCONTEXTMENUINFO cmi = {};
cmi.hdr.hwndFrom = this->m_hWnd;
cmi.hdr.idFrom = nPage;
cmi.hdr.code = TBVN_CONTEXTMENU;
cmi.pt = pt;
this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&cmi);
}
};
class CTabView : public CTabViewImpl<CTabView>
{
public:
DECLARE_WND_CLASS_EX(_T("WTL_TabView"), 0, COLOR_APPWORKSPACE)
};
} // namespace WTL
#endif // __ATLCTRLX_H__