using System; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.Win32; namespace Common { public enum ColumnDataType { String, Integer, DateTime, Decimal, Custom } public delegate int ListViewItemCompareEventHandler(ListViewItem x, ListViewItem y); public class ListViewColumn { public string Name; public int Width; public HorizontalAlignment TextAlign; public bool Visible; public int FieldIndex; public ColumnDataType DataType; public ListViewItemCompareEventHandler OnCompare; } // Supports sorting by column in ascending or descending order public class ListViewItemComparer : IComparer, IComparer { public int Column; public SortOrder Order; public ListView ListView; public ListViewManager ListViewManager; public void ColumnClick(int newColumn) { // Determine if clicked column is already the column that is being sorted. if (newColumn == Column) { // Reverse the current sort direction for this column. Order = Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending; } else { // Set the column number that is to be sorted; default to ascending. Column = newColumn; Order = SortOrder.Ascending; } } public void SetColumn(int newColumn) { Column = newColumn; } public void SetOrder(SortOrder newOrder) { Order = newOrder; } public ListViewItemComparer() { Column = -1; Order = SortOrder.None; } public int Compare(ListViewItem x, ListViewItem y) { if (Column >= ListView.Columns.Count) { Column = -1; } int result = 0; if (x != null && y != null) { if (Column < x.SubItems.Count && Column < y.SubItems.Count && Column >= 0) { ListViewColumn listViewColumn = null; ColumnDataType dataType = ColumnDataType.String; if (ListView.Columns[Column].Tag != null) { listViewColumn = (ListViewColumn)(ListView.Columns[Column].Tag); dataType = listViewColumn.DataType; } if (dataType == ColumnDataType.Integer) { if (int.TryParse(x.SubItems[Column].Text, out int xi) && int.TryParse(y.SubItems[Column].Text, out int yi)) { if (xi < yi) result = -1; else if (xi > yi) result = 1; else result = 0; } } else if (dataType == ColumnDataType.Decimal) { if (decimal.TryParse(x.SubItems[Column].Text, out decimal xi) && decimal.TryParse(y.SubItems[Column].Text, out decimal yi)) { if (xi < yi) result = -1; else if (xi > yi) result = 1; else result = 0; } } else if (dataType == ColumnDataType.DateTime) { if (DateTime.TryParse(x.SubItems[Column].Text, out DateTime xi) && DateTime.TryParse(y.SubItems[Column].Text, out DateTime yi)) { result = DateTime.Compare(xi, yi); } } else if (dataType == ColumnDataType.Custom) { if (listViewColumn?.OnCompare != null) { result = listViewColumn.OnCompare(x, y); } } else result = string.CompareOrdinal(x.SubItems[Column].Text, y.SubItems[Column].Text); } else { if (ListView.VirtualMode) { bool xSelected = ListView.SelectedIndices.IndexOf(x.Index) != -1; bool ySelected = ListView.SelectedIndices.IndexOf(y.Index) != -1; if (!xSelected && ySelected) result = 1; else if (xSelected && !ySelected) result = -1; } else { if (!x.Selected && y.Selected) result = 1; else if (x.Selected && !y.Selected) result = -1; } } } switch (Order) { case SortOrder.Ascending: return result; case SortOrder.Descending: return -result; case SortOrder.None: default: return 0; } } public int Compare(object x, object y) { return Compare((ListViewItem)x, (ListViewItem)y); } } public class ListViewManager { public List ListColumns; public List VisibleListColumns; public ListView ListView; public List ListViewCache; public Dictionary ListViewDict; private readonly List listViewCacheMatched; public delegate void LoadItemHandler(ListViewItem listViewItem, int itemIndex); public event LoadItemHandler LoadItem; public delegate void LoadItemObjectHandler(ListViewItem listViewItem, object item); public event LoadItemObjectHandler LoadItemObject; public delegate bool FilterItemHandler(object item, Dictionary filter); public event FilterItemHandler FilterItem; public Dictionary Filter; public ListViewManager(ListView listView) { ListView = listView; var listViewComparer = new ListViewItemComparer { ListView = listView, ListViewManager = this }; listView.ListViewItemSorter = listViewComparer; ListColumns = new List(); foreach (ColumnHeader columnHeader in ListView.Columns) { ListViewColumn listViewColumn = new ListViewColumn() { Name = columnHeader.Text, TextAlign = columnHeader.TextAlign, Width = columnHeader.Width, Visible = true, FieldIndex = Convert.ToInt32(columnHeader.Tag) }; columnHeader.Tag = listViewColumn; ListColumns.Add(listViewColumn); } VisibleListColumns = new List(); if (listView.VirtualMode) { ListViewCache = new List(); ListView.RetrieveVirtualItem += delegate (object sender, RetrieveVirtualItemEventArgs e) { e.Item = ListViewCache[e.ItemIndex]; }; } ListViewDict = new Dictionary(); listViewCacheMatched = new List(); ListView.ColumnClick += delegate (object sender, ColumnClickEventArgs e) { ((ListViewItemComparer)((ListView)sender).ListViewItemSorter).ColumnClick(e.Column); Sort(); }; Filter = new Dictionary(); ConfigureListView(); } public void Sort() { ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter; if (((ListViewItemComparer)ListView.ListViewItemSorter).Column != -1 && ((ListViewItemComparer)ListView.ListViewItemSorter).Order != SortOrder.None) { if (ListView.VirtualMode) { // Store selected items List selectedItems = null; if (ListView.SelectedIndices.Count > 0) { int[] selected = new int[ListView.SelectedIndices.Count]; ListView.SelectedIndices.CopyTo(selected, 0); selectedItems = new List(selected.Length); foreach (int index in ListView.SelectedIndices) { selectedItems.Add(ListViewCache[index].Tag); } } ListViewCache.Sort(listViewComparer); ListView.Refresh(); // Restore selected items if (selectedItems != null) { ListView.BeginUpdate(); try { ListView.SelectedIndices.Clear(); foreach (var selectedObject in selectedItems) { ListView.SelectedIndices.Add(ListViewCache.IndexOf(ListViewDict[selectedObject])); } } finally { ListView.EndUpdate(); } } } else { ListView.Sort(); } } ListView.SetSortIcon(listViewComparer.Column, listViewComparer.Order); } public void ReloadCache(int itemCount) { ListView.BeginUpdate(); try { // Sync number of cached items if (listViewCacheMatched.Count > itemCount) listViewCacheMatched.RemoveRange(itemCount, listViewCacheMatched.Count - itemCount); if (listViewCacheMatched.Capacity < itemCount) listViewCacheMatched.Capacity = itemCount; while (listViewCacheMatched.Count < itemCount) { ListViewItem listViewItem = new ListViewItem(); listViewCacheMatched.Add(listViewItem); } // Update items content if (LoadItem != null) { for (int i = 0; i < itemCount; i++) { LoadItem?.Invoke(listViewCacheMatched[i], i); } } if (Filter.Count > 0 && FilterItem != null) { ListViewCache = listViewCacheMatched.FindAll(x => (bool)FilterItem?.Invoke((object)x.Tag, Filter)).ToList(); } else { ListViewCache = listViewCacheMatched.ToList(); } ListView.VirtualListSize = ListViewCache.Count; ListViewDict = ListViewCache.ToDictionary(key => key.Tag, value => value); Sort(); } finally { ListView.EndUpdate(); } } public void ReloadCacheItem(ListViewItem listViewItem, int itemIndex) { LoadItem?.Invoke(listViewItem, itemIndex); } public void ReloadCacheItemObject(ListViewItem listViewItem, object item) { LoadItemObject?.Invoke(listViewItem, item); } public void UpdateFromListView() { // Store current width for (int i = 0; i < VisibleListColumns.Count; i++) { VisibleListColumns[i].Width = ListView.Columns[i].Width; } } public void UpdateToListView() { ListView.BeginUpdate(); try { VisibleListColumns = ListColumns.FindAll(x => x.Visible); while (ListView.Columns.Count < VisibleListColumns.Count) ListView.Columns.Add(""); while (ListView.Columns.Count > VisibleListColumns.Count) ListView.Columns.RemoveAt(ListView.Columns.Count - 1); for (int i = 0; i < VisibleListColumns.Count; i++) { ColumnHeader item = ListView.Columns[i]; item.Text = VisibleListColumns[i].Name; item.Width = VisibleListColumns[i].Width; item.TextAlign = VisibleListColumns[i].TextAlign; item.Tag = VisibleListColumns[i]; Theme.ApplyTheme(item); } } finally { ListView.EndUpdate(); } } public void UpdateColumns() { UpdateFromListView(); UpdateToListView(); } public void ConfigureListView(int column = -1, SortOrder order = SortOrder.None) { ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter; listViewComparer.SetOrder(order); listViewComparer.SetColumn(column); if ((column != -1) && (order != SortOrder.None)) { Sort(); } } public void SetColumnDataType(int fieldIndex, ColumnDataType dataType, ListViewItemCompareEventHandler compareHandler = null) { ListViewColumn column = ListColumns.FirstOrDefault(x => x.FieldIndex == fieldIndex); if (column == null) return; column.DataType = dataType; column.OnCompare = compareHandler; } public void SetColumnVisible(int fieldIndex, bool visible) { ListViewColumn column = ListColumns.FirstOrDefault(x => x.FieldIndex == fieldIndex); if (column == null) return; column.Visible = visible; } public void LoadFromRegistry(string regSubKey) { RegistryKey regKey = Application.UserAppDataRegistry.OpenSubKey(regSubKey) ?? Application.UserAppDataRegistry.CreateSubKey(regSubKey); ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter; listViewComparer.Order = (SortOrder)regKey.GetValueInt("SortOrder", (int)listViewComparer.Order); listViewComparer.Column = regKey.GetValueInt("SortColumn", listViewComparer.Column); foreach (ListViewColumn listViewColumn in ListColumns) { listViewColumn.Width = regKey.GetValueInt("Width" + listViewColumn.Name, listViewColumn.Width); listViewColumn.Visible = regKey.GetValueBool("Visible" + listViewColumn.Name, listViewColumn.Visible); } UpdateToListView(); } public void SaveToRegistry(string regSubKey) { RegistryKey regKey = Application.UserAppDataRegistry.OpenSubKey(regSubKey, true) ?? Application.UserAppDataRegistry.CreateSubKey(regSubKey); ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter; regKey.SetValueInt("SortOrder", (int)listViewComparer.Order); regKey.SetValueInt("SortColumn", listViewComparer.Column); foreach (ListViewColumn listViewColumn in ListColumns) { regKey.SetValueInt("Width" + listViewColumn.Name, listViewColumn.Width); regKey.SetValueBool("Visible" + listViewColumn.Name, listViewColumn.Visible); } } } internal static class ListViewExtensions { [StructLayout(LayoutKind.Sequential)] public struct LVCOLUMN { public Int32 mask; public Int32 cx; [MarshalAs(UnmanagedType.LPTStr)] public string pszText; public IntPtr hbm; public Int32 cchTextMax; public Int32 fmt; public Int32 iSubItem; public Int32 iImage; public Int32 iOrder; } const Int32 HDI_WIDTH = 0x0001; const Int32 HDI_HEIGHT = HDI_WIDTH; const Int32 HDI_TEXT = 0x0002; const Int32 HDI_FORMAT = 0x0004; const Int32 HDI_LPARAM = 0x0008; const Int32 HDI_BITMAP = 0x0010; const Int32 HDI_IMAGE = 0x0020; const Int32 HDI_DI_SETITEM = 0x0040; const Int32 HDI_ORDER = 0x0080; const Int32 HDI_FILTER = 0x0100; const Int32 HDF_LEFT = 0x0000; const Int32 HDF_RIGHT = 0x0001; const Int32 HDF_CENTER = 0x0002; const Int32 HDF_JUSTIFYMASK = 0x0003; const Int32 HDF_RTLREADING = 0x0004; const Int32 HDF_OWNERDRAW = 0x8000; const Int32 HDF_STRING = 0x4000; const Int32 HDF_BITMAP = 0x2000; const Int32 HDF_BITMAP_ON_RIGHT = 0x1000; const Int32 HDF_IMAGE = 0x0800; const Int32 HDF_SORTUP = 0x0400; const Int32 HDF_SORTDOWN = 0x0200; const Int32 LVM_FIRST = 0x1000; // List messages const Int32 LVM_GETHEADER = LVM_FIRST + 31; const Int32 HDM_FIRST = 0x1200; // Header messages const Int32 HDM_SETIMAGELIST = HDM_FIRST + 8; const Int32 HDM_GETIMAGELIST = HDM_FIRST + 9; const Int32 HDM_GETITEM = HDM_FIRST + 11; const Int32 HDM_SETITEM = HDM_FIRST + 12; [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")] private static extern IntPtr SendMessageLVCOLUMN(IntPtr hWnd, Int32 Msg, IntPtr wParam, ref LVCOLUMN lPLVCOLUMN); // This method used to set arrow icon public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order) { IntPtr columnHeader = SendMessage(listView.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero); for (int columnNumber = 0; columnNumber <= listView.Columns.Count - 1; columnNumber++) { IntPtr columnPtr = new IntPtr(columnNumber); LVCOLUMN lvColumn = new LVCOLUMN {mask = HDI_FORMAT}; SendMessageLVCOLUMN(columnHeader, HDM_GETITEM, columnPtr, ref lvColumn); if (order != SortOrder.None && columnNumber == columnIndex) { switch (order) { case SortOrder.Ascending: lvColumn.fmt &= ~HDF_SORTDOWN; lvColumn.fmt |= HDF_SORTUP; break; case SortOrder.Descending: lvColumn.fmt &= ~HDF_SORTUP; lvColumn.fmt |= HDF_SORTDOWN; break; } lvColumn.fmt |= (HDF_LEFT | HDF_BITMAP_ON_RIGHT); } else { lvColumn.fmt &= ~HDF_SORTDOWN & ~HDF_SORTUP & ~HDF_BITMAP_ON_RIGHT; } SendMessageLVCOLUMN(columnHeader, HDM_SETITEM, columnPtr, ref lvColumn); } } } }