// CListCtrl - A WTL list control with Windows Vista style item selection // Revision: 1.5 // Last modified: 2nd November 2016 #pragma once #include #include #pragma warning(push) #pragma warning(disable : 4838) // warning C4838: conversion from 'int' to 'UINT' requires a narrowing conversion #include #include #include #include #pragma warning(pop) #include "DragDrop.h" #include "DropArrows.h" #include "TitleTip.h" #include "ListEdit.h" #include "ListCombo.h" #include "ListDate.h" struct CListColumn { stdstr m_strText; int m_nWidth; BOOL m_bFixed; UINT m_nFormat; UINT m_nFlags; int m_nImage; int m_nIndex; CListArray < stdstr > m_aComboList; }; template < class T > class CListImpl : public CWindowImpl< CListImpl< T > >, public CDoubleBufferImpl< CListImpl< T > > { public: CListImpl() { m_bSortEnabled = TRUE; // Added by Rowan 05/12/2006 m_bRightClickSelect = FALSE; // Shygoo 11/02/2016 m_bShowHeader = TRUE; m_bSortAscending = TRUE; m_bButtonDown = FALSE; m_bMouseOver = FALSE; m_bColumnSizing = FALSE; m_bBeginSelect = FALSE; m_bSingleSelect = FALSE; m_bFocusSubItem = FALSE; m_bGroupSelect = FALSE; m_bEnableHorizScroll = FALSE; m_bEnableVertScroll = FALSE; m_bShowHorizScroll = TRUE; m_bShowVertScroll = TRUE; m_bShowSort = TRUE; m_bResizeTimer = FALSE; m_bDragDrop = FALSE; m_bSmoothScroll = TRUE; m_bEditItem = FALSE; m_bScrolling = FALSE; m_bScrollDown = FALSE; m_bTileBackground = FALSE; m_nMouseWheelScroll = 3; m_nTotalWidth = 0; m_nHeaderHeight = 0; m_nItemHeight = 0; m_nFirstSelected = NULL_ITEM; m_nFocusItem = NULL_ITEM; m_nFocusSubItem = NULL_SUBITEM; m_nHotItem = NULL_ITEM; m_nHotSubItem = NULL_SUBITEM; m_nTitleTipItem = NULL_ITEM; m_nTitleTipSubItem = NULL_SUBITEM; m_nSortColumn = NULL_COLUMN; m_nHighlightColumn = NULL_COLUMN; m_nDragColumn = NULL_COLUMN; m_nHotColumn = NULL_COLUMN; m_nHotDivider = NULL_COLUMN; m_nColumnSizing = NULL_COLUMN; m_nScrollOffset = 0; m_nScrollDelta = 0; m_nScrollUnit = 0; m_nStartScrollPos = 0; m_nStartSize = 0; m_nStartPos = 0; m_ptDownPoint = 0; m_ptSelectPoint = 0; m_rcGroupSelect = 0; m_dwSearchTick = 0; m_dwScrollTick = 0; m_strSearchString = _T( "" ); } ~CListImpl() { if (m_wndItemEdit.IsWindow()) { // Patch memory window crash m_wndItemEdit.UnsubclassWindow(); } } protected: BOOL m_bSortEnabled; // Added by Rowan 05/12/2006 to disable sorting BOOL m_bRightClickSelect; // Shygoo 11/02/2016 BOOL m_bShowHeader; BOOL m_bShowSort; BOOL m_bSortAscending; BOOL m_bButtonDown; BOOL m_bMouseOver; BOOL m_bColumnSizing; BOOL m_bBeginSelect; BOOL m_bSingleSelect; BOOL m_bFocusSubItem; BOOL m_bGroupSelect; BOOL m_bShowHorizScroll; BOOL m_bShowVertScroll; BOOL m_bEnableHorizScroll; BOOL m_bEnableVertScroll; BOOL m_bResizeTimer; BOOL m_bDragDrop; BOOL m_bSmoothScroll; BOOL m_bEditItem; BOOL m_bScrolling; BOOL m_bScrollDown; BOOL m_bTileBackground; CPoint m_ptDownPoint; CPoint m_ptSelectPoint; CRect m_rcGroupSelect; int m_nItemHeight; int m_nHeaderHeight; int m_nFirstSelected; int m_nFocusItem; int m_nFocusSubItem; int m_nHotItem; int m_nHotSubItem; int m_nTitleTipItem; int m_nTitleTipSubItem; int m_nMouseWheelScroll; int m_nTotalWidth; int m_nSortColumn; int m_nDragColumn; int m_nHighlightColumn; int m_nHotColumn; int m_nHotDivider; int m_nColumnSizing; int m_nScrollOffset; int m_nScrollDelta; int m_nScrollUnit; int m_nStartScrollPos; int m_nStartSize; int m_nStartPos; DWORD m_dwSearchTick; DWORD m_dwScrollTick; stdstr m_strSearchString; CBitmap m_bmpScrollList; CBitmap m_bmpBackground; CLIPFORMAT m_nHeaderClipboardFormat; COLORREF m_rgbBackground; COLORREF m_rgbHeaderBackground; COLORREF m_rgbHeaderBorder; COLORREF m_rgbHeaderShadow; COLORREF m_rgbHeaderText; COLORREF m_rgbHeaderHighlight; COLORREF m_rgbSelectedItem; COLORREF m_rgbSelectedText; COLORREF m_rgbItemText; COLORREF m_rgbSelectOuter; COLORREF m_rgbSelectInner; COLORREF m_rgbSelectTop; COLORREF m_rgbSelectBottom; COLORREF m_rgbNoFocusTop; COLORREF m_rgbNoFocusBottom; COLORREF m_rgbNoFocusOuter; COLORREF m_rgbNoFocusInner; COLORREF m_rgbFocusTop; COLORREF m_rgbFocusBottom; COLORREF m_rgbProgressTop; COLORREF m_rgbProgressBottom; COLORREF m_rgbItemFocus; COLORREF m_rgbHyperLink; CCursor m_curDivider; CCursor m_curHyperLink; CFont m_fntListFont; CFont m_fntUnderlineFont; CImageList m_ilListItems; CImageList m_ilItemImages; CDragDrop < CListImpl > m_oleDragDrop; CToolTipCtrl m_ttToolTip; CDropArrows m_wndDropArrows; CTitleTip m_wndTitleTip; CListEdit m_wndItemEdit; CListCombo m_wndItemCombo; CListDate m_wndItemDate; CListArray < CListColumn > m_aColumns; set < int > m_setSelectedItems; public: BOOL SubclassWindow( HWND hWnd ) { T* pT; pT = static_cast(this); return CWindowImpl< CListImpl >::SubclassWindow( hWnd ) ? pT->Initialise() : FALSE; } void RegisterClass() { T* pT = static_cast(this); pT = pT; pT->GetWndClassInfo().m_wc.lpfnWndProc = m_pfnSuperWindowProc; pT->GetWndClassInfo().Register( &m_pfnSuperWindowProc ); } BOOL Initialise() { // Load list images if ( !m_ilListItems.CreateFromImage( IDB_LISTITEMS, 16, 0, RGB( 255, 0, 255 ), IMAGE_BITMAP, LR_CREATEDIBSECTION ) ) return FALSE; if ( m_curDivider.LoadCursor( IDC_DIVIDER ) == nullptr ) return FALSE; if ( m_curHyperLink.LoadCursor( IDC_HYPERLINK ) == nullptr ) return FALSE; // Load interface settings if ( !LoadSettings() ) return FALSE; // Give control a static border ModifyStyle( WS_BORDER, WS_CLIPCHILDREN ); ModifyStyleEx( WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, SWP_FRAMECHANGED ); // Register drag drop m_oleDragDrop.Register( this ); m_oleDragDrop.AddTargetFormat( m_nHeaderClipboardFormat ); m_oleDragDrop.AddSourceFormat( m_nHeaderClipboardFormat ); // Create the tooltip if ( !m_ttToolTip.Create( m_hWnd ) ) return FALSE; m_ttToolTip.SetMaxTipWidth( SHRT_MAX ); return TRUE; } BOOL LoadSettings() { m_rgbBackground = GetSysColor( COLOR_WINDOW ); m_rgbHeaderBackground = GetSysColor( COLOR_BTNFACE ); m_rgbHeaderBorder = GetSysColor( COLOR_3DHIGHLIGHT ); m_rgbHeaderShadow = GetSysColor( COLOR_3DSHADOW ); m_rgbHeaderText = GetSysColor( COLOR_WINDOWTEXT ); m_rgbHeaderHighlight = RGB( 130, 140, 180 ); m_rgbSelectedItem = GetSysColor( COLOR_HIGHLIGHT ); m_rgbSelectedText = GetSysColor( COLOR_HIGHLIGHTTEXT ); m_rgbItemText = GetSysColor( COLOR_WINDOWTEXT ); m_rgbSelectOuter = RGB( 170, 200, 245 ); m_rgbSelectInner = RGB( 230, 250, 250 ); m_rgbSelectTop = RGB( 210, 240, 250 ); m_rgbSelectBottom = RGB( 185, 215, 250 ); m_rgbNoFocusTop = RGB( 250, 250, 250 ); m_rgbNoFocusBottom = RGB( 235, 235, 235 ); m_rgbNoFocusOuter = RGB( 220, 220, 220 ); m_rgbNoFocusInner = RGB( 245, 245, 245 ); m_rgbFocusTop = RGB( 235, 245, 245 ); m_rgbFocusBottom = RGB( 225, 235, 245 ); m_rgbProgressTop = RGB( 170, 240, 170 ); m_rgbProgressBottom = RGB( 45, 210, 50 ); m_rgbItemFocus = RGB( 180, 190, 210 ); m_rgbHyperLink = RGB( 0, 0, 200 ); m_nHeaderClipboardFormat = (CLIPFORMAT)RegisterClipboardFormat( _T( "HEADERCLIPBOARDFORMAT" ) ); // Get number of lines to scroll #if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 0, &m_nMouseWheelScroll, 0 ); #endif // Get system message font CLogFont logFont; logFont.SetMessageBoxFont(); if ( !m_fntListFont.IsNull() ) m_fntListFont.DeleteObject(); if ( m_fntListFont.CreateFontIndirect( &logFont ) == nullptr ) return FALSE; // Get system underline font logFont.lfUnderline = BYTE(TRUE); if ( !m_fntUnderlineFont.IsNull() ) m_fntUnderlineFont.DeleteObject(); if ( m_fntUnderlineFont.CreateFontIndirect( &logFont ) == nullptr ) return FALSE; CClientDC dcClient( m_hWnd ); HFONT hOldFont = dcClient.SelectFont( m_fntListFont ); CSize sizeExtent; if ( !dcClient.GetTextExtent( _T( "Height" ), -1, &sizeExtent ) ) return FALSE; dcClient.SelectFont( hOldFont ); // Has system font changed? if ( m_nItemHeight != sizeExtent.cy + ITEM_HEIGHT_MARGIN ) { m_nItemHeight = sizeExtent.cy + ITEM_HEIGHT_MARGIN; m_nHeaderHeight = m_nItemHeight; // Create drop arrows window if ( m_wndDropArrows.IsWindow() ) m_wndDropArrows.DestroyWindow(); if ( !m_wndDropArrows.Create( m_hWnd, m_nHeaderHeight, TRUE ) ) return FALSE; } // Create title tip window if ( m_wndTitleTip.IsWindow() ) m_wndTitleTip.DestroyWindow(); if ( !m_wndTitleTip.Create( m_hWnd ) ) return FALSE; return TRUE; } // Added by Rowan 05/12/2006 void SetSortEnabled(BOOL bSortEnabled) { m_bSortEnabled = bSortEnabled; } // Shygoo 11/02/2016 void SetRightClickSelect( BOOL bRightClickSelect = TRUE) { m_bRightClickSelect = bRightClickSelect; } void ShowHeader( BOOL bShowHeader = TRUE ) { m_bShowHeader = bShowHeader; ResetScrollBars(); Invalidate(); } void ShowHeaderSort( BOOL bShowSort = TRUE ) { m_bShowSort = bShowSort; Invalidate(); } void SetSingleSelect( BOOL bSingleSelect = TRUE ) { m_bSingleSelect = bSingleSelect; Invalidate(); } void SetFocusSubItem( BOOL bFocusSubItem = TRUE ) { m_bFocusSubItem = bFocusSubItem; Invalidate(); } void SetDragDrop( BOOL bDragDrop = TRUE ) { m_bDragDrop = bDragDrop; } void SetSmoothScroll( BOOL bSmoothScroll = TRUE ) { m_bSmoothScroll = bSmoothScroll; } void SetBackgroundImage( HBITMAP hBackgroundImage, BOOL bTileImage = FALSE ) { m_bmpBackground = hBackgroundImage; m_bTileBackground = bTileImage; } void SetImageList( CImageList& ilItemImages ) { m_ilItemImages = ilItemImages; } UINT ValidateFlags( UINT nFlags ) { if ( nFlags & ITEM_FLAGS_CENTRE ) nFlags &= ~( ITEM_FLAGS_LEFT | ITEM_FLAGS_RIGHT ); if ( nFlags & ITEM_FLAGS_RIGHT ) nFlags &= ~ITEM_FLAGS_LEFT; if ( nFlags & ITEM_FLAGS_DATE_ONLY ) nFlags &= ~ITEM_FLAGS_TIME_ONLY; if ( nFlags & ( ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT ) ) nFlags &= ~ITEM_FLAGS_EDIT_UPPER; if ( !( nFlags & ( ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT ) ) ) nFlags &= ~( ITEM_FLAGS_EDIT_NEGATIVE | ITEM_FLAGS_EDIT_OPERATOR ); if ( nFlags & ITEM_FLAGS_COMBO_EDIT ) nFlags &= ~( ITEM_FLAGS_DATE_ONLY | ITEM_FLAGS_TIME_ONLY | ITEM_FLAGS_DATETIME_NONE ); return nFlags; } void AddColumn( CListColumn& listColumn ) { // Minimum column width if ( listColumn.m_strText.empty() && listColumn.m_nImage != ITEM_IMAGE_NONE ) { CSize sizeIcon; m_ilListItems.GetIconSize( sizeIcon ); listColumn.m_nWidth = sizeIcon.cx + 5; listColumn.m_nFlags |= ITEM_FLAGS_CENTRE; } // Correct incompatible flag mask values listColumn.m_nFlags = ValidateFlags( listColumn.m_nFlags ); // Initial data index listColumn.m_nIndex = GetColumnCount(); m_aColumns.Add( listColumn ); ResetScrollBars(); Invalidate(); } void AddColumn( LPCTSTR lpszText, int nWidth = 0, int nImage = ITEM_IMAGE_NONE, BOOL bFixed = FALSE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE ) { CListColumn listColumn; listColumn.m_strText = lpszText; listColumn.m_nWidth = nWidth; listColumn.m_bFixed = bFixed; listColumn.m_nFormat = nFormat; listColumn.m_nFlags = nFlags; listColumn.m_nImage = nImage; AddColumn( listColumn ); } void RemoveAllCoumns ( void ) { m_aColumns.RemoveAll(); ResetScrollBars(); Invalidate(); } BOOL GetHasEditItem () { return m_bEditItem; } int GetColumnCount() { return m_aColumns.GetSize(); } BOOL GetColumn( int nColumn, CListColumn& listColumn ) { if ( nColumn < 0 || nColumn >= GetColumnCount() ) return FALSE; listColumn = m_aColumns[ nColumn ]; return TRUE; } int GetTotalWidth( BOOL bRecalc = FALSE ) { if ( bRecalc ) { m_nTotalWidth = 0; for ( int nColumn = 0; nColumn < GetColumnCount(); nColumn++ ) m_nTotalWidth += GetColumnWidth( nColumn ); } return m_nTotalWidth - 1; } int GetTotalHeight() { T* pT = static_cast(this); return max( ( pT->GetItemCount() * m_nItemHeight ) + ( m_bShowHeader ? m_nHeaderHeight : 0 ), 1 ); } BOOL SetColumnWidth( int nColumn, int nWidth ) { if ( nColumn < 0 || nColumn >= GetColumnCount() ) return FALSE; // Set new column size if not fixed if ( !m_aColumns[ nColumn ].m_bFixed ) { m_aColumns[ nColumn ].m_nWidth = nWidth; ResetScrollBars(); Invalidate(); } return TRUE; } int GetColumnWidth( int nColumn ) { CListColumn listColumn; return GetColumn( nColumn, listColumn ) ? listColumn.m_nWidth : 0; } int GetColumnIndex( int nColumn ) { CListColumn listColumn; return GetColumn( nColumn, listColumn ) ? listColumn.m_nIndex : 0; } int IndexToOrder( int nIndex ) { for ( int nColumn = 0; nColumn < GetColumnCount(); nColumn++ ) { if ( GetColumnIndex( nColumn ) == nIndex ) return nColumn; } return -1; } BOOL SetColumnFormat( int nColumn, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE ) { if ( nColumn < 0 || nColumn >= GetColumnCount() ) return FALSE; m_aColumns[ nColumn ].m_nFormat = nFormat; m_aColumns[ nColumn ].m_nFlags = ValidateFlags( nFlags ); return TRUE; } BOOL SetColumnFormat( int nColumn, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList ) { if ( nColumn < 0 || nColumn >= GetColumnCount() ) return FALSE; m_aColumns[ nColumn ].m_nFormat = nFormat; m_aColumns[ nColumn ].m_nFlags = ValidateFlags( nFlags ); m_aColumns[ nColumn ].m_aComboList = aComboList; return TRUE; } UINT GetColumnFormat( int nColumn ) { CListColumn listColumn; return GetColumn( nColumn, listColumn ) ? listColumn.m_nFormat : ITEM_FORMAT_NONE; } UINT GetColumnFlags( int nColumn ) { CListColumn listColumn; return GetColumn( nColumn, listColumn ) ? listColumn.m_nFlags : ITEM_FLAGS_NONE; } BOOL GetColumnComboList( int nColumn, CListArray < stdstr >& aComboList ) { CListColumn listColumn; if ( !GetColumn( nColumn, listColumn ) ) return FALSE; aComboList = listColumn.m_aComboList; return !aComboList.IsEmpty(); } BOOL GetColumnRect( int nColumn, CRect& rcColumn ) { if ( nColumn < 0 || nColumn >= GetColumnCount() ) return FALSE; GetClientRect( rcColumn ); rcColumn.bottom = m_nHeaderHeight; for ( int nColumnOrder = 0; nColumnOrder < GetColumnCount(); nColumnOrder++ ) { int nWidth = GetColumnWidth( nColumnOrder ); if ( nColumn == nColumnOrder ) { rcColumn.right = rcColumn.left + nWidth; break; } rcColumn.left += nWidth; } // Offset column by scroll position rcColumn.OffsetRect( -GetScrollPos( SB_HORZ ), 0 ); return TRUE; } BOOL AddItem() { ResetScrollBars(); return Invalidate(); } BOOL DeleteItem( int nItem ) { m_setSelectedItems.erase( nItem ); ResetScrollBars(); return Invalidate(); } BOOL DeleteAllItems() { m_setSelectedItems.clear(); ResetScrollBars(); return Invalidate(); } int GetItemCount() { ATLASSERT( FALSE ); // Must be implemented in a derived class return 0; } stdstr GetItemText( int nItem, int nSubItem ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return _T( "" ); } BOOL GetItemDate( int nItem, int nSubItem, SYSTEMTIME& stItemDate ) { T* pT = static_cast(this); ZeroMemory( &stItemDate, sizeof( SYSTEMTIME ) ); stdstr strItemText = pT->GetItemText( nItem, nSubItem ); if ( strItemText.empty() ) return FALSE; // Get date and time from item text: yyyymmddhhmmss stItemDate.wYear = (WORD)_ttoi( strItemText.substr(0, 4 ).c_str() ); stItemDate.wMonth = (WORD)_ttoi( strItemText.substr( 4, 2 ).c_str() ); stItemDate.wDay = (WORD)_ttoi( strItemText.substr( 6, 2 ).c_str() ); stItemDate.wHour = (WORD)_ttoi( strItemText.substr( 8, 2 ).c_str() ); stItemDate.wMinute = (WORD)_ttoi( strItemText.substr( 10, 2 ).c_str() ); stItemDate.wSecond = (WORD)_ttoi( strItemText.substr( 12, 2 ).c_str() ); stItemDate.wMilliseconds = 0; return TRUE; } int GetItemImage( int nItem, int nSubItem ) { return ITEM_IMAGE_NONE; // May be implemented in a derived class } UINT GetItemFormat( int nItem, int nSubItem ) { return GetColumnFormat( IndexToOrder( nSubItem ) ); // May be implemented in a derived class } UINT GetItemFlags( int nItem, int nSubItem ) { return GetColumnFlags( IndexToOrder( nSubItem ) ); // May be implemented in a derived class } BOOL GetItemComboList( int nItem, int nSubItem, CListArray < stdstr >& aComboList ) { return GetColumnComboList( IndexToOrder( nSubItem ), aComboList ); // May be implemented in a derived class } HFONT GetItemFont( int /*nItem*/, int /*nSubItem*/ ) { return m_fntListFont; // May be implemented in a derived class } BOOL GetItemColours( int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText ) { rgbBackground = m_rgbBackground; rgbText = m_rgbItemText; return TRUE; } stdstr virtual GetItemToolTip( int /*nItem*/, int /*nSubItem*/ ) { return _T( "" ); // May be implemented in a derived class } stdstr virtual GetHeaderToolTip(int /*column*/) { return _T(""); // Implemented by child class } BOOL SetItemText( int nItem, int nSubItem, LPCTSTR lpszText ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemComboIndex( int nItem, int nSubItem, int nIndex ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemDate( int nItem, int nSubItem, SYSTEMTIME& stItemDate ) { T* pT = static_cast(this); // Set date and time in format (yyyymmddhhmmss) stdstr strFormatDate; strFormatDate.Format( _T( "%04d%02d%02d%02d%02d%02d" ), stItemDate.wYear, stItemDate.wMonth, stItemDate.wDay, stItemDate.wHour, stItemDate.wMinute, stItemDate.wSecond ); return pT->SetItemText( nItem, nSubItem, strFormatDate.c_str() ); } BOOL SetItemCheck( int nItem, int nSubItem, int nCheckValue ) { T* pT = static_cast(this); switch ( pT->GetItemFormat( nItem, nSubItem ) ) { case ITEM_FORMAT_CHECKBOX: return pT->SetItemText( nItem, nSubItem, nCheckValue > 0 ? _T( "1" ) : _T( "0" ) ); case ITEM_FORMAT_CHECKBOX_3STATE: if ( nCheckValue < 0 ) return pT->SetItemText( nItem, nSubItem, _T( "-1" ) ); if ( nCheckValue > 0 ) return pT->SetItemText( nItem, nSubItem, _T( "1" ) ); return pT->SetItemText( nItem, nSubItem, _T( "0" ) ); } return FALSE; } BOOL SetItemImage( int nItem, int nSubItem, int nImage ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemFont( int nItem, int nSubItem, HFONT hFont ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL SetItemColours( int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText ) { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } void ReverseItems() { ATLASSERT( FALSE ); // Must be implemented in a derived class } void SortItems( int nColumn, BOOL bAscending ) { ATLASSERT( FALSE ); // Must be implemented in a derived class } BOOL GetItemRect( int nItem, int nSubItem, CRect& rcItem ) { T* pT = static_cast(this); int nTopItem = GetTopItem(); if ( nItem < nTopItem || nItem >= pT->GetItemCount() || nItem >= nTopItem + GetCountPerPage() ) return FALSE; CRect rcClient; GetClientRect( rcClient ); // Calculate item rect based on scroll position rcItem = rcClient; rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ) + ( ( nItem - nTopItem ) * m_nItemHeight ); rcItem.bottom = rcItem.top + m_nItemHeight; rcItem.right = min( rcClient.right, GetTotalWidth() ); if ( nSubItem != NULL_SUBITEM ) { CRect rcColumn; if ( !GetColumnRect( nSubItem, rcColumn ) ) return FALSE; rcItem.left = rcColumn.left; rcItem.right = rcColumn.right; } return TRUE; } BOOL GetItemRect( int nItem, CRect& rcItem ) { return GetItemRect( nItem, NULL_SUBITEM, rcItem ); } BOOL InvalidateItem( int nItem, int nSubItem = NULL_SUBITEM ) { CRect rcItem; return GetItemRect( nItem, nSubItem, rcItem ) ? InvalidateRect( rcItem ) : FALSE; } BOOL InvalidateHeader() { if ( !m_bShowHeader ) return TRUE; CRect rcHeader; if ( !GetClientRect( rcHeader ) ) return FALSE; rcHeader.bottom = m_nHeaderHeight; return InvalidateRect( rcHeader ); } int GetTopItem() { return (int)( GetScrollPos( SB_VERT ) / m_nItemHeight ); } int GetCountPerPage( BOOL bPartial = TRUE ) { CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); // Calculate number of items per control height (include partial item) div_t divHeight = div( rcClient.Height(), m_nItemHeight ); // Round up to nearest item count return max( bPartial && divHeight.rem > 0 ? divHeight.quot + 1 : divHeight.quot, 1 ); } BOOL IsItemVisible( int nItem, int nSubItem = NULL_SUBITEM, BOOL bPartial = TRUE ) { T* pT = static_cast(this); int nTopItem = GetTopItem(); if ( nItem < nTopItem || nItem >= pT->GetItemCount() ) return FALSE; // Check whether item is visible if ( nItem < nTopItem || nItem >= nTopItem + GetCountPerPage( bPartial ) ) return FALSE; // Check whether subitem is visible if ( m_bFocusSubItem && nSubItem != NULL_SUBITEM ) { CRect rcColumn; if ( !GetColumnRect( nSubItem, rcColumn ) ) return FALSE; CRect rcClient; GetClientRect( rcClient ); if ( rcColumn.left < rcClient.left || rcColumn.right > rcClient.right ) return FALSE; } return TRUE; } BOOL EnsureItemVisible( int nItem, int nSubItem = NULL_SUBITEM ) { if ( IsItemVisible( nItem, nSubItem, FALSE ) ) return TRUE; HideTitleTip(); CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); CRect rcItem; rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ) + ( ( nItem - GetTopItem() ) * m_nItemHeight ); rcItem.bottom = rcItem.top + m_nItemHeight; if ( rcItem.top < rcClient.top || rcItem.bottom > rcClient.bottom ) { int nScrollItem = NULL_ITEM; // Scroll list up/down to include item if ( rcItem.top < rcClient.top || rcItem.Height() > rcClient.Height() ) nScrollItem = nItem; else if ( rcItem.bottom > rcClient.bottom ) nScrollItem = nItem - ( GetCountPerPage( FALSE ) - 1 ); if ( nScrollItem != NULL_ITEM ) SetScrollPos( SB_VERT, nScrollItem * m_nItemHeight ); } if ( m_bFocusSubItem && nSubItem != NULL_SUBITEM ) { CRect rcColumn; if ( !GetColumnRect( nSubItem, rcColumn ) ) return FALSE; GetClientRect( rcClient ); int nScrollPos = 0; // Scroll list left/right to include subitem if ( rcColumn.Width() > rcClient.Width() || rcColumn.left < 0 ) nScrollPos = rcColumn.left; else if ( rcColumn.right > rcClient.right ) nScrollPos = rcColumn.right - rcClient.right; if ( nScrollPos != 0 ) SetScrollPos( SB_HORZ, GetScrollPos( SB_HORZ ) + nScrollPos ); } return Invalidate(); } void ShowScrollBar( int nScrollBar, BOOL bShow = TRUE ) { switch ( nScrollBar ) { case SB_HORZ: m_bShowHorizScroll = bShow; break; case SB_VERT: m_bShowVertScroll = bShow; break; case SB_BOTH: m_bShowHorizScroll = bShow; m_bShowVertScroll = bShow; break; } ResetScrollBars(); Invalidate(); } void ResetScrollBars( int nScrollBar = SB_BOTH, int nScrollPos = -1, BOOL bRecalc = TRUE ) { T* pT = static_cast(this); CRect rcClient; GetClientRect( rcClient ); SCROLLINFO infoScroll; infoScroll.cbSize = sizeof( SCROLLINFO ); infoScroll.fMask = nScrollPos < 0 ? SIF_PAGE | SIF_RANGE : SIF_PAGE | SIF_RANGE | SIF_POS; infoScroll.nPos = nScrollPos; infoScroll.nMin = 0; if ( ( nScrollBar == SB_BOTH || nScrollBar == SB_VERT ) && m_bShowVertScroll ) { infoScroll.nMax = ( pT->GetItemCount() * m_nItemHeight ) + ( m_bShowHeader ? m_nHeaderHeight : 0 ); infoScroll.nPage = rcClient.Height() - ( m_bShowHeader ? m_nHeaderHeight : 0 ); // Are we within client range? if ( (UINT)infoScroll.nMax <= infoScroll.nPage + ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) infoScroll.nMax = 0; // Set vertical scroll bar m_bEnableVertScroll = SetScrollInfo( SB_VERT, &infoScroll, TRUE ) ? ( infoScroll.nMax > 0 ) : FALSE; } if ( ( nScrollBar == SB_BOTH || nScrollBar == SB_HORZ ) && m_bShowHorizScroll ) { infoScroll.nMax = GetTotalWidth( bRecalc ); infoScroll.nPage = rcClient.Width(); // Are we within client range? if ( infoScroll.nPage >= (UINT)infoScroll.nMax ) infoScroll.nMax = 0; // Set horizontal scroll bar m_bEnableHorizScroll = SetScrollInfo( SB_HORZ, &infoScroll, TRUE ) ? ( infoScroll.nMax > (int)infoScroll.nPage ) : FALSE; } } BOOL IsScrollBarVisible( int nScrollBar ) { switch ( nScrollBar ) { case SB_HORZ: return m_bEnableHorizScroll; case SB_VERT: return m_bEnableVertScroll; case SB_BOTH: return ( m_bEnableHorizScroll && m_bEnableVertScroll ); default: return FALSE; } } BOOL ResetSelected() { m_setSelectedItems.clear(); m_nFocusItem = NULL_ITEM; m_nFocusSubItem = NULL_SUBITEM; m_nFirstSelected = NULL_ITEM; return Invalidate(); } BOOL SelectItem( int nItem, int nSubItem = NULL_SUBITEM, UINT nFlags = 0 ) { T* pT = static_cast(this); if ( nItem < 0 || nItem >= pT->GetItemCount() ) return FALSE; BOOL bSelectItem = TRUE; BOOL bSelectRange = !m_bSingleSelect && ( nFlags & MK_SHIFT ); BOOL bNewSelect = !( bSelectRange || ( nFlags & MK_CONTROL ) ); BOOL bEnsureVisible = FALSE; // Are we starting a new select sequence? if ( bNewSelect || bSelectRange ) { // Are we simply reselecting the same item? if ( m_setSelectedItems.size() == 1 && *m_setSelectedItems.begin() == nItem ) { bSelectItem = FALSE; m_nFirstSelected = nItem; m_nFocusItem = nItem; m_nFocusSubItem = nSubItem; } else m_setSelectedItems.clear(); } else // We adding to or removing from select sequence { if ( m_bSingleSelect ) m_setSelectedItems.clear(); set < int >::iterator posSelectedItem = m_setSelectedItems.find( nItem ); // Is this item already selected? if ( posSelectedItem != m_setSelectedItems.end() ) { bSelectItem = FALSE; m_setSelectedItems.erase( posSelectedItem ); m_nFirstSelected = nItem; m_nFocusItem = nItem; m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem; } } // Are we adding this item to the select sequence? if ( bSelectItem ) { bEnsureVisible = TRUE; if ( bSelectRange ) { if ( m_nFirstSelected == NULL_ITEM ) m_nFirstSelected = nItem; for ( int nSelectedItem = min( m_nFirstSelected, nItem ); nSelectedItem <= max( m_nFirstSelected, nItem ); nSelectedItem++ ) m_setSelectedItems.insert( nSelectedItem ); } else { m_nFirstSelected = nItem; m_setSelectedItems.insert( nItem ); } m_nFocusItem = nItem; m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem; // Notify parent of selected item NotifyParent( m_nFocusItem, m_nFocusSubItem, LCN_SELECTED ); } // Start visible timer (scrolls list to partially hidden item) if ( !IsItemVisible( nItem, m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem, FALSE ) ) SetTimer( ITEM_VISIBLE_TIMER, ITEM_VISIBLE_PERIOD ); else if ( m_nFocusItem != NULL_ITEM && m_nFocusSubItem != NULL_SUBITEM ) EditItem( m_nFocusItem, m_nFocusSubItem ); return Invalidate(); } BOOL IsSelected( int nItem ) { set < int >::iterator posSelectedItem = m_setSelectedItems.find( nItem ); return ( posSelectedItem != m_setSelectedItems.end() ); } BOOL GetSelectedItems( CListArray < int >& aSelectedItems ) { aSelectedItems.RemoveAll(); for ( set < int >::iterator posSelectedItem = m_setSelectedItems.begin(); posSelectedItem != m_setSelectedItems.end(); ++posSelectedItem ) aSelectedItems.Add( *posSelectedItem ); return !aSelectedItems.IsEmpty(); } BOOL SetFocusItem( int nItem, int nSubItem = NULL_SUBITEM ) { m_nFocusItem = nItem; m_nFocusSubItem = nSubItem; return EnsureItemVisible( m_nFocusItem, m_nFocusSubItem ); } BOOL GetFocusItem( int& nItem, int& nSubItem ) { nItem = IsSelected( m_nFocusItem ) ? m_nFocusItem : ( m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin() ); nSubItem = !m_bFocusSubItem || nItem == NULL_ITEM ? NULL_SUBITEM : m_nFocusSubItem; return ( nItem != NULL_ITEM ); } int GetFocusItem() { return IsSelected( m_nFocusItem ) ? m_nFocusItem : ( m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin() ); } BOOL HitTestHeader( CPoint point, int& nColumn, UINT& nFlags ) { // Reset hit test flags nFlags = HITTEST_FLAG_NONE; if ( !m_bShowHeader ) return FALSE; CRect rcClient; if ( !GetClientRect( rcClient ) ) return FALSE; // Are we over the header? if ( point.y < rcClient.top || point.y > m_nHeaderHeight ) return FALSE; int nDividerPos = 0; int nColumnCount = GetColumnCount(); // Get hit test subitem for ( nColumn = 0; nColumn < nColumnCount; nColumn++ ) { int nColumnWidth = GetColumnWidth( nColumn ); nDividerPos += nColumnWidth; // Offset divider position with current scroll position int nRelativePos = nDividerPos - GetScrollPos( SB_HORZ ); // Are we over the divider zone? if ( point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x <= nRelativePos + DRAG_HEADER_OFFSET ) { nFlags |= HITTEST_FLAG_HEADER_DIVIDER; // Are we to the left of the divider (or over last column divider)? if ( ( point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x < nRelativePos ) || nColumn + 1 >= nColumnCount - 1 ) { nFlags |= HITTEST_FLAG_HEADER_LEFT; return TRUE; } // Find last zero-length column after this column for ( int nNextColumn = nColumn + 1; nNextColumn < nColumnCount; nNextColumn++ ) { if ( GetColumnWidth( nNextColumn ) > 0 ) break; nColumn = nNextColumn; } nFlags |= HITTEST_FLAG_HEADER_RIGHT; return TRUE; } // Are we over a column? if ( point.x > nRelativePos - nColumnWidth && point.x < nRelativePos ) return TRUE; } return FALSE; } BOOL HitTest( CPoint point, int& nItem, int& nSubItem ) { T* pT = static_cast(this); // Are we over the header? if ( point.y < ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) return FALSE; // Calculate hit test item nItem = GetTopItem() + (int)( ( point.y - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight ); if ( nItem < 0 || nItem >= pT->GetItemCount() ) return FALSE; int nTotalWidth = 0; int nColumnCount = GetColumnCount(); // Get hit test subitem for ( nSubItem = 0; nSubItem < nColumnCount; nSubItem++ ) { int nColumnWidth = GetColumnWidth( nSubItem ); nTotalWidth += nColumnWidth; // Offset position with current scroll position int nRelativePos = nTotalWidth - GetScrollPos( SB_HORZ ); // Are we over a subitem? if ( point.x > nRelativePos - nColumnWidth && point.x < nRelativePos ) return TRUE; } return FALSE; } BOOL AutoSizeColumn( int nColumn ) { T* pT = static_cast(this); CListColumn listColumn; if ( !GetColumn( nColumn, listColumn ) || listColumn.m_bFixed ) return FALSE; CClientDC dcClient( m_hWnd ); HFONT hOldFont = dcClient.SelectFont( m_fntListFont ); // Set to column text width if zero-length CSize sizeExtent; if ( !dcClient.GetTextExtent( listColumn.m_strText.c_str(), -1, &sizeExtent ) ) return FALSE; int nMaxWidth = sizeExtent.cx + ITEM_WIDTH_MARGIN; CSize sizeIcon = 0; if ( !m_ilItemImages.IsNull() ) m_ilItemImages.GetIconSize( sizeIcon ); // Calculate maximum column width required for ( int nItem = 0; nItem < pT->GetItemCount(); nItem++ ) { if ( !dcClient.GetTextExtent( pT->GetItemText( nItem, listColumn.m_nIndex ), -1, &sizeExtent ) ) return FALSE; if ( !m_ilItemImages.IsNull() && pT->GetItemImage( nItem, listColumn.m_nIndex ) != ITEM_IMAGE_NONE ) sizeExtent.cx += sizeIcon.cx; nMaxWidth = max( nMaxWidth, (int)sizeExtent.cx + ITEM_WIDTH_MARGIN ); } dcClient.SelectFont( hOldFont ); return SetColumnWidth( nColumn, nMaxWidth ); } void ResizeColumn( BOOL bColumnScroll = FALSE ) { HideTitleTip(); int nCurrentPos = GET_X_LPARAM( GetMessagePos() ); CRect rcClient; GetClientRect( rcClient ); int nScrollLimit = GetTotalWidth() - rcClient.Width(); if ( bColumnScroll ) { // Have we finished scrolling list to accommodate new column size? if ( !m_bColumnSizing || !m_bEnableHorizScroll || nCurrentPos - m_nStartScrollPos > 0 ) { KillTimer( RESIZE_COLUMN_TIMER ); // Reset resize start point m_nStartPos = nCurrentPos; m_bResizeTimer = FALSE; } else if ( nCurrentPos < m_nStartPos && GetScrollPos( SB_HORZ ) >= nScrollLimit ) { // Reset start column size m_nStartSize = max( GetColumnWidth( m_nColumnSizing ) + ( nCurrentPos - m_nStartScrollPos ), 0 ); // Resize column SetColumnWidth( m_nColumnSizing, m_nStartSize ); } } else { int nColumnSize = max( m_nStartSize + ( nCurrentPos - m_nStartPos ), 0 ); // Are we scrolled fully to the right and wanting to reduce the size of a column? if ( m_bEnableHorizScroll && GetScrollPos( SB_HORZ ) >= nScrollLimit && nColumnSize < GetColumnWidth( m_nColumnSizing ) ) { if ( !m_bResizeTimer ) { // Only start the scroll timer once m_bResizeTimer = TRUE; // Set new start scroll position m_nStartScrollPos = nCurrentPos; // Start column resize / scroll timer SetTimer( RESIZE_COLUMN_TIMER, RESIZE_COLUMN_PERIOD ); } } else { // Resizing is done in scroll timer (if started) if ( !m_bResizeTimer ) SetColumnWidth( m_nColumnSizing, nColumnSize ); } } } void DragColumn() { HideTitleTip(); CRect rcColumn; if ( !GetColumnRect( m_nHighlightColumn, rcColumn ) ) return; CRect rcHeaderItem( rcColumn ); rcHeaderItem.MoveToXY( 0, 0 ); CListColumn listColumn; if ( !GetColumn( m_nHighlightColumn, listColumn ) ) return; // Store drag column m_nDragColumn = m_nHighlightColumn; CClientDC dcClient( m_hWnd ); CDC dcHeader; dcHeader.CreateCompatibleDC( dcClient ); int nContextState = dcHeader.SaveDC(); // Create drag header bitmap CBitmapHandle bmpHeader; bmpHeader.CreateCompatibleBitmap( dcClient, rcHeaderItem.Width(), rcHeaderItem.Height() ); dcHeader.SelectBitmap( bmpHeader ); dcHeader.SetBkColor( m_rgbHeaderBackground ); dcHeader.ExtTextOut( rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T( "" ), 0, nullptr ); dcHeader.Draw3dRect( rcHeaderItem, m_rgbHeaderBorder, m_rgbHeaderShadow ); CRect rcHeaderText( rcHeaderItem ); rcHeaderText.left += m_nHighlightColumn == 0 ? 4 : 3; rcHeaderText.OffsetRect( 0, 1 ); // Margin header text rcHeaderText.DeflateRect( 4, 0, 5, 0 ); // Has this header item an associated image? if ( listColumn.m_nImage != ITEM_IMAGE_NONE ) { CSize sizeIcon; m_ilListItems.GetIconSize( sizeIcon ); CRect rcHeaderImage; rcHeaderImage.left = listColumn.m_strText.empty() ? ( ( rcHeaderText.left + rcHeaderText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcHeaderText.left; rcHeaderImage.right = min( rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right ); rcHeaderImage.top = ( ( rcHeaderItem.top + rcHeaderItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 ); rcHeaderImage.bottom = min( rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom ); m_ilListItems.DrawEx( listColumn.m_nImage, dcHeader, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); // Offset header text (for image) rcHeaderText.left += sizeIcon.cx + 4; } dcHeader.SelectFont( m_fntListFont ); dcHeader.SetTextColor( m_rgbHeaderText ); dcHeader.SetBkMode( TRANSPARENT ); UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS; if ( listColumn.m_nFlags & ITEM_FLAGS_CENTRE ) nFormat |= DT_CENTER; else if ( listColumn.m_nFlags & ITEM_FLAGS_RIGHT ) nFormat |= DT_RIGHT; else nFormat |= DT_LEFT; // Draw header text if ( !listColumn.m_strText.empty() ) dcHeader.DrawText( listColumn.m_strText.c_str(), (int)listColumn.m_strText.length(), rcHeaderText, nFormat ); dcHeader.RestoreDC( nContextState ); SHDRAGIMAGE shDragImage; ZeroMemory( &shDragImage, sizeof( SHDRAGIMAGE ) ); shDragImage.sizeDragImage.cx = rcHeaderItem.Width(); shDragImage.sizeDragImage.cy = rcHeaderItem.Height(); shDragImage.ptOffset.x = rcColumn.Width() / 2; shDragImage.ptOffset.y = rcColumn.Height() / 2; shDragImage.hbmpDragImage = bmpHeader; shDragImage.crColorKey = m_rgbBackground; // Start header drag operation m_oleDragDrop.DoDragDrop( &shDragImage, DROPEFFECT_MOVE ); // Hide drop arrows after moving column m_wndDropArrows.Hide(); if ( m_bButtonDown ) { ReleaseCapture(); m_bButtonDown = FALSE; m_bBeginSelect = FALSE; m_ptDownPoint = 0; m_ptSelectPoint = 0; } // Finish moving a column if ( m_nHighlightColumn != NULL_COLUMN ) { m_nHighlightColumn = NULL_COLUMN; InvalidateHeader(); } m_nDragColumn = NULL_COLUMN; } BOOL DropColumn( CPoint point ) { if ( !m_bShowHeader ) return FALSE; m_nHotDivider = NULL_COLUMN; m_nHotColumn = NULL_COLUMN; UINT nHeaderFlags = HITTEST_FLAG_NONE; // Are we over the header? if ( HitTestHeader( point, m_nHotColumn, nHeaderFlags ) ) { CRect rcColumn; if ( !GetColumnRect( m_nHotColumn, rcColumn ) ) return FALSE; m_nHotDivider = point.x < ( ( rcColumn.left + rcColumn.right ) / 2 ) ? m_nHotColumn : m_nHotColumn + 1; if ( m_nHotDivider == m_nDragColumn || m_nHotDivider == m_nDragColumn + 1 ) m_nHotDivider = NULL_COLUMN; } if ( m_nHotDivider != NULL_COLUMN ) { CRect rcHeader; GetClientRect( rcHeader ); rcHeader.bottom = m_nHeaderHeight; CPoint ptDivider( 0, rcHeader.Height() / 2 ); CRect rcColumn; int nColumnCount = GetColumnCount(); // Set closest divider position if ( GetColumnRect( m_nHotDivider < nColumnCount ? m_nHotDivider : nColumnCount - 1, rcColumn ) ) ptDivider.x = m_nHotDivider < nColumnCount ? rcColumn.left : rcColumn.right; ClientToScreen( &ptDivider ); // Track drop window m_wndDropArrows.Show( ptDivider ); return TRUE; } m_wndDropArrows.Hide(); return FALSE; } BOOL SortColumn( int nColumn ) { T* pT = static_cast(this); if ( !m_bShowHeader || !m_bShowSort ) return FALSE; int nSortIndex = GetColumnIndex( nColumn ); CWaitCursor curWait; if ( nSortIndex != m_nSortColumn ) { // Sort by new column m_bSortAscending = TRUE; m_nSortColumn = nSortIndex; pT->SortItems( m_nSortColumn, m_bSortAscending ); } else { // Toggle sort order if sorting same column m_bSortAscending = !m_bSortAscending; pT->ReverseItems(); } return ResetSelected(); } BOOL GetSortColumn( int& nColumn, BOOL& bAscending ) { if ( !m_bShowHeader || !m_bShowSort || m_nSortColumn == NULL_COLUMN ) return FALSE; nColumn = m_nSortColumn; bAscending = m_bSortAscending; return TRUE; } BOOL DragItem() { ATLASSERT( FALSE ); // Must be implemented in a derived class return FALSE; } BOOL GroupSelect( CPoint point ) { HideTitleTip(); int nHorzScroll = GetScrollPos( SB_HORZ ); int nVertScroll = GetScrollPos( SB_VERT ); m_rcGroupSelect.left = min( m_ptSelectPoint.x, point.x + nHorzScroll ); m_rcGroupSelect.right = max( m_ptSelectPoint.x, point.x + nHorzScroll ); m_rcGroupSelect.top = min( m_ptSelectPoint.y, point.y + nVertScroll ); m_rcGroupSelect.bottom = max( m_ptSelectPoint.y, point.y + nVertScroll ); if ( m_rcGroupSelect.IsRectEmpty() ) return FALSE; // Select items in group AutoSelect( point ); // Start auto scroll timer SetTimer( ITEM_AUTOSCROLL_TIMER, ITEM_SCROLL_PERIOD ); DWORD dwCurrentTick = GetTickCount(); // Timer messages are a low priority, therefore we need to simulate the timer when moving the mouse if ( ( dwCurrentTick - m_dwScrollTick ) > ITEM_SCROLL_PERIOD - 10 ) { if ( AutoScroll( point ) ) m_dwScrollTick = dwCurrentTick; } // Redraw list immediately return RedrawWindow(); } void AutoSelect( CPoint /*point*/ ) { m_setSelectedItems.clear(); if ( m_rcGroupSelect.left < GetTotalWidth() ) { int nHorzScroll = GetScrollPos( SB_HORZ ); int nVertScroll = GetScrollPos( SB_VERT ); CRect rcGroupSelect( m_rcGroupSelect ); rcGroupSelect.OffsetRect( -nHorzScroll, -nVertScroll ); int nTopItem = GetTopItem(); int nLastItem = nTopItem + ( ( rcGroupSelect.bottom - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight ); nTopItem += ( ( rcGroupSelect.top - ( m_bShowHeader ? m_nHeaderHeight : 0 ) ) / m_nItemHeight ) - ( ( rcGroupSelect.top < 0 ) ? 1 : 0 ); for ( int nItem = nTopItem; nItem <= nLastItem; nItem++ ) { if ( m_setSelectedItems.empty() ) m_nFirstSelected = nItem; m_setSelectedItems.insert( nItem ); m_nFocusItem = nItem; m_nFocusSubItem = NULL_SUBITEM; } } if ( m_setSelectedItems.empty() ) m_nFirstSelected = NULL_ITEM; } BOOL AutoScroll( CPoint point ) { CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); int nHorzScroll = GetScrollPos( SB_HORZ ); int nVertScroll = GetScrollPos( SB_VERT ); BOOL bAutoScroll = FALSE; if ( point.y < rcClient.top ) { SendMessage( WM_VSCROLL, MAKEWPARAM( SB_LINEUP, 0 ) ); int nAutoScroll = GetScrollPos( SB_VERT ); if ( nVertScroll != nAutoScroll ) { m_rcGroupSelect.top = rcClient.top + nAutoScroll - 1; m_rcGroupSelect.bottom = max( m_ptSelectPoint.y, point.y + nAutoScroll - 1 ); bAutoScroll = TRUE; } } if ( point.y > rcClient.bottom ) { SendMessage( WM_VSCROLL, MAKEWPARAM( SB_LINEDOWN, 0 ) ); int nAutoScroll = GetScrollPos( SB_VERT ); if ( nVertScroll != nAutoScroll ) { m_rcGroupSelect.top = min( m_ptSelectPoint.y, point.y + nAutoScroll + 1 ); m_rcGroupSelect.bottom = rcClient.bottom + nAutoScroll + 1; bAutoScroll = TRUE; } } if ( point.x < rcClient.left ) { SendMessage( WM_HSCROLL, MAKEWPARAM( SB_LINELEFT, 0 ) ); int nAutoScroll = GetScrollPos( SB_HORZ ); if ( nHorzScroll != nAutoScroll ) { m_rcGroupSelect.left = rcClient.left + nAutoScroll - 1; m_rcGroupSelect.right = max( m_ptSelectPoint.x, point.x + nAutoScroll - 1 ); bAutoScroll = TRUE; } } if ( point.x > rcClient.right ) { SendMessage( WM_HSCROLL, MAKEWPARAM( SB_LINERIGHT, 0 ) ); int nAutoScroll = GetScrollPos( SB_HORZ ); if ( nHorzScroll != nAutoScroll ) { m_rcGroupSelect.left = min( m_ptSelectPoint.x, point.x + nAutoScroll + 1 ); m_rcGroupSelect.right = rcClient.right + nAutoScroll + 1; bAutoScroll = TRUE; } } // Was the scrolling performed? return bAutoScroll; } BOOL BeginScroll( int nBeginItem, int nEndItem ) { T* pT = static_cast(this); // Any scroll required? if ( nBeginItem == nEndItem ) return FALSE; // Calculate scroll offset m_nScrollOffset = abs( nEndItem - nBeginItem ) * m_nItemHeight; m_nScrollUnit = min( max( m_nScrollOffset / m_nItemHeight, ITEM_SCROLL_UNIT_MIN ), ITEM_SCROLL_UNIT_MAX ); m_nScrollDelta = ( m_nScrollOffset - m_nScrollUnit ) / m_nScrollUnit; m_bScrollDown = ( nBeginItem < nEndItem ); CClientDC dcClient( m_hWnd ); CDC dcScrollList; dcScrollList.CreateCompatibleDC( dcClient ); int nContextState = dcScrollList.SaveDC(); CRect rcScrollList; GetClientRect( rcScrollList ); rcScrollList.bottom = ( GetCountPerPage() + abs( nEndItem - nBeginItem ) ) * m_nItemHeight; if ( !m_bmpScrollList.IsNull() ) m_bmpScrollList.DeleteObject(); m_bmpScrollList.CreateCompatibleBitmap( dcClient, rcScrollList.Width(), rcScrollList.Height() ); dcScrollList.SelectBitmap( m_bmpScrollList ); pT->DrawBkgnd( dcScrollList.m_hDC ); CRect rcItem; rcItem.left = -GetScrollPos( SB_HORZ ); rcItem.right = GetTotalWidth(); rcItem.top = 0; rcItem.bottom = rcItem.top; // Draw all visible items into bitmap for ( int nItem = min( nBeginItem, nEndItem ); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++ ) { rcItem.bottom = rcItem.top + m_nItemHeight; if ( rcItem.top > rcScrollList.bottom ) break; // May be implemented in a derived class pT->DrawItem( dcScrollList.m_hDC, nItem, rcItem ); } dcScrollList.RestoreDC( nContextState ); ScrollList(); // Start scrolling timer SetTimer( ITEM_SCROLL_TIMER, ITEM_SCROLL_PERIOD ); return TRUE; } BOOL EndScroll() { KillTimer( ITEM_SCROLL_TIMER ); if ( !m_bmpScrollList.IsNull() ) m_bmpScrollList.DeleteObject(); return Invalidate(); } BOOL ScrollList() { if ( m_nScrollOffset <= m_nScrollUnit ) m_nScrollOffset--; else { m_nScrollOffset -= m_nScrollDelta; if ( m_nScrollOffset < m_nScrollDelta ) m_nScrollOffset = m_nScrollUnit; } if ( m_bmpScrollList.IsNull() || m_nScrollOffset < 0 ) return FALSE; CClientDC dcClient( m_hWnd ); CDC dcScrollList; dcScrollList.CreateCompatibleDC( dcClient ); CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); HBITMAP hOldBitmap = dcScrollList.SelectBitmap( m_bmpScrollList ); CSize sizScrollBitmap; m_bmpScrollList.GetSize( sizScrollBitmap ); // Draw scrolled list dcClient.BitBlt( 0, rcClient.top, rcClient.Width(), rcClient.Height(), dcScrollList, 0, m_bScrollDown ? ( sizScrollBitmap.cy - ( GetCountPerPage() * m_nItemHeight ) - m_nScrollOffset ) : m_nScrollOffset, SRCCOPY ); dcScrollList.SelectBitmap( hOldBitmap ); return TRUE; } BOOL EditItem( int nItem, int nSubItem = NULL_SUBITEM ) { T* pT = static_cast(this); if ( !EnsureItemVisible( nItem, nSubItem ) ) return FALSE; if ( GetFocus() != m_hWnd ) return FALSE; CRect rcSubItem; if ( !GetItemRect( nItem, nSubItem, rcSubItem ) ) return FALSE; int nIndex = GetColumnIndex( nSubItem ); if ( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) return TRUE; switch ( pT->GetItemFormat( nItem, nIndex ) ) { case ITEM_FORMAT_EDIT: m_bEditItem = TRUE; if ( !RedrawWindow() ) return FALSE; if ( !m_wndItemEdit.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), pT->GetItemText( nItem, nIndex ), pT->GetItemMaxEditLen(nItem, nIndex ) ) ) return FALSE; m_wndItemEdit.SetFont(GetItemFont(nItem, nIndex)); break; case ITEM_FORMAT_DATETIME: { m_bEditItem = TRUE; if ( !RedrawWindow() ) return FALSE; SYSTEMTIME stItemDate; GetItemDate( nItem, nIndex, stItemDate ); if ( !m_wndItemDate.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), stItemDate ) ) return FALSE; } break; case ITEM_FORMAT_COMBO: { m_bEditItem = TRUE; if ( !RedrawWindow() ) return FALSE; CListArray < stdstr > aComboList; if ( !pT->GetItemComboList( nItem, nIndex, aComboList ) ) return FALSE; if ( !m_wndItemCombo.Create( m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags( nItem, nIndex ), pT->GetItemText( nItem, nIndex ), aComboList ) ) return FALSE; } break; } return TRUE; } stdstr FormatDate( SYSTEMTIME& stFormatDate ) { if ( stFormatDate.wYear == 0 ) return _T( "" ); // Format date to local format TCHAR szDateFormat[ DATE_STRING ]; return GetDateFormat( LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stFormatDate, nullptr, szDateFormat, DATE_STRING ) == 0 ? _T( "" ) : szDateFormat; } stdstr FormatTime( SYSTEMTIME& stFormatDate ) { SYSTEMTIME stFormatTime = stFormatDate; stFormatTime.wYear = 0; stFormatTime.wMonth = 0; stFormatTime.wDay = 0; // Format time to local format TCHAR szTimeFormat[ DATE_STRING ]; return GetTimeFormat( LOCALE_USER_DEFAULT, 0, &stFormatTime, nullptr, szTimeFormat, DATE_STRING ) == 0 ? _T( "" ) : szTimeFormat; } void NotifyParent( int nItem, int nSubItem, int nMessage ) { T* pT = static_cast(this); CListNotify listNotify; listNotify.m_hdrNotify.hwndFrom = pT->m_hWnd; listNotify.m_hdrNotify.idFrom = pT->GetDlgCtrlID(); listNotify.m_hdrNotify.code = nMessage; listNotify.m_nItem = nItem; listNotify.m_nSubItem = GetColumnIndex( nSubItem ); listNotify.m_nExitChar = 0; listNotify.m_lpszItemText = nullptr; listNotify.m_lpItemDate = nullptr; // Forward notification to parent FORWARD_WM_NOTIFY( pT->GetParent(), listNotify.m_hdrNotify.idFrom, &listNotify.m_hdrNotify, ::SendMessage ); } BOOL ShowTitleTip( CPoint point, int nItem, int nSubItem ) { T* pT = static_cast(this); // Do not show title tip if editing if ( m_bEditItem ) return FALSE; // Is title tip already shown for this item? if ( nItem == m_nTitleTipItem && nSubItem == m_nTitleTipSubItem ) return FALSE; CRect rcSubItem; if ( !GetItemRect( nItem, nSubItem, rcSubItem ) ) { HideTitleTip(); return FALSE; } int nIndex = GetColumnIndex( nSubItem ); CRect rcItemText( rcSubItem ); // margin item text // rcItemText.left += nSubItem == 0 ? 4 : 3; // rcItemText.DeflateRect( 4, 0 ); // Offset item text (for image) if ( !m_ilItemImages.IsNull() && pT->GetItemImage( nItem, nIndex ) != ITEM_IMAGE_NONE ) { CSize sizeIcon; m_ilItemImages.GetIconSize( sizeIcon ); rcItemText.left += sizeIcon.cx + 4; } // Is current cursor position over item text (not over an icon)? if ( !rcItemText.PtInRect( point ) ) return FALSE; stdstr strItemText; switch ( pT->GetItemFormat( nItem, nIndex ) ) { case ITEM_FORMAT_CHECKBOX: case ITEM_FORMAT_CHECKBOX_3STATE: case ITEM_FORMAT_PROGRESS: break; // No title tip for checkboxes or progress case ITEM_FORMAT_DATETIME: { SYSTEMTIME stItemDate; if ( !GetItemDate( nItem, nIndex, stItemDate ) ) break; UINT nItemFlags = pT->GetItemFlags( nItem, nIndex ); if ( nItemFlags & ITEM_FLAGS_DATE_ONLY ) strItemText = FormatDate( stItemDate ); else if ( nItemFlags & ITEM_FLAGS_TIME_ONLY ) strItemText = FormatTime( stItemDate ); else strItemText = FormatDate( stItemDate ) + _T( " " ) + FormatTime( stItemDate ); } break; default: strItemText = pT->GetItemText( nItem, nIndex ); break; } if ( strItemText.empty() ) { HideTitleTip(); return FALSE; } ClientToScreen( rcItemText ); if ( !m_wndTitleTip.Show( rcItemText, strItemText.c_str(), pT->GetItemToolTip( nItem, nIndex ).c_str() ) ) { HideTitleTip(); return FALSE; } m_nTitleTipItem = nItem; m_nTitleTipSubItem = nSubItem; return TRUE; } BOOL HideTitleTip( BOOL bResetItem = TRUE ) { if ( bResetItem ) { m_nTitleTipItem = NULL_ITEM; m_nTitleTipSubItem = NULL_SUBITEM; } return m_wndTitleTip.Hide(); } BEGIN_MSG_MAP_EX(CListImpl) MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) MSG_WM_SETFOCUS(OnSetFocus) MSG_WM_KILLFOCUS(OnKillFocus) MSG_WM_GETDLGCODE(OnGetDlgCode) MSG_WM_SIZE(OnSize) MSG_WM_HSCROLL(OnHScroll) MSG_WM_VSCROLL(OnVScroll) MSG_WM_CANCELMODE(OnCancelMode) MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST,WM_MOUSELAST,OnMouseRange) MSG_WM_LBUTTONDOWN(OnLButtonDown) MSG_WM_LBUTTONUP(OnLButtonUp) MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) MSG_WM_RBUTTONDOWN(OnRButtonDown) MSG_WM_RBUTTONUP(OnRButtonUp) MSG_WM_MOUSEMOVE(OnMouseMove) #if(_WIN32_WINNT >= 0x0400) MSG_WM_MOUSELEAVE(OnMouseLeave) MSG_WM_MOUSEWHEEL(OnMouseWheel) #endif MSG_WM_TIMER(OnTimer) MSG_WM_KEYDOWN(OnKeyDown) MSG_WM_SYSKEYDOWN(OnSysKeyDown) MSG_WM_SETTINGCHANGE(OnSettingsChange) MSG_WM_SYSCOLORCHANGE(OnSettingsChange) MSG_WM_FONTCHANGE(OnSettingsChange) //MSG_WM_THEMECHANGED(OnSettingsChange) NOTIFY_CODE_HANDLER_EX(LCN_ENDEDIT,OnEndEdit) MESSAGE_HANDLER(WM_CTLCOLORLISTBOX, OnCtlColorListBox) MESSAGE_HANDLER(WM_CTLCOLOREDIT, OnCtlColorListBox) CHAIN_MSG_MAP(CDoubleBufferImpl< CListImpl >) REFLECT_NOTIFICATIONS() END_MSG_MAP() int OnCreate( LPCREATESTRUCT /*lpCreateStruct*/ ) { T* pT = static_cast(this); return pT->Initialise() ? 0 : -1; } void OnDestroy() { m_oleDragDrop.Revoke(); if ( m_wndDropArrows.IsWindow() ) m_wndDropArrows.DestroyWindow(); if ( m_wndTitleTip.IsWindow() ) m_wndTitleTip.DestroyWindow(); if ( m_wndItemEdit.IsWindow() ) m_wndItemEdit.DestroyWindow(); if ( m_wndItemCombo.IsWindow() ) m_wndItemCombo.DestroyWindow(); if ( m_wndItemDate.IsWindow() ) m_wndItemDate.DestroyWindow(); if ( m_ttToolTip.IsWindow() ) m_ttToolTip.DestroyWindow(); } void OnSetFocus( HWND /*hOldWnd*/ ) { Invalidate(); } void OnKillFocus( HWND /*hNewWnd*/ ) { Invalidate(); } UINT OnGetDlgCode( LPMSG /*lpMessage*/ ) { return DLGC_WANTARROWS | DLGC_WANTTAB | DLGC_WANTCHARS; } void OnSize( UINT /*nType*/, CSize /*size*/ ) { // Stop any pending scroll EndScroll(); // End any pending edit if ( m_bEditItem ) SetFocus(); ResetScrollBars( SB_BOTH, -1, FALSE ); Invalidate(); } void OnHScroll( int nSBCode, short /*nPos*/, HWND /*hScrollBar*/ ) { // Stop any pending scroll EndScroll(); // End any pending edit if ( m_bEditItem ) SetFocus(); HideTitleTip(); CRect rcClient; GetClientRect( rcClient ); int nScrollPos = GetScrollPos( SB_HORZ ); switch ( nSBCode ) { case SB_LEFT: nScrollPos = 0; break; case SB_LINELEFT: nScrollPos = max( nScrollPos - ITEM_SCROLL_OFFSET, 0 ); break; case SB_PAGELEFT: nScrollPos = max( nScrollPos - rcClient.Width(), 0 ); break; case SB_RIGHT: nScrollPos = rcClient.Width(); break; case SB_LINERIGHT: nScrollPos = min( nScrollPos + ITEM_SCROLL_OFFSET, GetTotalWidth() ); break; case SB_PAGERIGHT: nScrollPos = min( nScrollPos + rcClient.Width(), GetTotalWidth() ); break; case SB_THUMBPOSITION: case SB_THUMBTRACK: { SCROLLINFO infoScroll; ZeroMemory( &infoScroll, sizeof( SCROLLINFO ) ); infoScroll.cbSize = sizeof( SCROLLINFO ); infoScroll.fMask = SIF_TRACKPOS; // Get 32-bit scroll position if ( !GetScrollInfo( SB_HORZ, &infoScroll ) ) return; // Has scroll position changed? if ( infoScroll.nTrackPos == nScrollPos ) return; nScrollPos = infoScroll.nTrackPos; } break; default: return; } ResetScrollBars( SB_HORZ, nScrollPos, FALSE ); Invalidate(); } void OnVScroll( int nSBCode, short /*nPos*/, HWND /*hScrollBar*/ ) { T* pT = static_cast(this); // End any pending edit if ( m_bEditItem ) SetFocus(); HideTitleTip(); CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); int nScrollPos = GetScrollPos( SB_VERT ); BOOL bScrollList = m_bSmoothScroll; switch ( nSBCode ) { case SB_TOP: nScrollPos = 0; bScrollList = FALSE; break; case SB_LINEUP: nScrollPos = max( nScrollPos - m_nItemHeight, 0 ); break; case SB_PAGEUP: nScrollPos = max( nScrollPos - rcClient.Height(), 0 ); break; case SB_BOTTOM: nScrollPos = pT->GetItemCount() * m_nItemHeight; bScrollList = FALSE; break; case SB_LINEDOWN: nScrollPos += m_nItemHeight; break; case SB_PAGEDOWN: nScrollPos += rcClient.Height(); break; case SB_THUMBTRACK: case SB_THUMBPOSITION: { SCROLLINFO infoScroll; ZeroMemory( &infoScroll, sizeof( SCROLLINFO ) ); infoScroll.cbSize = sizeof( SCROLLINFO ); infoScroll.fMask = SIF_TRACKPOS; // Get 32-bit scroll position if ( !GetScrollInfo( SB_VERT, &infoScroll ) ) return; // Has scroll position changed? if ( infoScroll.nTrackPos == nScrollPos ) return; nScrollPos = infoScroll.nTrackPos; bScrollList = FALSE; } break; case SB_ENDSCROLL: m_bScrolling = FALSE; default: return; } // Store original top item before scrolling int nTopItem = GetTopItem(); ResetScrollBars( SB_VERT, nScrollPos, FALSE ); if ( bScrollList && !m_bScrolling ) m_bScrolling = BeginScroll( nTopItem, GetTopItem() ); else EndScroll(); } void OnCancelMode() { if ( m_bButtonDown ) ReleaseCapture(); HideTitleTip(); m_wndDropArrows.Hide(); m_nDragColumn = NULL_COLUMN; m_nHighlightColumn = NULL_COLUMN; } LRESULT OnMouseRange( UINT nMessage, WPARAM wParam, LPARAM lParam ) { if ( m_ttToolTip.IsWindow() ) { MSG msgRelay = { m_hWnd, nMessage, wParam, lParam }; m_ttToolTip.RelayEvent( &msgRelay ); } SetMsgHandled( FALSE ); return 0; } void OnLButtonDown( UINT nFlags, CPoint point ) { T* pT = static_cast(this); // TODO: Fix? // We have a bug here with setcapture() and the tooltip notifying the parent with mouse messages. // Hard to explain, but what I think happens is that this click is sent by the tooltip, which then // releases capture, so it gets no more mouse events, thus not receiving the actual double click // on the tool tip, and what results is two single clicks for this parent control. // Not sure how to fix - Rowan // Explorer doesn't actually hide the tip when clicked on, so we can remove this code and it shouldn't really matter //HideTitleTip(FALSE); m_bButtonDown = TRUE; m_ptDownPoint = point; m_ptSelectPoint = CPoint( point.x + GetScrollPos( SB_HORZ ), point.y + GetScrollPos( SB_VERT ) ); // Stop any pending scroll EndScroll(); SetFocus(); // Capture all mouse input SetCapture(); int nColumn = NULL_COLUMN; UINT nHeaderFlags = HITTEST_FLAG_NONE; // Are we over the header? if ( HitTestHeader( point, nColumn, nHeaderFlags ) ) { CListColumn listColumn; if ( !GetColumn( nColumn, listColumn ) ) return; if ( !listColumn.m_bFixed && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) ) { SetCursor( m_curDivider ); // Begin column resizing m_bColumnSizing = TRUE; m_nColumnSizing = nColumn; m_nStartSize = listColumn.m_nWidth; m_nStartPos = GET_X_LPARAM( GetMessagePos() ); } else if (m_bSortEnabled) // Added by Rowan 05/12/2006 { m_nHighlightColumn = nColumn; InvalidateHeader(); } return; } int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; if ( !HitTest( point, nItem, nSubItem ) ) { m_nFirstSelected = NULL_ITEM; m_bBeginSelect = TRUE; } else { // Do not begin group select from first columns if ( !( nFlags & MK_SHIFT ) && !( nFlags & MK_CONTROL ) && nSubItem != 0 ) { m_bBeginSelect = TRUE; m_nFirstSelected = nItem; } // Only select item if not already selected if ( ( nFlags & MK_SHIFT ) || ( nFlags & MK_CONTROL ) || !IsSelected( nItem ) || m_setSelectedItems.size() <= 1 ) SelectItem( nItem, nSubItem, nFlags ); int nIndex = GetColumnIndex( nSubItem ); if ( !( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) ) { switch ( pT->GetItemFormat( nItem, nIndex ) ) { case ITEM_FORMAT_CHECKBOX: m_bBeginSelect = FALSE; pT->SetItemText( nItem, nIndex, _ttoi( pT->GetItemText( nItem, nIndex ) ) > 0 ? _T( "0" ) : _T( "1" ) ); NotifyParent( nItem, nSubItem, LCN_MODIFIED ); InvalidateItem( nItem ); break; case ITEM_FORMAT_CHECKBOX_3STATE: { m_bBeginSelect = FALSE; int nCheckImage = _ttoi( pT->GetItemText( nItem, nIndex ) ); if ( nCheckImage < 0 ) pT->SetItemText( nItem, nIndex, _T( "0" ) ); else if ( nCheckImage > 0 ) pT->SetItemText( nItem, nIndex, _T( "-1" ) ); else pT->SetItemText( nItem, nIndex, _T( "1" ) ); NotifyParent( nItem, nSubItem, LCN_MODIFIED ); InvalidateItem( nItem ); } break; case ITEM_FORMAT_HYPERLINK: m_bBeginSelect = FALSE; SetCursor( m_curHyperLink ); NotifyParent( nItem, nSubItem, LCN_HYPERLINK ); break; } if (( pT->GetItemFlags( nItem, nIndex ) & ITEM_FLAGS_HIT_NOTIFY ) != 0) { NotifyParent( nItem, nSubItem, LCN_HITTEST ); } } } } void OnLButtonUp( UINT nFlags, CPoint point ) { if ( m_bButtonDown ) ReleaseCapture(); // Finish resizing or selecting a column if ( m_bColumnSizing || m_nHighlightColumn != NULL_COLUMN ) { // Are we changing the sort order? if ( !m_bColumnSizing && m_nHighlightColumn != NULL_COLUMN && m_bSortEnabled) // Changed by Rowan 05/12/2006 //if ( !m_bColumnSizing && m_nHighlightColumn != NULL_COLUMN) SortColumn( m_nHighlightColumn ); m_bColumnSizing = FALSE; m_nColumnSizing = NULL_COLUMN; m_nHighlightColumn = NULL_COLUMN; m_nStartSize = 0; m_nStartPos = 0; InvalidateHeader(); } m_bBeginSelect = FALSE; m_bButtonDown = FALSE; m_ptDownPoint = 0; m_ptSelectPoint = 0; // Have we finished a group select? if ( m_bGroupSelect ) { m_bGroupSelect = FALSE; Invalidate(); } else { int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; // Deselect item if current item is selected if ( HitTest( point, nItem, nSubItem ) && IsSelected( nItem ) && m_setSelectedItems.size() > 1 && !( nFlags & MK_SHIFT ) && !( nFlags & MK_CONTROL ) ) SelectItem( nItem, nSubItem, nFlags ); // Notify parent of left-click item NotifyParent( nItem, nSubItem, LCN_LEFTCLICK ); } } void OnLButtonDblClk( UINT /*nFlags*/, CPoint point ) { HideTitleTip( FALSE ); // Handle double-clicks (for drawing) SendMessage( WM_LBUTTONDOWN, 0, MAKELPARAM( point.x, point.y ) ); int nColumn = NULL_COLUMN; UINT nHeaderFlags = HITTEST_FLAG_NONE; // Resize column if double-click on a divider if ( HitTestHeader( point, nColumn, nHeaderFlags ) && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) ) AutoSizeColumn( nColumn ); int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; HitTest( point, nItem, nSubItem ); //WriteTraceF(TraceInfo, "List Ctrl Double Click, Item: %d", nItem); // Notify parent of double-clicked item NotifyParent( nItem, nSubItem, LCN_DBLCLICK ); } void OnRButtonDown( UINT nFlags, CPoint point ) { // Stop any pending scroll EndScroll(); SetFocus(); HideTitleTip( FALSE ); int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; if (m_bRightClickSelect) { // Only select item if not already selected (deselect in OnLButtonUp) if (HitTest(point, nItem, nSubItem) && !IsSelected(nItem)) SelectItem(nItem, nSubItem, nFlags); } } void OnRButtonUp( UINT /*nFlags*/, CPoint point ) { int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; if ( !HitTest( point, nItem, nSubItem ) ) ResetSelected(); // Notify parent of right-click item NotifyParent( nItem, nSubItem, LCN_RIGHTCLICK ); } void OnMouseMove( UINT nFlags, CPoint point ) { T* pT = static_cast(this); if ( !( nFlags & MK_LBUTTON ) ) { if ( m_bButtonDown ) ReleaseCapture(); m_bButtonDown = FALSE; } if ( !m_bMouseOver ) { m_bMouseOver = TRUE; TRACKMOUSEEVENT trkMouse; trkMouse.cbSize = sizeof( TRACKMOUSEEVENT ); trkMouse.dwFlags = TME_LEAVE; trkMouse.hwndTrack = m_hWnd; // Notify when the mouse leaves button _TrackMouseEvent( &trkMouse ); } if ( m_bButtonDown ) { // Are we resizing a column? if ( m_bColumnSizing ) { ResizeColumn(); return; } // Are we beginning to drag a column? if ( m_nHighlightColumn != NULL_COLUMN && ( point.x < m_ptDownPoint.x - DRAG_HEADER_OFFSET || point.x > m_ptDownPoint.x + DRAG_HEADER_OFFSET || point.y < m_ptDownPoint.y - DRAG_HEADER_OFFSET || point.y > m_ptDownPoint.y + DRAG_HEADER_OFFSET ) ) { DragColumn(); return; } // Are we beginning a group select or dragging an item? if ( point.x < m_ptDownPoint.x - DRAG_ITEM_OFFSET || point.x > m_ptDownPoint.x + DRAG_ITEM_OFFSET || point.y < m_ptDownPoint.y - DRAG_ITEM_OFFSET || point.y > m_ptDownPoint.y + DRAG_ITEM_OFFSET ) { if ( m_bBeginSelect || !m_bDragDrop ) m_bGroupSelect = ( !m_bSingleSelect && !m_bEditItem ); else { int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; if ( HitTest( point, nItem, nSubItem ) ) { // Select the drag item (if not already selected) if ( !IsSelected( nItem ) ) SelectItem( nItem, nSubItem, nFlags ); // Begin drag item operation pT->DragItem(); } } } if ( m_bGroupSelect ) { GroupSelect( point ); return; } } else { int nColumn = NULL_COLUMN; UINT nHeaderFlags = HITTEST_FLAG_NONE; // Are we over the header? BOOL bHitTestHeader = HitTestHeader( point, nColumn, nHeaderFlags ); if ( bHitTestHeader ) { HideTitleTip(); CListColumn listColumn; if ( GetColumn( nColumn, listColumn ) && !listColumn.m_bFixed && ( nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER ) ) SetCursor( m_curDivider ); else { // Get tooltip for this item stdstr strToolTip = pT->GetHeaderToolTip(nColumn); if(!strToolTip.empty()) { CRect rcColumn; if(!GetColumnRect(nColumn, rcColumn)) return; rcColumn.bottom = m_nHeaderHeight; m_ttToolTip.Activate( TRUE ); m_ttToolTip.AddTool( m_hWnd, (LPCTSTR)strToolTip.c_str(), rcColumn,TOOLTIP_TOOL_ID); } } return; } int nItem = NULL_ITEM; int nSubItem = NULL_SUBITEM; if ( !HitTest( point, nItem, nSubItem ) ) { if ( m_nHotItem != NULL_ITEM && m_nHotSubItem != NULL_SUBITEM ) { // Redraw old hot item int nIndex = GetColumnIndex( m_nHotSubItem ); if ( pT->GetItemFormat( m_nHotItem, nIndex ) == ITEM_FORMAT_HYPERLINK && !( pT->GetItemFlags( m_nHotItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) ) InvalidateItem( m_nHotItem, m_nHotSubItem ); } m_ttToolTip.Activate( FALSE ); m_ttToolTip.DelTool( m_hWnd, TOOLTIP_TOOL_ID ); m_nHotItem = NULL_ITEM; m_nHotSubItem = NULL_SUBITEM; HideTitleTip(); } else { // Has the hot item changed? if ( nItem != m_nHotItem || nSubItem != m_nHotSubItem ) { // Redraw old hot item int nIndex = GetColumnIndex( m_nHotSubItem ); if ( pT->GetItemFormat( m_nHotItem, nIndex ) == ITEM_FORMAT_HYPERLINK && !( pT->GetItemFlags( m_nHotItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) ) InvalidateItem( m_nHotItem, m_nHotSubItem ); m_nHotItem = nItem; m_nHotSubItem = nSubItem; NotifyParent(nItem, nSubItem, LCN_HOTITEMCHANGED); } int nIndex = GetColumnIndex( m_nHotSubItem ); UINT nItemFormat = pT->GetItemFormat( m_nHotItem, nIndex ); UINT nItemFlags = pT->GetItemFlags( m_nHotItem, nIndex ); // Draw new hot hyperlink item if ( nItemFormat == ITEM_FORMAT_HYPERLINK && !( nItemFlags & ITEM_FLAGS_READ_ONLY ) ) { InvalidateItem( m_nHotItem, m_nHotSubItem ); SetCursor( m_curHyperLink ); } // Get tooltip for this item stdstr strToolTip = pT->GetItemToolTip( m_nHotItem, nIndex ); CRect rcSubItem; if ( !strToolTip.empty() && GetItemRect( m_nHotItem, rcSubItem ) ) { m_ttToolTip.Activate( TRUE ); m_ttToolTip.AddTool( m_hWnd, (LPCTSTR)strToolTip.substr(0, SHRT_MAX ).c_str(), rcSubItem, TOOLTIP_TOOL_ID ); } else { m_ttToolTip.Activate( FALSE ); m_ttToolTip.DelTool( m_hWnd, TOOLTIP_TOOL_ID ); } // Show title tips for this item ShowTitleTip( point, m_nHotItem, m_nHotSubItem ); } } } void OnMouseLeave() { m_bMouseOver = FALSE; if ( m_nHotColumn != NULL_COLUMN ) { m_nHotColumn = NULL_COLUMN; InvalidateHeader(); } if ( m_nHotItem != NULL_ITEM || m_nHotSubItem != NULL_SUBITEM ) { m_nHotItem = NULL_ITEM; m_nHotSubItem = NULL_SUBITEM; Invalidate(); } } BOOL OnMouseWheel( UINT /*nFlags*/, short nDelta, CPoint /*point*/ ) { HideTitleTip(); // End any pending edit if ( m_bEditItem ) SetFocus(); int nRowsScrolled = m_nMouseWheelScroll * nDelta / 120; int nScrollPos = GetScrollPos( SB_VERT ); if ( nRowsScrolled > 0 ) nScrollPos = max( nScrollPos - ( nRowsScrolled * m_nItemHeight ), 0 ); else nScrollPos += ( -nRowsScrolled * m_nItemHeight ); ResetScrollBars( SB_VERT, nScrollPos, FALSE ); Invalidate(); return TRUE; } void OnTimer(UINT_PTR nIDEvent ) { switch ( nIDEvent ) { case RESIZE_COLUMN_TIMER: ResizeColumn( TRUE ); break; case ITEM_VISIBLE_TIMER: { KillTimer( ITEM_VISIBLE_TIMER ); int nFocusItem = NULL_ITEM; int nFocusSubItem = NULL_SUBITEM; // Get current focus item if ( !GetFocusItem( nFocusItem, nFocusSubItem ) ) break; // Make sure current focus item is visible before editing if ( !EditItem( nFocusItem, nFocusSubItem ) ) break; } break; case ITEM_AUTOSCROLL_TIMER: if ( !m_bGroupSelect ) KillTimer( ITEM_AUTOSCROLL_TIMER ); else { DWORD dwPoint = GetMessagePos(); CPoint ptMouse( GET_X_LPARAM( dwPoint ), GET_Y_LPARAM( dwPoint ) ); ScreenToClient( &ptMouse ); // Automatically scroll when group selecting AutoScroll( ptMouse ); AutoSelect( ptMouse ); } break; case ITEM_SCROLL_TIMER: if ( !ScrollList() ) EndScroll(); break; } } void OnKeyDown( TCHAR nChar, UINT /*nRepCnt*/, UINT /*nFlags*/ ) { T* pT = static_cast(this); // Stop any pending scroll EndScroll(); BOOL bCtrlKey = ( ( GetKeyState( VK_CONTROL ) & 0x8000 ) != 0 ); BOOL bShiftKey = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 ); CRect rcClient; GetClientRect( rcClient ); int nFocusItem = NULL_ITEM; int nFocusSubItem = NULL_SUBITEM; GetFocusItem( nFocusItem, nFocusSubItem ); switch ( nChar ) { case VK_DOWN: SetFocusItem( min( nFocusItem + 1, pT->GetItemCount() - 1 ), nFocusSubItem ); break; case VK_UP: SetFocusItem( max( nFocusItem - 1, 0 ), nFocusSubItem ); break; case VK_NEXT: SetFocusItem( min( nFocusItem + GetCountPerPage( FALSE ) - 1, pT->GetItemCount() - 1 ), nFocusSubItem ); break; case VK_PRIOR: SetFocusItem( max( nFocusItem - GetCountPerPage( FALSE ) + 1, 0 ), nFocusSubItem ); break; case VK_HOME: SetFocusItem( 0, nFocusSubItem ); break; case VK_END: SetFocusItem( pT->GetItemCount() - 1, nFocusSubItem ); break; case VK_LEFT: if ( m_bFocusSubItem ) SetFocusItem( nFocusItem, max( nFocusSubItem - 1, 0 ) ); else SetScrollPos( SB_HORZ, max( GetScrollPos( SB_HORZ ) - ( bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET ), 0 ) ); break; case VK_RIGHT: if ( m_bFocusSubItem ) SetFocusItem( nFocusItem, min( nFocusSubItem + 1, GetColumnCount() - 1 ) ); else SetScrollPos( SB_HORZ, min( GetScrollPos( SB_HORZ ) + ( bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET ), rcClient.Width() ) ); break; case VK_TAB: if ( !bCtrlKey && m_bFocusSubItem ) SetFocusItem( nFocusItem, bShiftKey ? max( nFocusSubItem - 1, 0 ) : min( nFocusSubItem + 1, GetColumnCount() - 1 ) ); break; default: if ( nChar == VK_SPACE ) { int nIndex = GetColumnIndex( nFocusSubItem ); if ( !( pT->GetItemFlags( nFocusItem, nIndex ) & ITEM_FLAGS_READ_ONLY ) ) { switch ( pT->GetItemFormat( nFocusItem, nIndex ) ) { case ITEM_FORMAT_CHECKBOX: pT->SetItemText( nFocusItem, nIndex, _ttoi( pT->GetItemText( nFocusItem, nIndex ) ) > 0 ? _T( "0" ) : _T( "1" ) ); NotifyParent( nFocusItem, nFocusSubItem, LCN_MODIFIED ); InvalidateItem( nFocusItem ); return; case ITEM_FORMAT_CHECKBOX_3STATE: { int nCheckImage = _ttoi( pT->GetItemText( nFocusItem, nIndex ) ); if ( nCheckImage < 0 ) pT->SetItemText( nFocusItem, nIndex, _T( "0" ) ); else if ( nCheckImage > 0 ) pT->SetItemText( nFocusItem, nIndex, _T( "-1" ) ); else pT->SetItemText( nFocusItem, nIndex, _T( "1" ) ); NotifyParent( nFocusItem, nFocusSubItem, LCN_MODIFIED ); InvalidateItem( nFocusItem ); } return; } } } if ( bCtrlKey && nChar == _T( 'A' ) && !m_bSingleSelect ) { m_setSelectedItems.clear(); for ( int nItem = 0; nItem < pT->GetItemCount(); nItem++ ) m_setSelectedItems.insert( nItem ); Invalidate(); return; } if ( !bCtrlKey && iswprint( nChar ) && iswupper( nChar ) ) { int nSortIndex = GetColumnIndex( m_nSortColumn ); int nStartItem = nFocusItem + 1; DWORD dwCurrentTick = GetTickCount(); stdstr strStart; strStart += nChar; // Has there been another keypress since last search period? if ( ( dwCurrentTick - m_dwSearchTick ) < SEARCH_PERIOD ) { if ( m_strSearchString.substr(0, 1 ) != strStart ) m_strSearchString += nChar; stdstr strFocusText = pT->GetItemText( nFocusItem, nSortIndex ); // Are we continuing to type characters under current focus item? if ( m_strSearchString.length() > 1 && _tcsicmp(m_strSearchString.c_str(),strFocusText.substr(0, m_strSearchString.length() ).c_str() ) == 0 ) { m_dwSearchTick = GetTickCount(); return; } } else { if ( m_strSearchString.substr(0, 1 ) != strStart ) nStartItem = 0; m_strSearchString = strStart; } m_dwSearchTick = GetTickCount(); // Scan for next search string for ( int nFirst = nStartItem; nFirst < pT->GetItemCount(); nFirst++ ) { stdstr strItemText = pT->GetItemText( nFirst, nSortIndex ); if ( _tcsicmp(m_strSearchString.c_str(), strItemText.substr(0, m_strSearchString.length() ).c_str() ) == 0 ) { SelectItem( nFirst, nFocusSubItem, TRUE ); EnsureItemVisible( nFirst, nFocusSubItem ); return; } } // Rescan from top if not found search string for ( int nSecond = 0; nSecond < pT->GetItemCount(); nSecond++ ) { stdstr strItemText = pT->GetItemText( nSecond, nSortIndex ); if ( _tcsicmp(m_strSearchString.c_str(), strItemText.substr(0, m_strSearchString.length() ).c_str() ) == 0 ) { SelectItem( nSecond, nFocusSubItem, TRUE ); EnsureItemVisible( nSecond, nFocusSubItem ); return; } } } return; } if ( !bCtrlKey ) SelectItem( m_nFocusItem, m_nFocusSubItem, bShiftKey ? MK_SHIFT : 0 ); } void OnSysKeyDown( TCHAR /*nChar*/, UINT /*nRepCnt*/, UINT /*nFlags*/ ) { HideTitleTip( FALSE ); SetMsgHandled( FALSE ); } void OnSettingsChange( UINT /*nFlags*/, LPCTSTR /*lpszSection*/ ) { OnSettingsChange(); } void OnSettingsChange() { LoadSettings(); ResetScrollBars(); Invalidate(); } LRESULT OnCtlColorListBox( UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/ ) { return DefWindowProc( nMsg, wParam, lParam ); } LRESULT OnEndEdit( LPNMHDR lpNMHDR ) { T* pT = static_cast(this); CListNotify *pListNotify = reinterpret_cast( lpNMHDR ); m_bEditItem = FALSE; int nIndex = GetColumnIndex( pListNotify->m_nSubItem ); switch ( pListNotify->m_nExitChar ) { case VK_ESCAPE: break; // Do nothing case VK_DELETE: pT->SetItemText( pListNotify->m_nItem, nIndex, _T( "" ) ); NotifyParent( pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED ); break; default: if ( pListNotify->m_lpItemDate == nullptr ) pT->SetItemText( pListNotify->m_nItem, nIndex, pListNotify->m_lpszItemText ); else { if ( _ttoi( pListNotify->m_lpszItemText ) == 0 ) pT->SetItemText( pListNotify->m_nItem, nIndex, _T( "" ) ); else pT->SetItemDate( pListNotify->m_nItem, nIndex, *pListNotify->m_lpItemDate ); } if ( pListNotify->m_nExitChar == VK_TAB ) PostMessage( WM_KEYDOWN, (WPARAM)VK_TAB ); NotifyParent( pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED ); break; } InvalidateItem( pListNotify->m_nItem ); return 0; } DWORD OnDragEnter( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD /*dwKeyState*/, CPoint point ) { DWORD dwEffect = DROPEFFECT_NONE; if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat ) { LPBYTE lpDragHeader = (LPBYTE)GlobalLock( StgMedium.hGlobal ); if ( lpDragHeader == nullptr ) return DROPEFFECT_NONE; // Dragged column must originate from this control if ( *( (HWND*)lpDragHeader ) == m_hWnd ) dwEffect = DropColumn( point ) ? DROPEFFECT_MOVE : DROPEFFECT_NONE; GlobalUnlock( StgMedium.hGlobal ); } return dwEffect; } DWORD OnDragOver( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD /*dwKeyState*/, CPoint point ) { DWORD dwEffect = DROPEFFECT_NONE; if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat ) { LPBYTE lpDragHeader = (LPBYTE)GlobalLock( StgMedium.hGlobal ); if ( lpDragHeader == nullptr ) return DROPEFFECT_NONE; // Dragged column must originate from this control if ( *( (HWND*)lpDragHeader ) == m_hWnd ) dwEffect = DropColumn( point ) ? DROPEFFECT_MOVE : DROPEFFECT_NONE; GlobalUnlock( StgMedium.hGlobal ); } return dwEffect; } BOOL OnDrop( FORMATETC& FormatEtc, STGMEDIUM& /*StgMedium*/, DWORD /*dwEffect*/, CPoint /*point*/ ) { if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat ) { if ( m_nDragColumn != NULL_COLUMN && m_nHotDivider != NULL_COLUMN ) { CListColumn listColumn; if ( !GetColumn( m_nDragColumn, listColumn ) ) return FALSE; // Move column to new position m_aColumns.RemoveAt( m_nDragColumn ); m_aColumns.InsertAt( ( m_nDragColumn < m_nHotColumn ? ( m_nHotDivider == 0 ? 0 : m_nHotDivider - 1 ) : m_nHotDivider ), listColumn ); Invalidate(); } return TRUE; } // Not supported return FALSE; } void OnDragLeave() { } BOOL OnRenderData( FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL /*bDropComplete*/ ) { if ( FormatEtc.cfFormat == m_nHeaderClipboardFormat ) { pStgMedium->tymed = TYMED_HGLOBAL; pStgMedium->hGlobal = GlobalAlloc( GMEM_MOVEABLE, sizeof( HWND ) ); if ( pStgMedium->hGlobal == nullptr ) return FALSE; LPBYTE lpDragHeader = (LPBYTE)GlobalLock( pStgMedium->hGlobal ); if ( lpDragHeader == nullptr ) return FALSE; // Store this window handle *( (HWND*)lpDragHeader ) = m_hWnd; GlobalUnlock( pStgMedium->hGlobal ); return TRUE; } return FALSE; } void DoPaint( CDCHandle dcPaint ) { T* pT = static_cast(this); int nContextState = dcPaint.SaveDC(); pT->DrawBkgnd( dcPaint ); pT->DrawList( dcPaint ); pT->DrawSelect( dcPaint ); pT->DrawHeader( dcPaint ); dcPaint.RestoreDC( nContextState ); } void DrawBkgnd( CDCHandle dcPaint ) { CRect rcClip; if ( dcPaint.GetClipBox( rcClip ) == ERROR ) return; dcPaint.SetBkColor( m_rgbBackground ); dcPaint.ExtTextOut( rcClip.left, rcClip.top, ETO_OPAQUE, rcClip, _T( "" ), 0, nullptr ); CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); if ( !m_bmpBackground.IsNull() && rcClip.bottom > rcClient.top ) { CSize sizBackground; m_bmpBackground.GetSize( sizBackground ); CDC dcBackgroundImage; dcBackgroundImage.CreateCompatibleDC( dcPaint ); HBITMAP hOldBitmap = dcBackgroundImage.SelectBitmap( m_bmpBackground ); if ( m_bTileBackground ) { // Calculate tile image maximum rows and columns div_t divRows = div( (int)rcClient.Height(), (int)sizBackground.cy ); int nTileRows = divRows.rem > 0 ? divRows.quot + 1 : divRows.quot; div_t divColumns = div( (int)rcClient.Width(), (int)sizBackground.cx ); int nTileColumns = divColumns.rem > 0 ? divColumns.quot + 1 : divColumns.quot; // Draw tiled background image for ( int nRow = 0; nRow <= nTileRows; nRow++ ) { for ( int nColumn = 0; nColumn <= nTileColumns; nColumn++ ) dcPaint.BitBlt( nColumn * sizBackground.cx, nRow * sizBackground.cy, sizBackground.cx, sizBackground.cy, dcBackgroundImage, 0, 0, SRCCOPY ); } } else { CRect rcCentreImage( rcClient ); // Horizontally center image if smaller than the client width if ( sizBackground.cx < rcClient.Width() ) { rcCentreImage.left = ( rcClient.Width() / 2 ) - (int)( sizBackground.cx / 2 ); rcCentreImage.right = rcCentreImage.left + sizBackground.cx; } // Vertically center image if smaller than the client height if ( sizBackground.cy + 16 < rcClient.Height() ) { rcCentreImage.top = ( rcClient.Height() / 2 ) - (int)( ( sizBackground.cy + 16 ) / 2 ); rcCentreImage.bottom = rcCentreImage.top + sizBackground.cy; } // Draw centered background image dcPaint.BitBlt( rcCentreImage.left, rcCentreImage.top, rcCentreImage.Width(), rcCentreImage.Height(), dcBackgroundImage, 0, 0, SRCCOPY ); } dcBackgroundImage.SelectBitmap( hOldBitmap ); } } void DrawHeader( CDCHandle dcPaint ) { if ( !m_bShowHeader ) return; CRect rcClip; if ( dcPaint.GetClipBox( rcClip ) == ERROR ) return; CRect rcHeader; GetClientRect( rcHeader ); rcHeader.bottom = m_nHeaderHeight; if ( rcClip.top > rcHeader.bottom ) return; dcPaint.SetBkColor( m_rgbHeaderBackground ); dcPaint.ExtTextOut( rcHeader.left, rcHeader.top, ETO_OPAQUE, rcHeader, _T( "" ), 0, nullptr ); CPen penHighlight; penHighlight.CreatePen( PS_SOLID, 1, m_rgbHeaderBorder ); CPen penShadow; penShadow.CreatePen( PS_SOLID, 1, m_rgbHeaderShadow ); CRect rcHeaderItem( rcHeader ); rcHeaderItem.OffsetRect( -GetScrollPos( SB_HORZ ), 0 ); int nHeaderWidth = 0; for ( int nColumn = 0, nColumnCount = GetColumnCount(); nColumn < nColumnCount; rcHeaderItem.left = rcHeaderItem.right, nColumn++ ) { CListColumn listColumn; if ( !GetColumn( nColumn, listColumn ) ) break; rcHeaderItem.right = rcHeaderItem.left + listColumn.m_nWidth; nHeaderWidth += rcHeaderItem.Width(); if ( rcHeaderItem.right < rcClip.left ) continue; if ( rcHeaderItem.left > rcClip.right ) break; // Draw header and divider if ( nColumn == m_nHighlightColumn ) { dcPaint.SetBkColor( m_rgbHeaderHighlight ); dcPaint.ExtTextOut( rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T( "" ), 0, nullptr ); } dcPaint.SelectPen( penShadow ); dcPaint.MoveTo( rcHeaderItem.right - 1, rcHeaderItem.top + 1 ); dcPaint.LineTo( rcHeaderItem.right - 1, m_nHeaderHeight - 1 ); dcPaint.SelectPen( penHighlight ); dcPaint.MoveTo( rcHeaderItem.right, rcHeaderItem.top + 1 ); dcPaint.LineTo( rcHeaderItem.right, m_nHeaderHeight - 1 ); CRect rcHeaderText( rcHeaderItem ); rcHeaderText.left += nColumn == 0 ? 4 : 3; rcHeaderText.OffsetRect( 0, 1 ); BOOL bShowArrow = m_bShowSort && ( rcHeaderItem.Width() > 15 ); if(listColumn.m_nImage == ITEM_IMAGE_NONE ) { // Offset text bounding rectangle to account for sorting arrow if ( bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn ) rcHeaderText.right -= 15; } // Margin header text rcHeaderText.DeflateRect( 4, 0, 5, 0 ); // Has this header item an associated image? if ( listColumn.m_nImage != ITEM_IMAGE_NONE ) { CSize sizeIcon; m_ilListItems.GetIconSize( sizeIcon ); CRect rcHeaderImage; rcHeaderImage.left = listColumn.m_strText.empty() ? ( ( rcHeaderText.left + rcHeaderText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcHeaderText.left; rcHeaderImage.right = min( rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right - 2 ); rcHeaderImage.top = ( ( rcHeaderItem.top + rcHeaderItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 ); rcHeaderImage.bottom = min( rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom ); if(listColumn.m_nIndex == m_nSortColumn) m_ilListItems.DrawEx( listColumn.m_nImage, dcPaint, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT | ILD_SELECTED ); else m_ilListItems.DrawEx( listColumn.m_nImage, dcPaint, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); // Offset header text (for image) rcHeaderText.left += sizeIcon.cx + 4; } dcPaint.SelectFont( m_fntListFont ); dcPaint.SetTextColor( m_rgbHeaderText ); dcPaint.SetBkMode( TRANSPARENT ); UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS; if ( listColumn.m_nFlags & ITEM_FLAGS_CENTRE ) nFormat |= DT_CENTER; else if ( listColumn.m_nFlags & ITEM_FLAGS_RIGHT ) nFormat |= DT_RIGHT; else nFormat |= DT_LEFT; // Draw header text if ( !rcHeaderText.IsRectEmpty() && !listColumn.m_strText.empty() ) dcPaint.DrawText( listColumn.m_strText.c_str(), (int)listColumn.m_strText.length(), rcHeaderText, nFormat ); // Draw sorting arrow if ( bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn ) { CSize sizeIcon; m_ilListItems.GetIconSize( sizeIcon ); CRect rcSortArrow; rcSortArrow.left = rcHeaderText.right + 4; rcSortArrow.right = min( rcSortArrow.left + sizeIcon.cx, rcHeaderItem.right ); rcSortArrow.top = rcHeaderItem.Height() / 2 - 3; rcSortArrow.bottom = min( rcSortArrow.top + sizeIcon.cy, rcHeaderItem.bottom ); m_ilListItems.DrawEx( m_bSortAscending ? ITEM_IMAGE_UP : ITEM_IMAGE_DOWN, dcPaint, rcSortArrow, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); } } // Draw a frame around all header columns if ( nHeaderWidth > 0 ) dcPaint.Draw3dRect( CRect( rcHeader.left, rcHeader.top, rcHeader.right + 2, rcHeader.bottom ), m_rgbHeaderBorder, m_rgbHeaderShadow ); } void DrawRoundRect( CDCHandle dcPaint, CRect& rcRect, COLORREF rgbOuter, COLORREF rgbInner ) { CRect rcRoundRect( rcRect ); CPen penBorder; penBorder.CreatePen( PS_SOLID, 1, rgbOuter ); CBrush bshInterior; bshInterior.CreateSolidBrush( m_rgbBackground ); dcPaint.SelectPen( penBorder ); dcPaint.SelectBrush( bshInterior ); dcPaint.RoundRect( rcRoundRect, CPoint( 5, 5 ) ); rcRoundRect.DeflateRect( 1, 1 ); CPen penInnerBorder; penInnerBorder.CreatePen( PS_SOLID, 1, rgbInner ); dcPaint.SelectPen( penInnerBorder ); dcPaint.RoundRect( rcRoundRect, CPoint( 2, 2 ) ); } void DrawGradient( CDCHandle dcPaint, CRect& rcRect, COLORREF rgbTop, COLORREF rgbBottom ) { GRADIENT_RECT grdRect = { 0, 1 }; TRIVERTEX triVertext[ 2 ] = { rcRect.left, rcRect.top, (COLOR16)(GetRValue( rgbTop ) << 8), (COLOR16)(GetGValue( rgbTop ) << 8), (COLOR16)(GetBValue( rgbTop ) << 8), (COLOR16)(0x0000), rcRect.right, rcRect.bottom, (COLOR16)(GetRValue( rgbBottom ) << 8), (COLOR16)(GetGValue( rgbBottom ) << 8), (COLOR16)(GetBValue( rgbBottom ) << 8), (COLOR16)(0x0000) }; dcPaint.GradientFill( triVertext, 2, &grdRect, 1, GRADIENT_FILL_RECT_V ); } void DrawList( CDCHandle dcPaint ) { T* pT = static_cast(this); CRect rcClip; if ( dcPaint.GetClipBox( rcClip ) == ERROR ) return; CRect rcItem; rcItem.left = -GetScrollPos( SB_HORZ ); rcItem.right = GetTotalWidth(); rcItem.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); rcItem.bottom = rcItem.top; // Draw all visible items for ( int nItem = GetTopItem(); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++ ) { rcItem.bottom = rcItem.top + m_nItemHeight; if ( rcItem.bottom < rcClip.top || rcItem.right < rcClip.left ) continue; if ( rcItem.top > rcClip.bottom || rcItem.left > rcClip.right ) break; // May be implemented in a derived class pT->DrawItem( dcPaint, nItem, rcItem ); } } void DrawItem( CDCHandle dcPaint, int nItem, CRect& rcItem ) { T* pT = static_cast(this); CRect rcClip; if ( dcPaint.GetClipBox( rcClip ) == ERROR ) return; int nFocusItem = NULL_ITEM; int nFocusSubItem = NULL_SUBITEM; GetFocusItem( nFocusItem, nFocusSubItem ); BOOL bSelectedItem = IsSelected( nItem ); //BOOL bControlFocus = ( GetFocus() == m_hWnd || m_bEditItem ); // Draw selected background if ( bSelectedItem ) { dcPaint.SetBkColor( m_rgbSelectedItem ); dcPaint.ExtTextOut( rcItem.left, rcItem.top, ETO_OPAQUE, rcItem, _T( "" ), 0, nullptr ); } CRect rcSubItem( rcItem ); rcSubItem.right = rcSubItem.left; for ( int nSubItem = 0, nColumnCount = GetColumnCount(); nSubItem < nColumnCount; rcSubItem.left = rcSubItem.right + 1, nSubItem++ ) { CListColumn listColumn; if ( !GetColumn( nSubItem, listColumn ) ) break; rcSubItem.right = rcSubItem.left + listColumn.m_nWidth - 1; if ( rcSubItem.right < rcClip.left || rcSubItem.Width() == 0 ) continue; if ( rcSubItem.left > rcClip.right ) break; LPCTSTR strItemText = pT->GetItemText( nItem, listColumn.m_nIndex ); int nItemImage = pT->GetItemImage( nItem, listColumn.m_nIndex ); UINT nItemFormat = pT->GetItemFormat( nItem, listColumn.m_nIndex ); UINT nItemFlags = pT->GetItemFlags( nItem, listColumn.m_nIndex ); // Custom draw subitem format if ( nItemFormat == ITEM_FORMAT_CUSTOM ) { pT->DrawCustomItem( dcPaint, nItem, nSubItem, rcSubItem ); return; } BOOL bFocusSubItem = ( m_bFocusSubItem && nFocusItem == nItem && nFocusSubItem == nSubItem ); COLORREF rgbBackground = m_rgbBackground; COLORREF rgbText = m_rgbItemText; if ( bFocusSubItem ) { dcPaint.SetBkColor( m_bEditItem ? m_rgbBackground : m_rgbItemFocus ); dcPaint.ExtTextOut( rcSubItem.left, rcSubItem.top, ETO_OPAQUE, rcSubItem, _T( "" ), 0, nullptr ); if ( m_bEditItem ) { CBrush bshSelectFrame; bshSelectFrame.CreateSolidBrush( m_rgbItemFocus ); dcPaint.FrameRect( rcSubItem, bshSelectFrame ); } } else if ( pT->GetItemColours( nItem, nSubItem, rgbBackground, rgbText ) && rgbBackground != m_rgbBackground ) { CPen penBorder; penBorder.CreatePen( PS_SOLID, 1, rgbBackground ); CBrush bshInterior; bshInterior.CreateSolidBrush( rgbBackground ); dcPaint.SelectPen( penBorder ); dcPaint.SelectBrush( bshInterior ); dcPaint.RoundRect( rcSubItem, CPoint( 3, 3 ) ); } CRect rcItemText( rcSubItem ); // margin item text //rcItemText.left += nSubItem == 0 ? 4 : 3; //rcItemText.DeflateRect( 4, 0 ); // Draw subitem image if supplied if ( !m_ilItemImages.IsNull() && nItemImage != ITEM_IMAGE_NONE && ( !m_bEditItem || ( m_bEditItem && !bFocusSubItem ) ) ) { CSize sizeIcon; m_ilItemImages.GetIconSize( sizeIcon ); CRect rcItemImage; rcItemImage.left = (strItemText[0] == 0) ? ( ( rcItemText.left + rcItemText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - ( 0 ) : rcItemText.left; rcItemImage.right = min( rcItemImage.left + sizeIcon.cx, rcSubItem.right ); rcItemImage.top = ( ( rcSubItem.top + rcSubItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 ); rcItemImage.bottom = min( rcItemImage.top + sizeIcon.cy, rcSubItem.bottom ); m_ilItemImages.DrawEx( nItemImage, dcPaint, rcItemImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); // Offset item text (for image) rcItemText.left += sizeIcon.cx + 4; } if ( rcItemText.IsRectEmpty() ) continue; COLORREF rgbSelectedText = m_rgbSelectedText; pT->GetItemSelectedColours( nItem, nSubItem, rgbSelectedText ); dcPaint.SelectFont( pT->GetItemFont( nItem, nSubItem ) ); dcPaint.SetTextColor( ( bSelectedItem && !bFocusSubItem ) ? rgbSelectedText : rgbText ); dcPaint.SetBkMode( TRANSPARENT ); UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS; if ( nItemFlags & ITEM_FLAGS_CENTRE ) nFormat |= DT_CENTER; else if ( nItemFlags & ITEM_FLAGS_RIGHT ) nFormat |= DT_RIGHT; else nFormat |= DT_LEFT; switch ( nItemFormat ) { case ITEM_FORMAT_DATETIME: if ( strItemText[0] != 0 ) { SYSTEMTIME stItemDate; if ( !GetItemDate( nItem, listColumn.m_nIndex, stItemDate ) ) break; stdstr strItemDate; if ( nItemFlags & ITEM_FLAGS_DATE_ONLY ) strItemDate = FormatDate( stItemDate ); else if ( nItemFlags & ITEM_FLAGS_TIME_ONLY ) strItemDate = FormatTime( stItemDate ); else strItemDate = FormatDate( stItemDate ) + _T( " " ) + FormatTime( stItemDate ); dcPaint.DrawText( strItemDate.c_str(), (int)strItemDate.length(), rcItemText, nFormat ); } break; case ITEM_FORMAT_CHECKBOX: case ITEM_FORMAT_CHECKBOX_3STATE: { CSize sizeIcon; m_ilListItems.GetIconSize( sizeIcon ); CRect rcCheckBox; rcCheckBox.left = ( ( rcItemText.left + rcItemText.right ) / 2 ) - ( sizeIcon.cx / 2 ) - 1; rcCheckBox.right = min( rcCheckBox.left + sizeIcon.cx, rcSubItem.right ); rcCheckBox.top = ( ( rcSubItem.top + rcSubItem.bottom ) / 2 ) - ( sizeIcon.cy / 2 ); rcCheckBox.bottom = min( rcCheckBox.top + sizeIcon.cy, rcSubItem.bottom ); int nCheckValue = _ttoi( strItemText ); if ( nItemFormat == ITEM_FORMAT_CHECKBOX ) m_ilListItems.DrawEx( nCheckValue > 0 ? ITEM_IMAGE_CHECK_ON : ITEM_IMAGE_CHECK_OFF, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); else { int nCheckImage = ITEM_IMAGE_3STATE_UNDEF; if ( nCheckValue < 0 ) nCheckImage = ITEM_IMAGE_3STATE_OFF; else if ( nCheckValue > 0 ) nCheckImage = ITEM_IMAGE_3STATE_ON; m_ilListItems.DrawEx( nCheckImage, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT ); } } break; case ITEM_FORMAT_PROGRESS: { CRect rcProgress( rcSubItem ); rcProgress.DeflateRect( 3, 2 ); // Draw progress border DrawRoundRect( dcPaint, rcProgress, m_rgbHeaderShadow, m_rgbHeaderBackground ); // Fill progress bar area rcProgress.DeflateRect( 3, 3 ); rcProgress.right = rcProgress.left + (int)( (double)rcProgress.Width() * ( ( max( min( atof( strItemText ), 100 ), 0 ) ) / 100.0 ) ); DrawGradient( dcPaint, rcProgress, m_rgbProgressTop, m_rgbProgressBottom ); } break; case ITEM_FORMAT_HYPERLINK: if ( nItem == m_nHotItem && nSubItem == m_nHotSubItem && !( nItemFlags & ITEM_FLAGS_READ_ONLY ) ) { dcPaint.SelectFont( m_fntUnderlineFont ); dcPaint.SetTextColor( m_rgbHyperLink ); } default: // Draw item text { size_t len = strlen(strItemText); if ( len > 0 ) dcPaint.DrawText( strItemText, (int)len, rcItemText, nFormat ); } break; } } } void DrawSelect( CDCHandle dcPaint ) { if ( !m_bGroupSelect ) return; int nHorzScroll = GetScrollPos( SB_HORZ ); int nVertScroll = GetScrollPos( SB_VERT ); CRect rcGroupSelect( m_rcGroupSelect ); rcGroupSelect.OffsetRect( -nHorzScroll, -nVertScroll ); CRect rcClient; GetClientRect( rcClient ); rcClient.top = ( m_bShowHeader ? m_nHeaderHeight : 0 ); // Limit box to list client area if scrolled to limits if ( nHorzScroll > ( GetTotalWidth() - rcClient.Width() ) ) rcGroupSelect.right = min( rcClient.right, rcGroupSelect.right ); if ( nHorzScroll == 0 ) rcGroupSelect.left = max( rcClient.left, rcGroupSelect.left ); if ( nVertScroll > ( GetTotalHeight() - rcClient.Height() ) ) rcGroupSelect.bottom = min( rcClient.bottom, rcGroupSelect.bottom ); if ( nVertScroll == 0 ) rcGroupSelect.top = max( rcClient.top, rcGroupSelect.top ); // Limit bitmap to client area CRect rcSelectArea( rcGroupSelect ); rcSelectArea.IntersectRect( rcSelectArea, rcClient ); CDC dcBackground; dcBackground.CreateCompatibleDC( dcPaint ); int nBackgroundContext = dcBackground.SaveDC(); CBitmap bmpBackground; bmpBackground.CreateCompatibleBitmap( dcPaint, rcSelectArea.Width(), rcSelectArea.Height() ); dcBackground.SelectBitmap( bmpBackground ); // Take a copy of existing background dcBackground.BitBlt( 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcPaint, rcSelectArea.left, rcSelectArea.top, SRCCOPY ); CDC dcGroupSelect; dcGroupSelect.CreateCompatibleDC( dcPaint ); int nGroupSelectContext = dcGroupSelect.SaveDC(); CBitmap bmpGroupSelect; bmpGroupSelect.CreateCompatibleBitmap( dcPaint, rcSelectArea.Width(), rcSelectArea.Height() ); dcGroupSelect.SelectBitmap( bmpGroupSelect ); // Draw group select box dcGroupSelect.SetBkColor( m_rgbItemFocus ); dcGroupSelect.ExtTextOut( 0, 0, ETO_OPAQUE, CRect( CPoint( 0 ), rcSelectArea.Size() ), _T( "" ), 0, nullptr ); BLENDFUNCTION blendFunction; blendFunction.BlendOp = AC_SRC_OVER; blendFunction.BlendFlags = 0; blendFunction.SourceConstantAlpha = 180; blendFunction.AlphaFormat = 0; // Blend existing background with selection box dcGroupSelect.AlphaBlend( 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcBackground, 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), blendFunction ); // Draw blended selection box dcPaint.BitBlt( rcSelectArea.left, rcSelectArea.top, rcSelectArea.Width(), rcSelectArea.Height(), dcGroupSelect, 0, 0, SRCCOPY ); // Draw selection box frame CBrush bshSelectFrame; bshSelectFrame.CreateSolidBrush( m_rgbItemText ); dcPaint.FrameRect( rcGroupSelect, bshSelectFrame ); dcBackground.RestoreDC( nBackgroundContext ); dcGroupSelect.RestoreDC( nGroupSelectContext ); } void DrawCustomItem( CDCHandle dcPaint, int /*nItem*/, int /*nSubItem*/, CRect& /*rcSubItem*/ ) { ATLASSERT( FALSE ); // Must be implemented in a derived class } }; struct CSubItem { stdstr m_strText; int m_nImage; UINT m_nFormat; UINT m_nFlags; UINT m_nMaxEditLen; CListArray < stdstr > m_aComboList; HFONT m_hFont; COLORREF m_rgbBackground; COLORREF m_rgbText; COLORREF m_rgbSelectedText; }; template < class TData = DWORD > struct CListItem { CListArray < CSubItem > m_aSubItems; stdstr m_strToolTip; TData m_tData; }; template < class TData > class CListCtrlData : public CListImpl< CListCtrlData< TData > > { public: DECLARE_WND_CLASS( _T( "ListCtrl" ) ) protected: CListArray < CListItem< TData > > m_aItems; public: int AddItem( CListItem< TData >& listItem ) { if ( !m_aItems.Add( listItem ) ) return -1; return CListImpl< CListCtrlData >::AddItem() ? GetItemCount() - 1 : -1; } int AddItemAt( CListItem< TData >& listItem, int Index ) { if (Index < 0 ) { Index = 0; } if (Index > GetItemCount()) { Index = GetItemCount(); } if ( !m_aItems.AddAt( listItem, Index ) ) return -1; return CListImpl< CListCtrlData >::AddItem() ? Index : -1; } int AddItem( LPCTSTR lpszText, int nImage = ITEM_IMAGE_NONE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE ) { CSubItem listSubItem; listSubItem.m_nImage = ITEM_IMAGE_NONE; listSubItem.m_nFormat = nFormat; listSubItem.m_nFlags = ValidateFlags( nFlags ); listSubItem.m_hFont = nullptr; listSubItem.m_rgbBackground = m_rgbBackground; listSubItem.m_rgbText = m_rgbItemText; listSubItem.m_rgbSelectedText = m_rgbSelectedText; listSubItem.m_nMaxEditLen = -1; CListItem< TData > listItem; for ( int nSubItem = 0; nSubItem < GetColumnCount(); nSubItem++ ) listItem.m_aSubItems.Add( listSubItem ); // Set item details for first subitem listItem.m_aSubItems[ 0 ].m_strText = lpszText; listItem.m_aSubItems[ 0 ].m_nImage = nImage; return AddItem( listItem ); } int AddItemAt(int Index, LPCTSTR lpszText, int nImage = ITEM_IMAGE_NONE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE ) { CSubItem listSubItem; listSubItem.m_nImage = ITEM_IMAGE_NONE; listSubItem.m_nFormat = nFormat; listSubItem.m_nFlags = ValidateFlags( nFlags ); listSubItem.m_hFont = nullptr; listSubItem.m_rgbBackground = m_rgbBackground; listSubItem.m_rgbText = m_rgbItemText; listSubItem.m_rgbSelectedText = m_rgbSelectedText; listSubItem.m_nMaxEditLen = (UINT)-1; CListItem< TData > listItem; for ( int nSubItem = 0; nSubItem < GetColumnCount(); nSubItem++ ) listItem.m_aSubItems.Add( listSubItem ); // Set item details for first subitem listItem.m_aSubItems[ 0 ].m_strText = lpszText; listItem.m_aSubItems[ 0 ].m_nImage = nImage; return AddItemAt( listItem, Index ); } BOOL DeleteItem( int nItem ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; return m_aItems.RemoveAt( nItem ) ? CListImpl< CListCtrlData >::DeleteItem( nItem ) : FALSE; } BOOL DeleteAllItems() { m_aItems.RemoveAll(); return CListImpl< CListCtrlData >::DeleteAllItems(); } int GetItemCount() { return m_aItems.GetSize(); } BOOL GetItem( int nItem, CListItem< TData >& listItem ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; listItem = m_aItems[ nItem ]; return TRUE; } BOOL GetItem( int nItem, CListItem< TData >*& listItem ) { if ( nItem < 0 || nItem >= GetItemCount() ) { listItem = nullptr; return FALSE; } listItem = &m_aItems[ nItem ]; return TRUE; } BOOL GetSubItem( int nItem, int nSubItem, CSubItem& listSubItem ) { CListItem< TData > * listItem; if ( !GetItem( nItem, listItem ) ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)listItem->m_aSubItems.GetSize() ) return FALSE; listSubItem = listItem->m_aSubItems[ nSubItem ]; return TRUE; } BOOL GetSubItem( int nItem, int nSubItem, CSubItem *& listSubItem ) { CListItem< TData > * listItem; if ( !GetItem( nItem, listItem ) ) { listSubItem = nullptr; return FALSE; } if ( nSubItem < 0 || nSubItem >= (int)listItem->m_aSubItems.GetSize() ) { listSubItem = nullptr; return FALSE; } listSubItem = &listItem->m_aSubItems[ nSubItem ]; return TRUE; } LPCTSTR GetItemText( int nItem, int nSubItem ) { CSubItem * listSubItem; return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_strText.c_str() : _T( "" ); } UINT GetItemMaxEditLen( int nItem, int nSubItem ) { CSubItem * listSubItem; return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_nMaxEditLen : 0; } int GetItemImage( int nItem, int nSubItem ) { CSubItem *listSubItem; return GetSubItem( nItem, nSubItem, listSubItem ) ? listSubItem->m_nImage : ITEM_IMAGE_NONE; } UINT GetItemFormat( int nItem, int nSubItem ) { CSubItem * listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; return listSubItem->m_nFormat == ITEM_FORMAT_NONE ? GetColumnFormat( IndexToOrder( nSubItem ) ) : listSubItem->m_nFormat; } UINT GetItemFlags( int nItem, int nSubItem ) { CSubItem *listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; return listSubItem->m_nFlags == ITEM_FLAGS_NONE ? GetColumnFlags( IndexToOrder( nSubItem ) ) : listSubItem->m_nFlags; } BOOL GetItemComboList( int nItem, int nSubItem, CListArray < stdstr >& aComboList ) { CSubItem listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; aComboList = listSubItem.m_aComboList; return aComboList.IsEmpty() ? GetColumnComboList( IndexToOrder( nSubItem ), aComboList ) : !aComboList.IsEmpty(); } HFONT GetItemFont( int nItem, int nSubItem ) { CSubItem * listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; return listSubItem->m_hFont == nullptr ? CListImpl< CListCtrlData >::GetItemFont( nItem, nSubItem ) : listSubItem->m_hFont; } BOOL GetItemColours( int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText ) { CSubItem *listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; rgbBackground = listSubItem->m_rgbBackground; rgbText = listSubItem->m_rgbText; return TRUE; } BOOL GetItemSelectedColours( int nItem, int nSubItem, COLORREF& rgbSelectedText ) { CSubItem *listSubItem; if ( !GetSubItem( nItem, nSubItem, listSubItem ) ) return FALSE; rgbSelectedText = listSubItem->m_rgbSelectedText; return TRUE; } stdstr GetItemToolTip( int nItem, int /*nSubItem*/ ) { CListItem< TData > listItem; return GetItem( nItem, listItem ) ? listItem.m_strToolTip : _T( "" ); } BOOL GetItemData( int nItem, TData& tData ) { CListItem< TData > listItem; if ( !GetItem( nItem, listItem ) ) return FALSE; tData = listItem.m_tData; return TRUE; } BOOL SetItemText( int nItem, int nSubItem, LPCTSTR lpszText, bool bInvalidateItem = true) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_strText = lpszText; if (bInvalidateItem) InvalidateItem(nItem, nSubItem); return TRUE; } BOOL SetItemComboIndex( int nItem, int nSubItem, int nIndex ) { CListArray < stdstr > aComboList; if ( !GetItemComboList( nItem, nSubItem, aComboList ) ) return FALSE; return SetItemText( nItem, nSubItem, nIndex < 0 || nIndex >= aComboList.GetSize() ? _T( "" ) : aComboList[ nIndex ] ); } BOOL SetItemImage( int nItem, int nSubItem, int nImage ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nImage = nImage; return TRUE; } BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags; return TRUE; } BOOL SetItemFormat( int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < stdstr >& aComboList ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_aComboList = aComboList; return TRUE; } BOOL SetItemMaxEditLen( int nItem, int nSubItem, UINT nMaxEditLen ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nMaxEditLen = nMaxEditLen; return TRUE; } BOOL SetItemFont( int nItem, int nSubItem, HFONT hFont ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_hFont = hFont; return TRUE; } BOOL SetItemColours( int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbBackground = rgbBackground; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbText = rgbText; return TRUE; } BOOL SetItemHighlightColours( int nItem, int nSubItem, COLORREF rgbSelectedText ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; if ( nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize() ) return FALSE; m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbSelectedText = rgbSelectedText; return TRUE; } void ReverseItems() { m_aItems.Reverse(); } class CompareItem { public: CompareItem( int nColumn ) : m_nColumn( nColumn ) {} inline bool operator() ( const CListItem< TData >& listItem1, const CListItem< TData >& listItem2 ) { return ( _tcscmp(listItem1.m_aSubItems[ m_nColumn ].m_strText.c_str(), listItem2.m_aSubItems[ m_nColumn ].m_strText.c_str() ) < 0 ); } protected: int m_nColumn; }; void SortItems( int nColumn, BOOL /*bAscending*/ ) { m_aItems.Sort( CompareItem( nColumn ) ); } BOOL SetItemToolTip( int nItem, LPCTSTR lpszToolTip ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; m_aItems[ nItem ].m_strToolTip = lpszToolTip; return TRUE; } BOOL SetItemData( int nItem, TData& tData ) { if ( nItem < 0 || nItem >= GetItemCount() ) return FALSE; m_aItems[ nItem ].m_tData = tData; return TRUE; } }; typedef CListCtrlData< DWORD > CListCtrl;