3911 lines
125 KiB
C++
3911 lines
125 KiB
C++
// CListCtrl - A WTL list control with Windows Vista style item selection
|
|
// Revision: 1.5
|
|
// Last modified: 2nd November 2016
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4838) // warning C4838: conversion from 'int' to 'UINT' requires a narrowing conversion
|
|
#include <wtl/atlctrlx.h>
|
|
#include <wtl/atlframe.h>
|
|
#include <wtl/atlgdi.h>
|
|
#include <wtl/atlmisc.h>
|
|
#pragma warning(pop)
|
|
|
|
#include "DragDrop.h"
|
|
#include "DropArrows.h"
|
|
#include "ListCombo.h"
|
|
#include "ListDate.h"
|
|
#include "ListEdit.h"
|
|
#include "TitleTip.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<T *>(this);
|
|
return CWindowImpl<CListImpl>::SubclassWindow(hWnd) ? pT->Initialise() : FALSE;
|
|
}
|
|
|
|
void RegisterClass()
|
|
{
|
|
T * pT = static_cast<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(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<T *>(this);
|
|
CListNotify * pListNotify = reinterpret_cast<CListNotify *>(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<T *>(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<T *>(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<T *>(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;
|