source: Common/ListViewComparer.cs

Last change on this file was 15, checked in by chronos, 13 months ago
  • Modified: Updated files.
File size: 20.1 KB
Line 
1using System;
2using System.Windows.Forms;
3using System.Runtime.InteropServices;
4using System.Collections;
5using System.Collections.Generic;
6using System.Linq;
7using Microsoft.Win32;
8
9namespace Common
10{
11 public enum ColumnDataType { String, Integer, DateTime, Decimal, Custom }
12
13 public delegate int ListViewItemCompareEventHandler(ListViewItem x, ListViewItem y);
14
15 public class ListViewColumn
16 {
17 public string Name;
18 public int Width;
19 public HorizontalAlignment TextAlign;
20 public bool Visible;
21 public int FieldIndex;
22 public ColumnDataType DataType;
23 public ListViewItemCompareEventHandler OnCompare;
24 }
25
26 // Supports sorting by column in ascending or descending order
27 public class ListViewItemComparer : IComparer, IComparer<ListViewItem>
28 {
29 public int Column;
30 public SortOrder Order;
31 public ListView ListView;
32 public ListViewManager ListViewManager;
33
34 public void ColumnClick(int newColumn)
35 {
36 // Determine if clicked column is already the column that is being sorted.
37 if (newColumn == Column)
38 {
39 // Reverse the current sort direction for this column.
40 Order = Order == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
41 }
42 else
43 {
44 // Set the column number that is to be sorted; default to ascending.
45 Column = newColumn;
46 Order = SortOrder.Ascending;
47 }
48 }
49
50 public void SetColumn(int newColumn)
51 {
52 Column = newColumn;
53 }
54
55 public void SetOrder(SortOrder newOrder)
56 {
57 Order = newOrder;
58 }
59
60 public ListViewItemComparer()
61 {
62 Column = -1;
63 Order = SortOrder.None;
64 }
65
66 public int Compare(ListViewItem x, ListViewItem y)
67 {
68 if (Column >= ListView.Columns.Count)
69 {
70 Column = -1;
71 }
72 int result = 0;
73 if (x != null && y != null)
74 {
75 if (Column < x.SubItems.Count && Column < y.SubItems.Count && Column >= 0)
76 {
77 ListViewColumn listViewColumn = null;
78 ColumnDataType dataType = ColumnDataType.String;
79 if (ListView.Columns[Column].Tag != null)
80 {
81 listViewColumn = (ListViewColumn)(ListView.Columns[Column].Tag);
82 dataType = listViewColumn.DataType;
83 }
84
85 if (dataType == ColumnDataType.Integer)
86 {
87 if (int.TryParse(x.SubItems[Column].Text, out int xi) &&
88 int.TryParse(y.SubItems[Column].Text, out int yi))
89 {
90 if (xi < yi) result = -1;
91 else if (xi > yi) result = 1;
92 else result = 0;
93 }
94 }
95 else if (dataType == ColumnDataType.Decimal)
96 {
97 if (decimal.TryParse(x.SubItems[Column].Text, out decimal xi) &&
98 decimal.TryParse(y.SubItems[Column].Text, out decimal yi))
99 {
100 if (xi < yi) result = -1;
101 else if (xi > yi) result = 1;
102 else result = 0;
103 }
104 }
105 else if (dataType == ColumnDataType.DateTime)
106 {
107 if (DateTime.TryParse(x.SubItems[Column].Text, out DateTime xi) &&
108 DateTime.TryParse(y.SubItems[Column].Text, out DateTime yi))
109 {
110 result = DateTime.Compare(xi, yi);
111 }
112 }
113 else if (dataType == ColumnDataType.Custom)
114 {
115 if (listViewColumn?.OnCompare != null)
116 {
117 result = listViewColumn.OnCompare(x, y);
118 }
119 }
120 else
121 result = string.CompareOrdinal(x.SubItems[Column].Text,
122 y.SubItems[Column].Text);
123 }
124 else
125 {
126 if (ListView.VirtualMode)
127 {
128 bool xSelected = ListView.SelectedIndices.IndexOf(x.Index) != -1;
129 bool ySelected = ListView.SelectedIndices.IndexOf(y.Index) != -1;
130 if (!xSelected && ySelected) result = 1;
131 else if (xSelected && !ySelected) result = -1;
132 }
133 else
134 {
135 if (!x.Selected && y.Selected) result = 1;
136 else if (x.Selected && !y.Selected) result = -1;
137 }
138 }
139 }
140
141 switch (Order)
142 {
143 case SortOrder.Ascending:
144 return result;
145 case SortOrder.Descending:
146 return -result;
147 case SortOrder.None:
148 default:
149 return 0;
150 }
151 }
152
153 public int Compare(object x, object y)
154 {
155 return Compare((ListViewItem)x, (ListViewItem)y);
156 }
157 }
158
159 public class ListViewManager
160 {
161 public List<ListViewColumn> ListColumns;
162 public List<ListViewColumn> VisibleListColumns;
163 public ListView ListView;
164 public List<ListViewItem> ListViewCache;
165 public Dictionary<object, ListViewItem> ListViewDict;
166 private readonly List<ListViewItem> listViewCacheMatched;
167 public delegate void LoadItemHandler(ListViewItem listViewItem, int itemIndex);
168 public event LoadItemHandler LoadItem;
169 public delegate void LoadItemObjectHandler(ListViewItem listViewItem, object item);
170 public event LoadItemObjectHandler LoadItemObject;
171 public delegate bool FilterItemHandler(object item, Dictionary<int, string> filter);
172 public event FilterItemHandler FilterItem;
173 public Dictionary<int, string> Filter;
174
175 public ListViewManager(ListView listView)
176 {
177 ListView = listView;
178 var listViewComparer = new ListViewItemComparer
179 {
180 ListView = listView,
181 ListViewManager = this
182 };
183 listView.ListViewItemSorter = listViewComparer;
184
185 ListColumns = new List<ListViewColumn>();
186 foreach (ColumnHeader columnHeader in ListView.Columns)
187 {
188 ListViewColumn listViewColumn = new ListViewColumn()
189 {
190 Name = columnHeader.Text,
191 TextAlign = columnHeader.TextAlign,
192 Width = columnHeader.Width,
193 Visible = true,
194 FieldIndex = Convert.ToInt32(columnHeader.Tag)
195 };
196 columnHeader.Tag = listViewColumn;
197 ListColumns.Add(listViewColumn);
198 }
199 VisibleListColumns = new List<ListViewColumn>();
200 if (listView.VirtualMode)
201 {
202 ListViewCache = new List<ListViewItem>();
203
204 ListView.RetrieveVirtualItem += delegate (object sender, RetrieveVirtualItemEventArgs e)
205 {
206 e.Item = ListViewCache[e.ItemIndex];
207 };
208 }
209 ListViewDict = new Dictionary<object, ListViewItem>();
210 listViewCacheMatched = new List<ListViewItem>();
211 ListView.ColumnClick += delegate (object sender, ColumnClickEventArgs e)
212 {
213 ((ListViewItemComparer)((ListView)sender).ListViewItemSorter).ColumnClick(e.Column);
214 Sort();
215 };
216 Filter = new Dictionary<int, string>();
217
218 ConfigureListView();
219 }
220
221 public void Sort()
222 {
223 ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter;
224
225 if (((ListViewItemComparer)ListView.ListViewItemSorter).Column != -1 &&
226 ((ListViewItemComparer)ListView.ListViewItemSorter).Order != SortOrder.None)
227 {
228
229 if (ListView.VirtualMode)
230 {
231 // Store selected items
232 List<object> selectedItems = null;
233 if (ListView.SelectedIndices.Count > 0)
234 {
235 int[] selected = new int[ListView.SelectedIndices.Count];
236 ListView.SelectedIndices.CopyTo(selected, 0);
237 selectedItems = new List<object>(selected.Length);
238 foreach (int index in ListView.SelectedIndices)
239 {
240 selectedItems.Add(ListViewCache[index].Tag);
241 }
242 }
243
244 ListViewCache.Sort(listViewComparer);
245 ListView.Refresh();
246
247 // Restore selected items
248 if (selectedItems != null)
249 {
250 ListView.BeginUpdate();
251 try
252 {
253 ListView.SelectedIndices.Clear();
254 foreach (var selectedObject in selectedItems)
255 {
256 ListView.SelectedIndices.Add(ListViewCache.IndexOf(ListViewDict[selectedObject]));
257 }
258 }
259 finally
260 {
261 ListView.EndUpdate();
262 }
263 }
264 }
265 else
266 {
267 ListView.Sort();
268 }
269 }
270
271 ListView.SetSortIcon(listViewComparer.Column, listViewComparer.Order);
272 }
273
274 public void ReloadCache(int itemCount)
275 {
276 ListView.BeginUpdate();
277 try
278 {
279 // Sync number of cached items
280 if (listViewCacheMatched.Count > itemCount) listViewCacheMatched.RemoveRange(itemCount, listViewCacheMatched.Count - itemCount);
281 if (listViewCacheMatched.Capacity < itemCount) listViewCacheMatched.Capacity = itemCount;
282 while (listViewCacheMatched.Count < itemCount)
283 {
284 ListViewItem listViewItem = new ListViewItem();
285 listViewCacheMatched.Add(listViewItem);
286 }
287
288 // Update items content
289 if (LoadItem != null)
290 {
291 for (int i = 0; i < itemCount; i++)
292 {
293 LoadItem?.Invoke(listViewCacheMatched[i], i);
294 }
295 }
296
297 if (Filter.Count > 0 && FilterItem != null)
298 {
299 ListViewCache = listViewCacheMatched.FindAll(x => (bool)FilterItem?.Invoke((object)x.Tag, Filter)).ToList();
300 }
301 else
302 {
303 ListViewCache = listViewCacheMatched.ToList();
304 }
305 ListView.VirtualListSize = ListViewCache.Count;
306
307 ListViewDict = ListViewCache.ToDictionary(key => key.Tag, value => value);
308 Sort();
309 }
310 finally
311 {
312 ListView.EndUpdate();
313 }
314 }
315
316 public void ReloadCacheItem(ListViewItem listViewItem, int itemIndex)
317 {
318 LoadItem?.Invoke(listViewItem, itemIndex);
319 }
320
321 public void ReloadCacheItemObject(ListViewItem listViewItem, object item)
322 {
323 LoadItemObject?.Invoke(listViewItem, item);
324 }
325
326 public void UpdateFromListView()
327 {
328 // Store current width
329 for (int i = 0; i < VisibleListColumns.Count; i++)
330 {
331 VisibleListColumns[i].Width = ListView.Columns[i].Width;
332 }
333 }
334
335 public void UpdateToListView()
336 {
337 ListView.BeginUpdate();
338 try
339 {
340 VisibleListColumns = ListColumns.FindAll(x => x.Visible);
341
342 while (ListView.Columns.Count < VisibleListColumns.Count)
343 ListView.Columns.Add("");
344 while (ListView.Columns.Count > VisibleListColumns.Count)
345 ListView.Columns.RemoveAt(ListView.Columns.Count - 1);
346 for (int i = 0; i < VisibleListColumns.Count; i++)
347 {
348 ColumnHeader item = ListView.Columns[i];
349 item.Text = VisibleListColumns[i].Name;
350 item.Width = VisibleListColumns[i].Width;
351 item.TextAlign = VisibleListColumns[i].TextAlign;
352 item.Tag = VisibleListColumns[i];
353 Theme.ApplyTheme(item);
354 }
355 }
356 finally
357 {
358 ListView.EndUpdate();
359 }
360 }
361
362 public void UpdateColumns()
363 {
364 UpdateFromListView();
365 UpdateToListView();
366 }
367
368 public void ConfigureListView(int column = -1, SortOrder order = SortOrder.None)
369 {
370 ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter;
371 listViewComparer.SetOrder(order);
372 listViewComparer.SetColumn(column);
373 if ((column != -1) && (order != SortOrder.None))
374 {
375 Sort();
376 }
377 }
378
379 public void SetColumnDataType(int fieldIndex, ColumnDataType dataType, ListViewItemCompareEventHandler compareHandler = null)
380 {
381 ListViewColumn column = ListColumns.FirstOrDefault(x => x.FieldIndex == fieldIndex);
382 if (column == null) return;
383 column.DataType = dataType;
384 column.OnCompare = compareHandler;
385 }
386
387 public void SetColumnVisible(int fieldIndex, bool visible)
388 {
389 ListViewColumn column = ListColumns.FirstOrDefault(x => x.FieldIndex == fieldIndex);
390 if (column == null) return;
391 column.Visible = visible;
392 }
393
394 public void LoadFromRegistry(string regSubKey)
395 {
396 RegistryKey regKey = Application.UserAppDataRegistry.OpenSubKey(regSubKey) ??
397 Application.UserAppDataRegistry.CreateSubKey(regSubKey);
398
399 ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter;
400 listViewComparer.Order = (SortOrder)regKey.GetValueInt("SortOrder", (int)listViewComparer.Order);
401 listViewComparer.Column = regKey.GetValueInt("SortColumn", listViewComparer.Column);
402 foreach (ListViewColumn listViewColumn in ListColumns)
403 {
404 listViewColumn.Width = regKey.GetValueInt("Width" + listViewColumn.Name, listViewColumn.Width);
405 listViewColumn.Visible = regKey.GetValueBool("Visible" + listViewColumn.Name, listViewColumn.Visible);
406 }
407 UpdateToListView();
408 }
409
410 public void SaveToRegistry(string regSubKey)
411 {
412 RegistryKey regKey = Application.UserAppDataRegistry.OpenSubKey(regSubKey, true) ??
413 Application.UserAppDataRegistry.CreateSubKey(regSubKey);
414
415 ListViewItemComparer listViewComparer = (ListViewItemComparer)ListView.ListViewItemSorter;
416 regKey.SetValueInt("SortOrder", (int)listViewComparer.Order);
417 regKey.SetValueInt("SortColumn", listViewComparer.Column);
418 foreach (ListViewColumn listViewColumn in ListColumns)
419 {
420 regKey.SetValueInt("Width" + listViewColumn.Name, listViewColumn.Width);
421 regKey.SetValueBool("Visible" + listViewColumn.Name, listViewColumn.Visible);
422 }
423 }
424 }
425
426 internal static class ListViewExtensions
427 {
428 [StructLayout(LayoutKind.Sequential)]
429 public struct LVCOLUMN
430 {
431 public Int32 mask;
432 public Int32 cx;
433 [MarshalAs(UnmanagedType.LPTStr)]
434 public string pszText;
435 public IntPtr hbm;
436 public Int32 cchTextMax;
437 public Int32 fmt;
438 public Int32 iSubItem;
439 public Int32 iImage;
440 public Int32 iOrder;
441 }
442
443 const Int32 HDI_WIDTH = 0x0001;
444 const Int32 HDI_HEIGHT = HDI_WIDTH;
445 const Int32 HDI_TEXT = 0x0002;
446 const Int32 HDI_FORMAT = 0x0004;
447 const Int32 HDI_LPARAM = 0x0008;
448 const Int32 HDI_BITMAP = 0x0010;
449 const Int32 HDI_IMAGE = 0x0020;
450 const Int32 HDI_DI_SETITEM = 0x0040;
451 const Int32 HDI_ORDER = 0x0080;
452 const Int32 HDI_FILTER = 0x0100;
453
454 const Int32 HDF_LEFT = 0x0000;
455 const Int32 HDF_RIGHT = 0x0001;
456 const Int32 HDF_CENTER = 0x0002;
457 const Int32 HDF_JUSTIFYMASK = 0x0003;
458 const Int32 HDF_RTLREADING = 0x0004;
459 const Int32 HDF_OWNERDRAW = 0x8000;
460 const Int32 HDF_STRING = 0x4000;
461 const Int32 HDF_BITMAP = 0x2000;
462 const Int32 HDF_BITMAP_ON_RIGHT = 0x1000;
463 const Int32 HDF_IMAGE = 0x0800;
464 const Int32 HDF_SORTUP = 0x0400;
465 const Int32 HDF_SORTDOWN = 0x0200;
466
467 const Int32 LVM_FIRST = 0x1000; // List messages
468 const Int32 LVM_GETHEADER = LVM_FIRST + 31;
469 const Int32 HDM_FIRST = 0x1200; // Header messages
470 const Int32 HDM_SETIMAGELIST = HDM_FIRST + 8;
471 const Int32 HDM_GETIMAGELIST = HDM_FIRST + 9;
472 const Int32 HDM_GETITEM = HDM_FIRST + 11;
473 const Int32 HDM_SETITEM = HDM_FIRST + 12;
474
475 [DllImport("user32.dll")]
476 private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
477
478 [DllImport("user32.dll", EntryPoint = "SendMessage")]
479 private static extern IntPtr SendMessageLVCOLUMN(IntPtr hWnd, Int32 Msg, IntPtr wParam, ref LVCOLUMN lPLVCOLUMN);
480
481
482 // This method used to set arrow icon
483 public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order)
484 {
485 IntPtr columnHeader = SendMessage(listView.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
486
487 for (int columnNumber = 0; columnNumber <= listView.Columns.Count - 1; columnNumber++)
488 {
489 IntPtr columnPtr = new IntPtr(columnNumber);
490 LVCOLUMN lvColumn = new LVCOLUMN {mask = HDI_FORMAT};
491
492 SendMessageLVCOLUMN(columnHeader, HDM_GETITEM, columnPtr, ref lvColumn);
493
494 if (order != SortOrder.None && columnNumber == columnIndex)
495 {
496 switch (order)
497 {
498 case SortOrder.Ascending:
499 lvColumn.fmt &= ~HDF_SORTDOWN;
500 lvColumn.fmt |= HDF_SORTUP;
501 break;
502 case SortOrder.Descending:
503 lvColumn.fmt &= ~HDF_SORTUP;
504 lvColumn.fmt |= HDF_SORTDOWN;
505 break;
506 }
507 lvColumn.fmt |= (HDF_LEFT | HDF_BITMAP_ON_RIGHT);
508 }
509 else
510 {
511 lvColumn.fmt &= ~HDF_SORTDOWN & ~HDF_SORTUP & ~HDF_BITMAP_ON_RIGHT;
512 }
513
514 SendMessageLVCOLUMN(columnHeader, HDM_SETITEM, columnPtr, ref lvColumn);
515 }
516 }
517 }
518}
Note: See TracBrowser for help on using the repository browser.