// 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 // CBitmapButton // CCheckListViewCtrlImpl // CCheckListViewCtrl // CHyperLinkImpl // CHyperLink // CWaitCursor // CCustomWaitCursor // CMultiPaneStatusBarCtrlImpl // CMultiPaneStatusBarCtrl // CPaneContainerImpl // CPaneContainer // CSortListViewImpl // CSortListViewCtrlImpl // CSortListViewCtrl // CTabViewImpl // 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 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(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(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(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 { public: DECLARE_WND_SUPERCLASS(_T("WTL_BitmapButton"), GetWndClassName()) CBitmapButton(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) : CBitmapButtonImpl(dwExtendedStyle, hImageList) { } }; /////////////////////////////////////////////////////////////////////////////// // CCheckListCtrlView - list view control with check boxes template 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 CCheckListViewCtrlTraits; template class ATL_NO_VTABLE CCheckListViewCtrlImpl : public ATL::CWindowImpl { 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(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(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(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(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(this); pT->CheckSelectedItems(nCurrItem); } } bHandled = FALSE; return 1; } }; class CCheckListViewCtrl : public CCheckListViewCtrlImpl { 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 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(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(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(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(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(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(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(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(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(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(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(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(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(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(""), 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(""), 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(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 { 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 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 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(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 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(this); pT->UpdatePanesLayout(); } return lRet; } // Implementation BOOL UpdatePanesLayout() { // get pane positions ATL::CTempBuffer 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 { 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 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(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(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(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(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(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(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(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(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(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(this); pT->DrawPaneTitleBackground((HDC)wParam); return 1; } LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast(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(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(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(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(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(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(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 { 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 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 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(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(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(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(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(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(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(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(this); pT->CreateSortBitmaps(); if(m_iSortColumn != -1) SetSortColumn(m_iSortColumn); } } }; typedef ATL::CWinTraits CSortListViewCtrlTraits; template class ATL_NO_VTABLE CSortListViewCtrlImpl: public ATL::CWindowImpl, public CSortListViewImpl { 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::OnInsertColumn) MESSAGE_HANDLER(LVM_DELETECOLUMN, CSortListViewImpl::OnDeleteColumn) NOTIFY_CODE_HANDLER(HDN_ITEMCLICKA, CSortListViewImpl::OnHeaderItemClick) NOTIFY_CODE_HANDLER(HDN_ITEMCLICKW, CSortListViewImpl::OnHeaderItemClick) MESSAGE_HANDLER(WM_SETTINGCHANGE, CSortListViewImpl::OnSettingChange) END_MSG_MAP() }; class CSortListViewCtrl : public CSortListViewCtrlImpl { 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) #define TBVN_TABCLOSEBTN (0U-743) // return 0 to close page, 1 to keep open // internal #define TBVN_CLOSEBTNMOUSELEAVE (0U-744) // Notification data for TBVN_CONTEXTMENU struct TBVCONTEXTMENUINFO { NMHDR hdr; POINT pt; }; typedef TBVCONTEXTMENUINFO* LPTBVCONTEXTMENUINFO; // Helper class for tab item hover close button class CTabViewCloseBtn : public ATL::CWindowImpl { public: DECLARE_WND_CLASS_EX(_T("WTL_TabView_CloseBtn"), 0, -1) enum { _xyBtnImageLeftTop = 3, _xyBtnImageRightBottom = 9 }; bool m_bHover; bool m_bPressed; CToolTipCtrl m_tip; CTabViewCloseBtn() : m_bHover(false), m_bPressed(false) { } // Message map and handlers BEGIN_MSG_MAP(CTabViewCloseBtn) MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseMessage) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove) MESSAGE_HANDLER(WM_MOUSELEAVE, OnMouseLeave) MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp) MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint) FORWARD_NOTIFICATIONS() END_MSG_MAP() LRESULT OnMouseMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { MSG msg = { m_hWnd, uMsg, wParam, lParam }; if(m_tip.IsWindow() != FALSE) m_tip.RelayEvent(&msg); bHandled = FALSE; return 1; } LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { SetCapture(); m_bHover = false; m_bPressed = true; Invalidate(FALSE); UpdateWindow(); return 0; } LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/) { if(::GetCapture() == m_hWnd) { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ClientToScreen(&pt); RECT rect = {}; GetWindowRect(&rect); bool bPressed = (::PtInRect(&rect, pt) != FALSE); if(m_bPressed != bPressed) { m_bPressed = bPressed; Invalidate(FALSE); UpdateWindow(); } } else { if(!m_bHover) { m_bHover = true; Invalidate(FALSE); UpdateWindow(); } TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_hWnd }; ::TrackMouseEvent(&tme); } return 0; } LRESULT OnMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(m_bHover) { m_bHover = false; Invalidate(FALSE); UpdateWindow(); } NMHDR nmhdr = { m_hWnd, (UINT_PTR)GetDlgCtrlID(), TBVN_CLOSEBTNMOUSELEAVE }; GetParent().SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmhdr); return 0; } LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(::GetCapture() == m_hWnd) { bool bAction = m_bPressed; ReleaseCapture(); if(bAction) GetParent().SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd); } return 0; } LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(m_bPressed) { m_bPressed = false; Invalidate(FALSE); UpdateWindow(); } return 0; } LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/) { if(wParam != NULL) { DoPaint((HDC)wParam); } else { CPaintDC dc(this->m_hWnd); DoPaint(dc.m_hDC); } return 0; } // painting helper void DoPaint(CDCHandle dc) { RECT rect = {}; GetClientRect(&rect); RECT rcImage = { _xyBtnImageLeftTop, _xyBtnImageLeftTop, _xyBtnImageRightBottom + 1, _xyBtnImageRightBottom + 1 }; ::OffsetRect(&rcImage, rect.left, rect.top); if(m_bPressed) ::OffsetRect(&rcImage, 1, 0); // draw button frame and background CPen penFrame; penFrame.CreatePen(PS_SOLID, 0, ::GetSysColor((m_bHover || m_bPressed) ? COLOR_BTNTEXT : COLOR_BTNSHADOW)); HPEN hPenOld = dc.SelectPen(penFrame); CBrush brush; brush.CreateSysColorBrush(m_bPressed ? COLOR_BTNSHADOW : COLOR_WINDOW); HBRUSH hBrushOld = dc.SelectBrush(brush); dc.Rectangle(&rect); // draw button "X" CPen penX; penX.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT)); dc.SelectPen(penX); 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); dc.SelectBrush(hBrushOld); } }; template 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 }; enum CloseBtn { _cxCloseBtn = 14, _cyCloseBtn = 13, _cxCloseBtnMargin = 4, _cxCloseBtnMarginSel = 1, _nCloseBtnID = ID_PANE_CLOSE }; // Data members ATL::CContainedWindowT 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; CTabViewCloseBtn m_btnClose; int m_nCloseItem; 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; bool m_bTabCloseButton: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_nCloseItem(-1), 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_bTabCloseButton(true), 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(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(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); HWND hWndFocus = ::GetFocus(); ATL::CWindow wndTop = this->GetTopLevelWindow(); if((hWndFocus == wndTop.m_hWnd) || ((wndTop.IsChild(hWndFocus) != FALSE) && (hWndFocus != 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(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(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(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 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(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 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(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(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(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((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 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(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) MESSAGE_HANDLER(WM_MOUSELEAVE, OnTabMouseLeave) NOTIFY_HANDLER(T::_nCloseBtnID, TBVN_CLOSEBTNMOUSELEAVE, OnTabCloseBtnMouseLeave) COMMAND_HANDLER(T::_nCloseBtnID, BN_CLICKED, OnTabCloseBtnClicked) END_MSG_MAP() LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { T* pT = static_cast(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(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(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(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(this); pT->OnContextMenu(nPage, pt); } else { bHandled = FALSE; } return 0; } LRESULT OnTabChanged(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) { if(m_bTabCloseButton && (m_btnClose.m_hWnd != NULL)) { T* pT = static_cast(this); RECT rcClose = {}; pT->CalcCloseButtonRect(m_nCloseItem, rcClose); m_btnClose.SetWindowPos(NULL, &rcClose, SWP_NOZORDER | SWP_NOACTIVATE); } SetActivePage(m_tab.GetCurSel()); T* pT = static_cast(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(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(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(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(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(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; } } else if(m_bTabCloseButton) { TCHITTESTINFO thti = {}; thti.pt.x = GET_X_LPARAM(lParam); thti.pt.y = GET_Y_LPARAM(lParam); int nItem = m_tab.HitTest(&thti); if(nItem >= 0) { ATLTRACE(_T("+++++ item = %i\n"), nItem); T* pT = static_cast(this); if(m_btnClose.m_hWnd == NULL) { pT->CreateCloseButton(nItem); m_nCloseItem = nItem; } else if(m_nCloseItem != nItem) { RECT rcClose = {}; pT->CalcCloseButtonRect(nItem, rcClose); m_btnClose.SetWindowPos(NULL, &rcClose, SWP_NOZORDER | SWP_NOACTIVATE); m_nCloseItem = nItem; } TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_tab.m_hWnd }; ::TrackMouseEvent(&tme); } } return 0; } LRESULT OnTabMouseLeave(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { bHandled = FALSE; if(m_btnClose.m_hWnd != NULL) { POINT pt = {}; ::GetCursorPos(&pt); RECT rect = {}; m_btnClose.GetWindowRect(&rect); if(::PtInRect(&rect, pt) == FALSE) { m_nCloseItem = -1; T* pT = static_cast(this); pT->DestroyCloseButton(); } else { bHandled = TRUE; } } return 0; } LRESULT OnTabCloseBtnMouseLeave(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) { TCHITTESTINFO thti = {}; ::GetCursorPos(&thti.pt); m_tab.ScreenToClient(&thti.pt); int nItem = m_tab.HitTest(&thti); if(nItem == -1) m_tab.SendMessage(WM_MOUSELEAVE); return 0; } LRESULT OnTabCloseBtnClicked(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) { T* pT = static_cast(this); pT->OnTabCloseBtn(m_nCloseItem); 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 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(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(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(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(this); LPCTSTR lpstrTitle = pT->GetPageTitle(m_nActivePage); LPCTSTR lpstrDivider = pT->GetTitleDividerText(); int cchBuffer = m_cchTitleBarLength + lstrlen(lpstrDivider) + lstrlen(m_lpstrTitleBarBase) + 1; ATL::CTempBuffer 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(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); } void OnTabCloseBtn(int nPage) { NMHDR nmhdr = {}; nmhdr.hwndFrom = this->m_hWnd; nmhdr.idFrom = nPage; nmhdr.code = TBVN_TABCLOSEBTN; LRESULT lRet = this->GetParent().SendMessage(WM_NOTIFY, this->GetDlgCtrlID(), (LPARAM)&nmhdr); if(lRet == 0) // default - close page { T* pT = static_cast(this); pT->RemovePage(m_nCloseItem); m_nCloseItem = -1; pT->DestroyCloseButton(); } else { m_tab.SendMessage(WM_MOUSELEAVE); } } // Close button overrideables void CreateCloseButton(int nItem) { ATLASSERT(m_btnClose.m_hWnd == NULL); m_btnClose.m_bPressed = false; T* pT = static_cast(this); RECT rcClose = {}; pT->CalcCloseButtonRect(nItem, rcClose); m_btnClose.Create(m_tab.m_hWnd, rcClose, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, T::_nCloseBtnID); ATLASSERT(m_btnClose.IsWindow()); if(m_btnClose.m_hWnd != NULL) { // create a tool tip ATLASSERT(m_btnClose.m_tip.m_hWnd == NULL); m_btnClose.m_tip.Create(m_btnClose.m_hWnd); ATLASSERT(m_btnClose.m_tip.IsWindow()); if(m_btnClose.m_tip.IsWindow()) { m_btnClose.m_tip.Activate(TRUE); RECT rect = {}; m_btnClose.GetClientRect(&rect); m_btnClose.m_tip.AddTool(m_btnClose.m_hWnd, LPSTR_TEXTCALLBACK, &rect, T::_nCloseBtnID); } } } void DestroyCloseButton() { ATLASSERT(m_btnClose.m_hWnd != NULL); if(m_btnClose.m_hWnd != NULL) { if(m_btnClose.m_tip.IsWindow()) { m_btnClose.m_tip.DestroyWindow(); m_btnClose.m_tip.m_hWnd = NULL; } m_btnClose.DestroyWindow(); } } void CalcCloseButtonRect(int nItem, RECT& rcClose) { RECT rcItem = {}; m_tab.GetItemRect(nItem, &rcItem); int cy = (rcItem.bottom - rcItem.top - _cyCloseBtn) / 2; int cx = (nItem == m_tab.GetCurSel()) ? _cxCloseBtnMarginSel : _cxCloseBtnMargin; ::SetRect(&rcClose, rcItem.right - cx - _cxCloseBtn, rcItem.top + cy, rcItem.right - cx, rcItem.top + cy + _cyCloseBtn); } }; class CTabView : public CTabViewImpl { public: DECLARE_WND_CLASS_EX(_T("WTL_TabView"), 0, COLOR_APPWORKSPACE) }; } // namespace WTL #endif // __ATLCTRLX_H__