Kawe Mazidjatari 04bee896be Fix string/wstring type conflict
cppkore uses string/wstring as StringBase while we use std::string/std::wstring as string/wstring. Changed all types in cppkore to String/WString instead.
2022-05-21 21:51:35 +02:00

1428 lines
35 KiB
C++

#include "stdafx.h"
#include "ListView.h"
namespace Forms
{
ListView::ListView()
: Control(), Columns(this), Items(this), _ItemActivation(ItemActivation::Standard), _ListViewAlignment(ListViewAlignment::Top), _BorderStyle(BorderStyle::Fixed3D), _ColumnHeaderStyle(ColumnHeaderStyle::Clickable), _SortOrder(SortOrder::None), _ViewStyle(View::LargeIcon), _VirtualListSize(0), _ColumnIndex(0), _DownButton(MouseButtons::None), _Flags(ListViewFlags::FlagScrollable | ListViewFlags::FlagMultiSelect | ListViewFlags::FlagLabelWrap | ListViewFlags::FlagHideSelection | ListViewFlags::FlagAutoArrange | ListViewFlags::FlagShowGroups)
{
SetStyle(ControlStyles::UserPaint | ControlStyles::StandardClick | ControlStyles::UseTextForAccessibility, false);
// Default back color is different...
this->_BackColor = Drawing::GetSystemColor(Drawing::SystemColors::Window);
// We are a ListView control.
this->_RTTI = ControlTypes::ListView;
}
ItemActivation ListView::Activation()
{
return _ItemActivation;
}
void ListView::SetActivation(ItemActivation Value)
{
if (_ItemActivation != Value)
{
_ItemActivation = Value;
UpdateExtendedStyles();
}
}
ListViewAlignment ListView::Alignment()
{
return this->_ListViewAlignment;
}
void ListView::SetAlignment(ListViewAlignment Value)
{
if (_ListViewAlignment != Value)
{
_ListViewAlignment = Value;
UpdateStyles();
}
}
bool ListView::AllowColumnReorder()
{
return GetFlag(ListViewFlags::FlagAllowColumnReorder);
}
void ListView::SetAllowColumnReorder(bool Value)
{
if (AllowColumnReorder() != Value)
{
SetFlag(ListViewFlags::FlagAllowColumnReorder, Value);
UpdateExtendedStyles();
}
}
bool ListView::AutoArrange()
{
return GetFlag(ListViewFlags::FlagAutoArrange);
}
void ListView::SetAutoArrange(bool Value)
{
if (AutoArrange() != Value)
{
SetFlag(ListViewFlags::FlagAutoArrange, Value);
UpdateStyles();
}
}
BorderStyle ListView::GetBorderStyle()
{
return this->_BorderStyle;
}
void ListView::SetBorderStyle(BorderStyle Value)
{
if (_BorderStyle != Value)
{
_BorderStyle = Value;
UpdateStyles();
}
}
int32_t ListView::FocusedItem()
{
return (int32_t)SendMessageA(this->_Handle, LVM_GETNEXTITEM, (WPARAM)-1, LVNI_FOCUSED);
}
void ListView::SetFocusedItem(int32_t Value)
{
if (GetState(ControlStates::StateCreated))
{
LVITEMA LvItem{};
LvItem.mask = LVIF_STATE;
LvItem.state = LVNI_FOCUSED;
LvItem.stateMask = LVIS_FOCUSED;
SendMessageA(this->_Handle, LVM_SETITEMSTATE, (WPARAM)Value, (LPARAM)&LvItem);
}
}
bool ListView::FullRowSelect()
{
return GetFlag(ListViewFlags::FlagFullRowSelect);
}
void ListView::SetFullRowSelect(bool Value)
{
if (FullRowSelect() != Value)
{
SetFlag(ListViewFlags::FlagFullRowSelect, Value);
UpdateExtendedStyles();
}
}
bool ListView::GridLines()
{
return GetFlag(ListViewFlags::FlagGridLines);
}
void ListView::SetGridLines(bool Value)
{
if (GridLines() != Value)
{
SetFlag(ListViewFlags::FlagGridLines, Value);
UpdateExtendedStyles();
}
}
ColumnHeaderStyle ListView::HeaderStyle()
{
return this->_ColumnHeaderStyle;
}
void ListView::SetHeaderStyle(ColumnHeaderStyle Value)
{
if (_ColumnHeaderStyle != Value)
{
_ColumnHeaderStyle = Value;
UpdateStyles();
}
}
bool ListView::HideSelection()
{
return GetFlag(ListViewFlags::FlagHideSelection);
}
void ListView::SetHideSelection(bool Value)
{
if (HideSelection() != Value)
{
SetFlag(ListViewFlags::FlagHideSelection, Value);
UpdateStyles();
}
}
bool ListView::HotTracking()
{
return GetFlag(ListViewFlags::FlagHotTracking);
}
void ListView::SetHotTracking(bool Value)
{
if (HotTracking() != Value)
{
SetFlag(ListViewFlags::FlagHotTracking, Value);
if (Value)
{
SetHoverSelection(true);
SetActivation(ItemActivation::OneClick);
}
}
}
bool ListView::HoverSelection()
{
return GetFlag(ListViewFlags::FlagHoverSelection);
}
void ListView::SetHoverSelection(bool Value)
{
if (HoverSelection() != Value)
{
SetFlag(ListViewFlags::FlagHoverSelection, Value);
UpdateExtendedStyles();
}
}
bool ListView::LabelEdit()
{
return GetFlag(ListViewFlags::FlagLabelEdit);
}
void ListView::SetLabelEdit(bool Value)
{
if (LabelEdit() != Value)
{
SetFlag(ListViewFlags::FlagLabelEdit, Value);
UpdateStyles();
}
}
bool ListView::LabelWrap()
{
return GetFlag(ListViewFlags::FlagLabelWrap);
}
void ListView::SetLabelWrap(bool Value)
{
if (LabelWrap() != Value)
{
SetFlag(ListViewFlags::FlagLabelWrap, Value);
UpdateStyles();
}
}
bool ListView::MultiSelect()
{
return GetFlag(ListViewFlags::FlagMultiSelect);
}
void ListView::SetMultiSelect(bool Value)
{
if (MultiSelect() != Value)
{
SetFlag(ListViewFlags::FlagMultiSelect, Value);
UpdateStyles();
}
}
bool ListView::OwnerDraw()
{
return GetFlag(ListViewFlags::FlagOwnerDraw);
}
void ListView::SetOwnerDraw(bool Value)
{
if (OwnerDraw() != Value)
{
SetFlag(ListViewFlags::FlagOwnerDraw, Value);
Invalidate(true);
}
}
bool ListView::Scrollable()
{
return GetFlag(ListViewFlags::FlagScrollable);
}
void ListView::SetScrollable(bool Value)
{
if (Scrollable() != Value)
{
SetFlag(ListViewFlags::FlagScrollable, Value);
UpdateStyles();
}
}
List<uint32_t> ListView::SelectedIndices()
{
// Fetch count of selected items
int32_t SelectedCount = (int32_t)(GetState(ControlStates::StateCreated) ? SendMessageA(this->_Handle, LVM_GETSELECTEDCOUNT, NULL, NULL) : -1);
// Ignore false counts
if (SelectedCount <= 0)
return List<uint32_t>();
// Generate a new list
List<uint32_t> Result(SelectedCount, true);
int32_t LastIndex = -1;
for (int32_t i = 0; i < SelectedCount; i++)
{
LastIndex = (int32_t)SendMessageA(this->_Handle, LVM_GETNEXTITEM, (WPARAM)LastIndex, LVNI_SELECTED);
Result[i] = (uint32_t)LastIndex;
}
return Result;
}
bool ListView::ShowGroups()
{
return GetFlag(ListViewFlags::FlagShowGroups);
}
void ListView::SetShowGroups(bool Value)
{
if (ShowGroups() != Value)
{
SetFlag(ListViewFlags::FlagShowGroups, Value);
}
}
SortOrder ListView::Sorting()
{
return this->_SortOrder;
}
void ListView::SetSorting(SortOrder Value)
{
if (_SortOrder != Value)
{
_SortOrder = Value;
UpdateStyles();
}
}
int32_t ListView::TopItem()
{
return (int32_t)(GetState(ControlStates::StateCreated) ? SendMessageA(this->_Handle, LVM_GETTOPINDEX, NULL, NULL) : -1);
}
void ListView::SetTopItem(int32_t Value)
{
if (GetState(ControlStates::StateCreated))
{
if (Value <= 0)
Value = 0;
// Make sure it's visible first
SendMessageA(this->_Handle, LVM_ENSUREVISIBLE, (WPARAM)Value, NULL);
if (Scrollable())
{
// Scroll to top
int32_t ScrollY = GetItemPosition(0).Y - GetItemPosition(Value).Y;
SendMessageA(this->_Handle, LVM_SCROLL, 0, ScrollY);
}
}
}
int32_t ListView::CountPerPage()
{
return (int32_t)SendMessageA(this->_Handle, LVM_GETCOUNTPERPAGE, NULL, NULL);
}
View ListView::GetView()
{
return this->_ViewStyle;
}
void ListView::SetView(View Value)
{
if (_ViewStyle != Value)
{
_ViewStyle = Value;
if (GetState(ControlStates::StateCreated))
{
SendMessageA(this->_Handle, LVM_SETVIEW, (WPARAM)_ViewStyle, NULL);
}
else
{
UpdateStyles();
}
}
}
int32_t ListView::VirtualListSize()
{
return this->_VirtualListSize;
}
void ListView::SetVirtualListSize(int32_t Value)
{
if (_VirtualListSize == Value)
return;
bool KeepTopItem = GetState(ControlStates::StateCreated) && this->VirtualMode() && this->_ViewStyle == View::Details;
int32_t TopIndex = -1;
if (KeepTopItem)
TopIndex = (int32_t)SendMessageA(this->_Handle, LVM_GETTOPINDEX, NULL, NULL);
_VirtualListSize = Value;
if (GetState(ControlStates::StateCreated) && this->VirtualMode())
SendMessageA(this->_Handle, LVM_SETITEMCOUNT, (WPARAM)_VirtualListSize, NULL);
if (KeepTopItem)
{
TopIndex = min(TopIndex, this->_VirtualListSize - 1);
if (TopIndex > 0)
this->SetTopItem(TopIndex);
}
}
bool ListView::VirtualMode()
{
return GetFlag(ListViewFlags::FlagVirtualMode);
}
void ListView::SetVirtualMode(bool Value)
{
if (VirtualMode() != Value)
{
SetFlag(ListViewFlags::FlagVirtualMode, Value);
UpdateStyles();
}
}
void ListView::OnHandleCreated()
{
// Call tbe base event first
Control::OnHandleCreated();
// Ensure we have a proper comctl version
int32_t Version = (int32_t)SendMessageA(this->_Handle, CCM_GETVERSION, NULL, NULL);
if (Version < 5)
SendMessageA(this->_Handle, CCM_SETVERSION, (WPARAM)5, NULL);
// Ensure extended styles are set
UpdateExtendedStyles();
// Update control colors
SendMessageA(this->_Handle, LVM_SETBKCOLOR, NULL, (LPARAM)Drawing::ColorToWin32(this->BackColor()));
SendMessageA(this->_Handle, LVM_SETTEXTCOLOR, NULL, (LPARAM)Drawing::ColorToWin32(this->ForeColor()));
SendMessageA(this->_Handle, LVM_SETTEXTBKCOLOR, NULL, CLR_NONE);
if (!Scrollable())
{
auto Style = (int32_t)GetWindowLong(this->_Handle, GWL_STYLE);
Style |= LVS_NOSCROLL;
SetWindowLong(this->_Handle, GWL_STYLE, Style);
}
SendMessageA(this->_Handle, LVM_SETVIEW, (WPARAM)_ViewStyle, NULL);
auto ColumnCount = Columns.Count();
if (ColumnCount > 0)
{
auto Indices = std::make_unique<int32_t[]>(ColumnCount);
int32_t Index = 0;
for (auto& Column : this->Columns)
{
Indices[Index] = Column.DisplayIndex();
NativeInsertColumn(Index++, Column);
}
SetDisplayIndices(Indices, ColumnCount);
}
if (!VirtualMode())
this->NativeInsertItems(0, Items.Count(), Items.ToList());
if (VirtualMode() && VirtualListSize() > -1)
SendMessageA(this->_Handle, LVM_SETITEMCOUNT, (WPARAM)VirtualListSize(), NULL);
}
void ListView::OnBackColorChanged()
{
SendMessageA(this->_Handle, LVM_SETBKCOLOR, NULL, (LPARAM)Drawing::ColorToWin32(this->BackColor()));
// We must call the base event last
Control::OnBackColorChanged();
}
void ListView::OnForeColorChanged()
{
SendMessageA(this->_Handle, LVM_SETTEXTCOLOR, NULL, (LPARAM)Drawing::ColorToWin32(this->ForeColor()));
// We must call the base event last
Control::OnForeColorChanged();
}
void ListView::OnFontChanged()
{
Control::OnFontChanged();
if (!VirtualMode() && GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, LVM_UPDATE, (WPARAM)-1, NULL);
InvalidateColumnHeaders();
}
void ListView::OnItemActivate()
{
ItemActivate.RaiseEvent(this);
}
void ListView::OnColumnClick(const std::unique_ptr<ColumnClickEventArgs>& EventArgs)
{
ColumnClick.RaiseEvent(EventArgs, this);
}
void ListView::OnBeforeLabelEdit(const std::unique_ptr<LabelEditEventArgs>& EventArgs)
{
BeforeLabelEdit.RaiseEvent(EventArgs, this);
}
void ListView::OnAfterLabelEdit(const std::unique_ptr<LabelEditEventArgs>& EventArgs)
{
AfterLabelEdit.RaiseEvent(EventArgs, this);
}
void ListView::OnDrawItem(const std::unique_ptr<DrawListViewItemEventArgs>& EventArgs)
{
DrawItem.RaiseEvent(EventArgs, this);
}
void ListView::OnDrawSubItem(const std::unique_ptr<DrawListViewSubItemEventArgs>& EventArgs)
{
DrawSubItem.RaiseEvent(EventArgs, this);
}
void ListView::OnDrawColumnHeader(const std::unique_ptr<DrawListViewColumnHeaderEventArgs>& EventArgs)
{
DrawColumnHeader.RaiseEvent(EventArgs, this);
}
void ListView::OnCacheVirtualItems(const std::unique_ptr<CacheVirtualItemsEventArgs>& EventArgs)
{
CacheVirtualItems.RaiseEvent(EventArgs, this);
}
void ListView::OnRetrieveVirtualItem(const std::unique_ptr<RetrieveVirtualItemEventArgs>& EventArgs)
{
RetrieveVirtualItem.RaiseEvent(EventArgs, this);
}
void ListView::OnVirtualItemsSelectionRangeChanged(const std::unique_ptr<ListViewVirtualItemsSelectionRangeChangedEventArgs>& EventArgs)
{
VirtualItemsSelectionRangeChanged.RaiseEvent(EventArgs, this);
}
void ListView::WndProc(Message& Msg)
{
switch (Msg.Msg)
{
case WM_REFLECT + WM_NOTIFY:
WmReflectNotify(Msg);
break;
case WM_LBUTTONDBLCLK:
SetCapture(true);
WmMouseDown(Msg, MouseButtons::Left, 2);
break;
case WM_LBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Left, 1);
_DownButton = MouseButtons::Left;
break;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
{
LVHITTESTINFO LvHip{};
auto Index = GetIndexOfClickedItem(LvHip);
if (GetFlag(ListViewFlags::FlagDoubleclickFired) && Index != -1)
{
SetFlag(ListViewFlags::FlagDoubleclickFired, false);
OnDoubleClick();
OnMouseDoubleClick(std::make_unique<MouseEventArgs>(_DownButton, 2, (int16_t)LOWORD(Msg.LParam), (int16_t)HIWORD(Msg.LParam), 0));
}
if (!GetFlag(ListViewFlags::FlagMouseUpFired))
{
OnMouseUp(std::make_unique<MouseEventArgs>(_DownButton, 1, (int16_t)LOWORD(Msg.LParam), (int16_t)HIWORD(Msg.LParam), 0));
SetFlag(ListViewFlags::FlagExpectingMouseUp, false);
}
SetFlag(ListViewFlags::FlagMouseUpFired, true);
SetCapture(false);
}
break;
case WM_MBUTTONDBLCLK:
WmMouseDown(Msg, MouseButtons::Middle, 2);
break;
case WM_MBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Middle, 1);
_DownButton = MouseButtons::Middle;
break;
case WM_RBUTTONDBLCLK:
WmMouseDown(Msg, MouseButtons::Right, 2);
break;
case WM_RBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Right, 1);
_DownButton = MouseButtons::Right;
break;
case WM_MOUSEMOVE:
if (GetFlag(ListViewFlags::FlagExpectingMouseUp) && !GetFlag(ListViewFlags::FlagMouseUpFired) && GetMouseButtons() == MouseButtons::None)
{
OnMouseUp(std::make_unique<MouseEventArgs>(_DownButton, 1, (int16_t)LOWORD(Msg.LParam), (int16_t)HIWORD(Msg.LParam), 0));
SetFlag(ListViewFlags::FlagMouseUpFired, true);
}
SetCapture(false);
Control::WndProc(Msg);
break;
case WM_NOTIFY:
if (WmNotify(Msg))
return;
Control::WndProc(Msg);
break;
case WM_MOUSEHOVER:
if (HoverSelection())
Control::WndProc(Msg);
else
OnMouseHover();
break;
default:
Control::WndProc(Msg);
break;
}
}
void ListView::CreateControl(Control* Parent)
{
INITCOMMONCONTROLSEX iCC{};
iCC.dwSize = sizeof(iCC);
iCC.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&iCC);
Control::CreateControl(Parent);
}
CreateParams ListView::GetCreateParams()
{
auto Cp = Control::GetCreateParams();
Cp.ClassName = "SysListView32";
if (GetState(ControlStates::StateCreated))
{
int32_t CurrentStyle = (int32_t)GetWindowLong(this->_Handle, GWL_STYLE);
Cp.Style |= (CurrentStyle & (WS_HSCROLL | WS_VSCROLL));
}
Cp.Style |= LVS_SHAREIMAGELISTS;
switch (_ListViewAlignment)
{
case ListViewAlignment::Top:
Cp.Style |= LVS_ALIGNTOP;
break;
case ListViewAlignment::Left:
Cp.Style |= LVS_ALIGNLEFT;
break;
}
if (AutoArrange())
Cp.Style |= LVS_AUTOARRANGE;
switch (_BorderStyle)
{
case BorderStyle::Fixed3D:
Cp.ExStyle |= WS_EX_CLIENTEDGE;
break;
case BorderStyle::FixedSingle:
Cp.Style |= WS_BORDER;
break;
}
switch (_ColumnHeaderStyle)
{
case ColumnHeaderStyle::None:
Cp.Style |= LVS_NOCOLUMNHEADER;
break;
case ColumnHeaderStyle::NonClickable:
Cp.Style |= LVS_NOSORTHEADER;
break;
}
if (LabelEdit())
Cp.Style |= LVS_EDITLABELS;
if (!LabelWrap())
Cp.Style |= LVS_NOLABELWRAP;
if (!HideSelection())
Cp.Style |= LVS_SHOWSELALWAYS;
if (!MultiSelect())
Cp.Style |= LVS_SINGLESEL;
switch (_SortOrder)
{
case SortOrder::Ascending:
Cp.Style |= LVS_SORTASCENDING;
break;
case SortOrder::Descending:
Cp.Style |= LVS_SORTDESCENDING;
break;
}
if (VirtualMode())
Cp.Style |= LVS_OWNERDATA;
if (_ViewStyle != View::Tile)
Cp.Style |= (int32_t)_ViewStyle;
return Cp;
}
void ListView::ScaleControl(Drawing::SizeF Factor, BoundsSpecified Specified)
{
Control::ScaleControl(Factor, Specified);
// We override this so we can scale the current column widths
for (auto& Column : this->Columns)
{
Column.SetWidth(this->ScaleSize({ Column.Width(), 0 }, Factor.Width, Factor.Height).Width);
}
}
bool ListView::GetFlag(ListViewFlags Flag)
{
return ((int)this->_Flags & (int)Flag) == (int)Flag;
}
void ListView::SetFlag(ListViewFlags Flags, bool Value)
{
this->_Flags = Value ? (ListViewFlags)((int)this->_Flags | (int)Flags) : (ListViewFlags)((int)this->_Flags & ~(int)Flags);
}
void ListView::NativeAddColumn(ColumnHeader& Header)
{
NativeInsertColumn(this->Columns.Count(), Header);
}
void ListView::NativeInsertColumn(const int32_t Index, ColumnHeader& Header)
{
LVCOLUMNA LvColumn{};
LvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
LvColumn.fmt = (int32_t)Header.TextAlign();
LvColumn.cx = Header.Width();
LvColumn.pszText = (char*)Header.Text();
SendMessageA(this->_Handle, LVM_INSERTCOLUMNA, (WPARAM)Index, (LPARAM)&LvColumn);
}
void ListView::NativeClearColumns()
{
for (int32_t ColIdx = this->Columns.Count() - 1; ColIdx >= 0; ColIdx--)
SendMessageA(this->_Handle, LVM_DELETECOLUMN, (WPARAM)ColIdx, NULL);
}
void ListView::NativeInsertItem(int32_t Index, const ListViewItem& Item)
{
SendMessageA(this->_Handle, LVM_SETITEMCOUNT, (WPARAM)Items.Count(), NULL);
LVITEMA LvItem{};
LvItem.mask = LVIF_TEXT;
LvItem.iItem = Index;
LvItem.pszText = (char*)Item.Text();
SendMessageA(this->_Handle, LVM_INSERTITEMA, NULL, (LPARAM)&LvItem);
for (uint32_t i = 1; i <= Item.SubItemCount(); i++)
{
LVITEMA LvSub{};
LvSub.mask = LVIF_TEXT;
LvSub.iItem = Index;
LvSub.iSubItem = i;
LvSub.pszText = (char*)Item.SubItem(i);
SendMessageA(this->_Handle, LVM_SETITEMTEXTA, (WPARAM)Index, (LPARAM)&LvSub);
}
}
void ListView::NativeInsertItems(int32_t Index, int32_t Count, const List<ListViewItem>& Items)
{
SendMessageA(this->_Handle, LVM_SETITEMCOUNT, (WPARAM)Items.Count(), NULL);
for (int32_t i = 0; i < Count; i++)
{
auto& Item = Items[Index + i];
LVITEMA LvItem{};
LvItem.mask = LVIF_TEXT;
LvItem.iItem = Index + i;
LvItem.pszText = (char*)Item.Text();
SendMessageA(this->_Handle, LVM_INSERTITEMA, NULL, (LPARAM)&LvItem);
for (uint32_t j = 1; j <= Item.SubItemCount(); j++)
{
LVITEMA LvSub{};
LvSub.mask = LVIF_TEXT;
LvSub.iItem = Index + i;
LvSub.iSubItem = j;
LvSub.pszText = (char*)Item.SubItem(j);
SendMessageA(this->_Handle, LVM_SETITEMTEXTA, (WPARAM)(Index + i), (LPARAM)&LvSub);
}
}
}
void ListView::NativeRemoveItem(int32_t Index)
{
SendMessageA(this->_Handle, LVM_DELETEITEM, (WPARAM)Index, NULL);
}
void ListView::NativeClearItems()
{
SendMessageA(this->_Handle, LVM_DELETEALLITEMS, NULL, NULL);
}
void ListView::InvalidateColumnHeaders()
{
if (GetState(ControlStates::StateCreated) && _ViewStyle == View::Details)
{
auto HwndHdr = (HWND)SendMessageA(this->_Handle, LVM_GETHEADER, NULL, NULL);
if (HwndHdr != NULL)
InvalidateRect(HwndHdr, NULL, TRUE);
}
}
Drawing::Point ListView::GetItemPosition(int32_t Index)
{
POINT Pt{};
SendMessageA(this->_Handle, LVM_GETITEMPOSITION, (WPARAM)Index, (LPARAM)&Pt);
return Drawing::Point(Pt.x, Pt.y);
}
Drawing::Rectangle ListView::GetItemRectOrEmpty(int32_t Index)
{
if (Index < 0 || Index >= (int32_t)this->Items.Count())
return Drawing::Rectangle();
if (_ViewStyle == View::Details && this->Columns.Count() == 0)
return Drawing::Rectangle();
RECT ItemRect{};
if (SendMessageA(this->_Handle, LVM_GETITEMRECT, (WPARAM)Index, (LPARAM)&ItemRect) == 0)
return Drawing::Rectangle();
return Drawing::Rectangle(ItemRect.left, ItemRect.top, ItemRect.right - ItemRect.left, ItemRect.bottom - ItemRect.top);
}
Drawing::Rectangle ListView::GetSubItemRect(int32_t Index, int32_t SubItem)
{
if (_ViewStyle != View::Details)
return Drawing::Rectangle();
if (this->Columns.Count() == 0)
return Drawing::Rectangle();
RECT ItemRect{};
ItemRect.top = SubItem;
if (SendMessageA(this->_Handle, LVM_GETSUBITEMRECT, (WPARAM)Index, (LPARAM)&ItemRect) == 0)
return Drawing::Rectangle();
return Drawing::Rectangle(ItemRect.left, ItemRect.top, ItemRect.right - ItemRect.left, ItemRect.bottom - ItemRect.top);
}
int32_t ListView::GetItemState(int32_t Index)
{
return (int32_t)SendMessageA(this->_Handle, LVM_GETITEMSTATE, (WPARAM)Index, LVIS_FOCUSED | LVIS_SELECTED | LVIS_CUT | LVIS_DROPHILITED | LVIS_OVERLAYMASK | LVIS_STATEIMAGEMASK);
}
std::pair<String, ListViewItemStyle> ListView::GetSubItem(int32_t Index, int32_t SubItem)
{
if (!VirtualMode())
{
auto& Item = Items[Index];
auto& Text = Item.SubItem(SubItem);
auto& Style = Item.SubItemStyle(SubItem);
return std::pair<String, ListViewItemStyle>(Text, Style);
}
auto EventArgs = std::make_unique<RetrieveVirtualItemEventArgs>(Index, SubItem);
OnRetrieveVirtualItem(EventArgs);
return std::pair<String, ListViewItemStyle>(EventArgs->Text, EventArgs->Style);
}
void ListView::SetColumnInfo(int32_t Mask, ColumnHeader& Header)
{
if (!GetState(ControlStates::StateCreated))
return;
LVCOLUMNA LvColumn{};
LvColumn.mask = Mask;
if ((Mask & LVCF_FMT) != 0)
{
LvColumn.fmt |= (int32_t)Header.TextAlign();
}
if ((Mask & LVCF_TEXT) != 0)
{
LvColumn.pszText = (char*)Header.Text();
}
SendMessageA(this->_Handle, LVM_SETCOLUMNA, (WPARAM)Header.Index(), (LPARAM)&LvColumn);
InvalidateColumnHeaders();
}
void ListView::SetColumnWidth(int32_t Index, int32_t Width)
{
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, LVM_SETCOLUMNWIDTH, (WPARAM)Index, MAKELPARAM(Width, 0));
}
void ListView::SetDisplayIndices(const std::unique_ptr<int32_t[]>& Indices, int32_t Count)
{
auto OrderedColumns = std::make_unique<int32_t[]>(Count);
for (int32_t i = 0; i < Count; i++)
{
this->Columns[i].SetDisplayIndexInternal(Indices[i]);
OrderedColumns[Indices[i]] = i;
}
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, LVM_SETCOLUMNORDERARRAY, (WPARAM)Count, (LPARAM)OrderedColumns.get());
}
void ListView::UpdateExtendedStyles()
{
if (GetState(ControlStates::StateCreated))
{
int32_t ExStyle = 0;
int32_t ExMask = LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE |
LVS_EX_TRACKSELECT | LVS_EX_UNDERLINEHOT | LVS_EX_ONECLICKACTIVATE |
LVS_EX_HEADERDRAGDROP | LVS_EX_CHECKBOXES |
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES |
LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER;
switch (_ItemActivation)
{
case ItemActivation::OneClick:
ExStyle |= LVS_EX_ONECLICKACTIVATE;
break;
case ItemActivation::TwoClick:
ExStyle |= LVS_EX_TWOCLICKACTIVATE;
break;
}
if (AllowColumnReorder())
ExStyle |= LVS_EX_HEADERDRAGDROP;
if (DoubleBuffered())
ExStyle |= LVS_EX_DOUBLEBUFFER;
if (FullRowSelect())
ExStyle |= LVS_EX_FULLROWSELECT;
if (GridLines())
ExStyle |= LVS_EX_GRIDLINES;
if (HoverSelection())
ExStyle |= LVS_EX_TRACKSELECT;
if (HotTracking())
ExStyle |= LVS_EX_UNDERLINEHOT;
SendMessageA(this->_Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, (WPARAM)ExMask, (LPARAM)ExStyle);
Invalidate();
}
}
int32_t ListView::GetIndexOfClickedItem(LVHITTESTINFO & LvHI)
{
auto Pos = GetMousePosition();
Pos = PointToClient(Pos);
LvHI.pt.x = Pos.X;
LvHI.pt.y = Pos.Y;
return (int32_t)SendMessageA(this->_Handle, LVM_HITTEST, NULL, (LPARAM)&LvHI);
}
void ListView::WmMouseDown(Message& Msg, MouseButtons Button, uint32_t Clicks)
{
SetFlag(ListViewFlags::FlagMouseUpFired, false);
SetFlag(ListViewFlags::FlagExpectingMouseUp, true);
this->Focus();
int32_t X = LOWORD((int)Msg.LParam);
int32_t Y = HIWORD((int)Msg.LParam);
auto EventArgs = std::make_unique<MouseEventArgs>(Button, Clicks, X, Y, 0);
OnMouseDown(EventArgs);
DefWndProc(Msg);
}
void ListView::WmReflectNotify(Message& Msg)
{
auto NMHdr = (NMHDR*)Msg.LParam;
switch (NMHdr->code)
{
case NM_CUSTOMDRAW:
NmCustomDraw(Msg);
break;
case LVN_BEGINLABELEDITA:
case LVN_BEGINLABELEDITW:
{
auto DispInfo = (NMLVDISPINFOA*)Msg.LParam;
auto EventArgs = std::make_unique<LabelEditEventArgs>(DispInfo->item.iItem);
OnBeforeLabelEdit(EventArgs);
Msg.Result = (uintptr_t)(EventArgs->CancelEdit ? 1 : 0);
SetFlag(ListViewFlags::FlagInLabelEdit, !EventArgs->CancelEdit);
}
break;
case LVN_COLUMNCLICK:
SetFlag(ListViewFlags::FlagColumnClicked, true);
_ColumnIndex = ((NMLISTVIEW*)Msg.LParam)->iSubItem;
break;
case LVN_ENDLABELEDITA:
case LVN_ENDLABELEDITW:
{
auto DispInfo = (NMLVDISPINFOA*)Msg.LParam;
auto EventArgs = std::make_unique<LabelEditEventArgs>(DispInfo->item.iItem, DispInfo->item.pszText);
OnAfterLabelEdit(EventArgs);
Msg.Result = (uintptr_t)(EventArgs->CancelEdit ? 1 : 0);
if (!EventArgs->CancelEdit && DispInfo->item.pszText != nullptr)
{
LVITEMA LvItem{};
LvItem.mask = LVIF_TEXT;
LvItem.iItem = DispInfo->item.iItem;
LvItem.pszText = DispInfo->item.pszText;
SendMessageA(this->_Handle, LVM_SETITEMTEXTA, (WPARAM)DispInfo->item.iItem, (LPARAM)&LvItem);
}
}
break;
case LVN_ITEMACTIVATE:
OnItemActivate();
break;
case NM_CLICK:
case NM_RCLICK:
{
LVHITTESTINFO LvHI{};
auto DisplayIndex = GetIndexOfClickedItem(LvHI);
auto Button = NMHdr->code == NM_CLICK ? MouseButtons::Left : MouseButtons::Right;
auto Pos = GetMousePosition();
Pos = PointToClient(Pos);
if (DisplayIndex != -1)
{
OnClick();
OnMouseClick(std::make_unique<MouseEventArgs>(Button, 1, Pos.X, Pos.Y, 0));
}
if (!GetFlag(ListViewFlags::FlagMouseUpFired))
{
OnMouseUp(std::make_unique<MouseEventArgs>(Button, 1, Pos.X, Pos.Y, 0));
SetFlag(ListViewFlags::FlagMouseUpFired, true);
}
}
break;
case NM_DBLCLK:
case NM_RDBLCLK:
{
LVHITTESTINFO LvHI{};
auto DisplayIndex = GetIndexOfClickedItem(LvHI);
if (DisplayIndex != -1)
SetFlag(ListViewFlags::FlagDoubleclickFired, true);
SetFlag(ListViewFlags::FlagMouseUpFired, false);
SetCapture(true);
}
break;
case LVN_ODCACHEHINT:
{
auto CacheHint = (NMLVCACHEHINT*)Msg.LParam;
OnCacheVirtualItems(std::make_unique<CacheVirtualItemsEventArgs>(CacheHint->iFrom, CacheHint->iTo));
}
break;
case LVN_GETDISPINFOA:
case LVN_GETDISPINFOW:
if (VirtualMode())
{
auto DispInfo = (NMLVDISPINFOA*)Msg.LParam;
if ((DispInfo->item.mask & LVIF_TEXT) != 0)
{
auto EventArgs = std::make_unique<RetrieveVirtualItemEventArgs>(DispInfo->item.iItem, DispInfo->item.iSubItem);
OnRetrieveVirtualItem(EventArgs);
auto TextSize = min(EventArgs->Text.Length(), (uint32_t)(DispInfo->item.cchTextMax - 1));
std::memcpy(DispInfo->item.pszText, (char*)EventArgs->Text, (size_t)TextSize);
DispInfo->item.pszText[TextSize] = '\0';
}
}
break;
case LVN_ODSTATECHANGED:
{
auto OdStateChange = (NMLVODSTATECHANGE*)Msg.LParam;
if ((OdStateChange->uNewState & LVIS_SELECTED) != (OdStateChange->uOldState & LVIS_SELECTED))
{
OnVirtualItemsSelectionRangeChanged(std::make_unique<ListViewVirtualItemsSelectionRangeChangedEventArgs>(OdStateChange->iFrom, OdStateChange->iTo, (OdStateChange->uNewState & LVIS_SELECTED) != 0));
}
}
break;
case LVN_ITEMCHANGED:
{
auto ChangeInfo = (NMLISTVIEW*)Msg.LParam;
if ((ChangeInfo->uChanged & LVIF_STATE) != 0)
{
int32_t oldState = ChangeInfo->uOldState & LVIS_SELECTED;
int32_t newState = ChangeInfo->uNewState & LVIS_SELECTED;
if (newState != oldState)
{
// Trigger virtual item selection change here as well...
if (this->VirtualListSize() > 0)
{
OnVirtualItemsSelectionRangeChanged(std::make_unique<ListViewVirtualItemsSelectionRangeChangedEventArgs>(0, this->VirtualListSize() - 1, newState != 0));
}
// TODO: Trigger OnSelectedIndexChanged();
// https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ListView.cs,5946
}
}
}
break;
}
}
bool ListView::WmNotify(Message& Msg)
{
auto NmHdr = (NMHDR*)Msg.LParam;
if (NmHdr->code == NM_CUSTOMDRAW && OwnerDraw())
{
auto NmCd = (NMCUSTOMDRAW*)Msg.LParam;
switch (NmCd->dwDrawStage)
{
case CDDS_PREPAINT:
Msg.Result = (uintptr_t)CDRF_NOTIFYITEMDRAW;
return true;
case CDDS_ITEMPREPAINT:
{
auto EventArgs = std::make_unique<DrawListViewColumnHeaderEventArgs>((HDC)NmCd->hdc, &Columns[(uint32_t)NmCd->dwItemSpec], (int32_t)NmCd->dwItemSpec, Drawing::Rectangle(NmCd->rc.left, NmCd->rc.top, NmCd->rc.right - NmCd->rc.left, NmCd->rc.bottom - NmCd->rc.top), (ListViewItemStates)NmCd->uItemState);
OnDrawColumnHeader(EventArgs);
if (EventArgs->DrawDefault)
{
Msg.Result = (uintptr_t)CDRF_DODEFAULT;
return false;
}
else
{
Msg.Result = (uintptr_t)CDRF_SKIPDEFAULT;
return true;
}
}
break;
default:
return false;
}
}
if (NmHdr->code == NM_RELEASEDCAPTURE && GetFlag(ListViewFlags::FlagColumnClicked))
{
SetFlag(ListViewFlags::FlagColumnClicked, false);
OnColumnClick(std::make_unique<ColumnClickEventArgs>(_ColumnIndex));
}
// Still need default handling
return false;
}
void ListView::NmCustomDraw(Message& Msg)
{
bool DontMess = false;
bool ItemDrawDefault = false;
auto NmCd = (NMLVCUSTOMDRAW*)Msg.LParam;
switch (NmCd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
if (OwnerDraw())
{
Msg.Result = (uintptr_t)CDRF_NOTIFYITEMDRAW;
return;
}
Msg.Result = (uintptr_t)CDRF_NOTIFYSUBITEMDRAW;
return;
case CDDS_ITEMPREPAINT:
{
int32_t ItemIndex = (int32_t)NmCd->nmcd.dwItemSpec;
auto ItemBounds = GetItemRectOrEmpty(ItemIndex);
if (!ClientRectangle().IntersectsWith(ItemBounds))
return;
if (OwnerDraw())
{
auto Item = this->GetSubItem(ItemIndex, 0);
auto EventArgs = std::make_unique<DrawListViewItemEventArgs>((HDC)NmCd->nmcd.hdc, Item.first, Item.second, ItemBounds, ItemIndex, (ListViewItemStates)NmCd->nmcd.uItemState);
OnDrawItem(EventArgs);
ItemDrawDefault = EventArgs->DrawDefault;
if (_ViewStyle == View::Details)
Msg.Result = (uintptr_t)CDRF_NOTIFYSUBITEMDRAW;
else if (!EventArgs->DrawDefault)
Msg.Result = (uintptr_t)CDRF_SKIPDEFAULT;
if (!EventArgs->DrawDefault)
return;
}
if (_ViewStyle == View::Details || _ViewStyle == View::Tile)
{
Msg.Result = (uintptr_t)CDRF_NOTIFYSUBITEMDRAW;
DontMess = true;
}
}
case (CDDS_SUBITEM | CDDS_ITEMPREPAINT):
{
int32_t ItemIndex = (int32_t)NmCd->nmcd.dwItemSpec;
auto ItemBounds = GetItemRectOrEmpty(ItemIndex);
if (!ClientRectangle().IntersectsWith(ItemBounds))
return;
if (OwnerDraw() && !ItemDrawDefault)
{
bool SkipCustomDrawCode = true;
if (NmCd->iSubItem < (int32_t)Columns.Count())
{
auto SubItemBounds = GetSubItemRect(ItemIndex, NmCd->iSubItem);
if (NmCd->iSubItem == 0 && Columns.Count() > 1)
{
SubItemBounds.Width = Columns[0].Width();
}
if (ClientRectangle().IntersectsWith(SubItemBounds))
{
auto Item = this->GetSubItem(ItemIndex, NmCd->iSubItem);
auto EventArgs = std::make_unique<DrawListViewSubItemEventArgs>((HDC)NmCd->nmcd.hdc, Item.first, Item.second, SubItemBounds, ItemIndex, NmCd->iSubItem, (ListViewItemStates)NmCd->nmcd.uItemState);
OnDrawSubItem(EventArgs);
SkipCustomDrawCode = !EventArgs->DrawDefault;
}
}
if (SkipCustomDrawCode)
{
Msg.Result = (uintptr_t)CDRF_SKIPDEFAULT;
return;
}
}
auto State = NmCd->nmcd.uItemState;
if (!HideSelection())
{
auto RealState = GetItemState((int32_t)NmCd->nmcd.dwItemSpec);
if ((RealState & LVIS_SELECTED) == 0)
State &= ~CDIS_SELECTED;
}
auto SubItem = ((NmCd->nmcd.dwDrawStage & CDDS_SUBITEM) != 0) ? NmCd->iSubItem : 0;
auto Item = GetSubItem(ItemIndex, SubItem);
bool ChangeColor = (GetState(ControlStates::StateEnabled) ? true : false);
if (Activation() == ItemActivation::OneClick || Activation() == ItemActivation::TwoClick)
{
if ((State & (CDIS_SELECTED | CDIS_GRAYED | CDIS_HOT | CDIS_DISABLED)) != 0)
ChangeColor = false;
}
if (ChangeColor)
{
NmCd->clrText = Drawing::ColorToWin32(Item.second.ForeColor);
NmCd->clrTextBk = Drawing::ColorToWin32(Item.second.BackColor);
}
if (!DontMess)
Msg.Result = (uintptr_t)CDRF_NEWFONT;
}
return;
default:
Msg.Result = (uintptr_t)CDRF_DODEFAULT;
return;
}
}
ListView::ColumnHeaderCollection::ColumnHeaderCollection(ListView* Owner)
: _Owner(Owner), _Columns()
{
}
void ListView::ColumnHeaderCollection::Add(const ColumnHeader& Value)
{
auto& Result = _Columns.Emplace(Value);
Result.SetListView(_Owner);
if (Result.DisplayIndex() <= -1)
Result.SetDisplayIndexInternal(_Columns.Count() - 1);
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeAddColumn(Result);
}
void ListView::ColumnHeaderCollection::Insert(int32_t Index, const ColumnHeader& Value)
{
_Columns.Insert(Index, Value);
auto& Result = _Columns[Index];
Result.SetListView(_Owner);
if (Result.DisplayIndex() <= -1)
Result.SetDisplayIndexInternal(Index);
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeInsertColumn(Index, Result);
}
void ListView::ColumnHeaderCollection::Clear()
{
_Owner->NativeClearColumns();
_Columns.Clear();
}
int32_t ListView::ColumnHeaderCollection::IndexOf(const ColumnHeader& Value)
{
return this->_Columns.IndexOf(Value);
}
uint32_t ListView::ColumnHeaderCollection::Count()
{
return this->_Columns.Count();
}
ColumnHeader* ListView::ColumnHeaderCollection::begin() const
{
return this->_Columns.begin();
}
ColumnHeader* ListView::ColumnHeaderCollection::end() const
{
return this->_Columns.end();
}
ColumnHeader& ListView::ColumnHeaderCollection::operator[](uint32_t Index)
{
return this->_Columns[Index];
}
ListView::ListViewItemCollection::ListViewItemCollection(ListView* Owner)
: _Owner(Owner)
{
}
void ListView::ListViewItemCollection::Add(const ListViewItem& Value)
{
_Items.EmplaceBack(Value);
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeInsertItem(_Items.Count(), Value);
}
void ListView::ListViewItemCollection::Insert(int32_t Index, const ListViewItem& Value)
{
_Items.Insert(Index, Value);
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeInsertItem(Index, Value);
}
void ListView::ListViewItemCollection::RemoveAt(int32_t Index)
{
_Items.RemoveAt(Index);
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeRemoveItem(Index);
}
void ListView::ListViewItemCollection::Clear()
{
_Items.Clear();
if (_Owner->GetState(ControlStates::StateCreated))
_Owner->NativeClearItems();
}
uint32_t ListView::ListViewItemCollection::Count()
{
return (_Owner->VirtualMode() ? (uint32_t)_Owner->_VirtualListSize : this->_Items.Count());
}
ListViewItem& ListView::ListViewItemCollection::operator[](uint32_t Index)
{
return _Items[Index];
}
ListViewItem* ListView::ListViewItemCollection::begin() const
{
return _Items.begin();
}
ListViewItem* ListView::ListViewItemCollection::end() const
{
return _Items.end();
}
const List<ListViewItem>& ListView::ListViewItemCollection::ToList()
{
return _Items;
}
}