在Windows Forms應用程序中,ListView是一個非常實用的控件,用于顯示數(shù)據(jù)列表。但默認情況下,ListView并不支持點擊列頭進行排序,并增加了單元格設置顏色。本文將介紹如何開發(fā)一個可點擊列頭排序的ListView控件。
1. 創(chuàng)建自定義比較器
ListViewItemSorter
?類是一個用于排序 ListView 控件中項目的自定義比較器。它實現(xiàn)了?IComparer<ListViewItem>
?接口,可以按照指定的列、排序順序和數(shù)據(jù)類型對 ListViewItem 進行排序。
public class ListViewItemSorter : IComparer<ListViewItem>
{
? ?private int _columnIndex;
? ?private SortOrder _sortOrder;
? ?private ColumnDataType _dataType;
? ?public ListViewItemSorter(int columnIndex, SortOrder sortOrder, ColumnDataType dataType)
{
? ? ? ?_columnIndex = columnIndex;
? ? ? ?_sortOrder = sortOrder;
? ? ? ?_dataType = dataType;
? ?}
? ?public int Compare(ListViewItem x, ListViewItem y)
{
? ? ? ?string textX = x.SubItems[_columnIndex].Text;
? ? ? ?string textY = y.SubItems[_columnIndex].Text;
? ? ? ?int result;
? ? ? ?switch (_dataType)
? ? ? ?{
? ? ? ? ? ?case ColumnDataType.Number:
? ? ? ? ? ? ? ?if (double.TryParse(textX, out double numX) && double.TryParse(textY, out double numY))
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?result = numX.CompareTo(numY);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?else
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?result = string.Compare(textX, textY, StringComparison.OrdinalIgnoreCase);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case ColumnDataType.Date:
? ? ? ? ? ? ? ?if (DateTime.TryParse(textX, out DateTime dateX) && DateTime.TryParse(textY, out DateTime dateY))
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?result = dateX.CompareTo(dateY);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?else
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?result = string.Compare(textX, textY, StringComparison.OrdinalIgnoreCase);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case ColumnDataType.Text:
? ? ? ? ? ?default:
? ? ? ? ? ? ? ?result = string.Compare(textX, textY, StringComparison.OrdinalIgnoreCase);
? ? ? ? ? ? ? ?break;
? ? ? ?}
? ? ? ?return _sortOrder == SortOrder.Ascending ? result : -result;
? ?}
}
構造函數(shù)接受三個參數(shù):
Compare
?方法實現(xiàn)了實際的比較邏輯:
比較結果會根據(jù)指定的排序順序進行調(diào)整(升序或降序)
2. 重寫SortableVirtualListView
SortableVirtualListView 類是一個擴展的 ListView 控件,它提供了高效的大數(shù)據(jù)集處理和排序功能。
public class SortableVirtualListView : ListView
{
? ?private ColumnHeader _sortedColumn = null;
? ?private SortOrder _sortOrder = SortOrder.None;
? ?private List<ListViewItem> _items = new List<ListViewItem>();
? ?private List<int> _sortedIndices = new List<int>();
? ?private Dictionary<int, ColumnDataType> _columnTypes = new Dictionary<int, ColumnDataType>();
? ?private const int MaxSortItems = 100000;
? ?private ProgressBar _progressBar;
? ?private BackgroundWorker _backgroundWorker;
? ?public event EventHandler DataLoadCompleted;
? ?public SortableVirtualListView()
? ?{
? ? ? ?this.DoubleBuffered = true;
? ? ? ?this.VirtualMode = true;
? ? ? ?this.VirtualListSize = 0;
? ? ? ?InitializeProgressBar();
? ? ? ?InitializeBackgroundWorker();
? ?}
? ?protected override void OnRetrieveVirtualItem(RetrieveVirtualItemEventArgs e)
? ?{
? ? ? ?if (e.ItemIndex >= 0 && e.ItemIndex < _items.Count)
? ? ? ?{
? ? ? ? ? ?int actualIndex = _sortedIndices.Count > e.ItemIndex ? _sortedIndices[e.ItemIndex] : e.ItemIndex;
? ? ? ? ? ?e.Item = _items[actualIndex];
? ? ? ?}
? ?}
? ?private void InitializeProgressBar()
? ?{
? ? ? ?_progressBar = new ProgressBar
? ? ? ?{
? ? ? ? ? ?Dock = DockStyle.Bottom,
? ? ? ? ? ?Visible = false,
? ? ? ? ? ?Height = 20
? ? ? ?};
? ? ? ?this.Controls.Add(_progressBar);
? ?}
? ?private void InitializeBackgroundWorker()
? ?{
? ? ? ?_backgroundWorker = new BackgroundWorker
? ? ? ?{
? ? ? ? ? ?WorkerReportsProgress = true,
? ? ? ? ? ?WorkerSupportsCancellation = true
? ? ? ?};
? ? ? ?_backgroundWorker.DoWork += BackgroundWorker_DoWork;
? ? ? ?_backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
? ? ? ?_backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
? ?}
? ?public void LoadItems(IEnumerable<ListViewItem> items)
? ?{
? ? ? ?if (_backgroundWorker.IsBusy)
? ? ? ?{
? ? ? ? ? ?_backgroundWorker.CancelAsync();
? ? ? ?}
? ? ? ?_progressBar.Visible = true;
? ? ? ?_backgroundWorker.RunWorkerAsync(items);
? ?}
? ?private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
? ?{
? ? ? ?var items = (IEnumerable<ListViewItem>)e.Argument;
? ? ? ?List<ListViewItem> loadedItems = new List<ListViewItem>();
? ? ? ?int totalItems = items.Count();
? ? ? ?int count = 0;
? ? ? ?foreach (var item in items)
? ? ? ?{
? ? ? ? ? ?if (_backgroundWorker.CancellationPending)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?e.Cancel = true;
? ? ? ? ? ? ? ?return;
? ? ? ? ? ?}
? ? ? ? ? ?loadedItems.Add(item);
? ? ? ? ? ?count++;
? ? ? ? ? ?if (count % 1000 == 0 || count == totalItems)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?int progressPercentage = (int)((double)count / totalItems * 100);
? ? ? ? ? ? ? ?_backgroundWorker.ReportProgress(progressPercentage);
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?e.Result = loadedItems;
? ?}
? ?private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
? ?{
? ? ? ?_progressBar.Value = e.ProgressPercentage;
? ?}
? ?private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
? ?{
? ? ? ?if (!e.Cancelled && e.Error == null)
? ? ? ?{
? ? ? ? ? ?SetItems((List<ListViewItem>)e.Result);
? ? ? ?}
? ? ? ?_progressBar.Visible = false;
? ? ? ?DataLoadCompleted?.Invoke(this, EventArgs.Empty);
? ?}
? ?public void SetColumnDataType(int columnIndex, ColumnDataType dataType)
? ?{
? ? ? ?_columnTypes[columnIndex] = dataType;
? ?}
? ?protected override void OnColumnClick(ColumnClickEventArgs e)
? ?{
? ? ? ?base.OnColumnClick(e);
? ? ? ?ColumnHeader clickedColumn = this.Columns[e.Column];
? ? ? ?if (_sortedColumn == null)
? ? ? ?{
? ? ? ? ? ?_sortOrder = SortOrder.Ascending;
? ? ? ?}
? ? ? ?else if (_sortedColumn == clickedColumn)
? ? ? ?{
? ? ? ? ? ?_sortOrder = _sortOrder == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
? ? ? ?}
? ? ? ?else
? ? ? ?{
? ? ? ? ? ?_sortOrder = SortOrder.Ascending;
? ? ? ?}
? ? ? ?_sortedColumn = clickedColumn;
? ? ? ?ColumnDataType dataType = _columnTypes.ContainsKey(e.Column) ? _columnTypes[e.Column] : ColumnDataType.Text;
? ? ? ?SortItems(e.Column, _sortOrder, dataType);
? ? ? ?this.Refresh();
? ? ? ?UpdateColumnHeaders();
? ?}
? ?private void SortItems(int columnIndex, SortOrder sortOrder, ColumnDataType dataType)
? ?{
? ? ? ?// 使用 LINQ 對索引進行排序,而不是對實際項目進行排序 ?
? ? ? ?IEnumerable<int> query = Enumerable.Range(0, _items.Count);
? ? ? ?switch (dataType)
? ? ? ?{
? ? ? ? ? ?case ColumnDataType.Number:
? ? ? ? ? ? ? ?query = sortOrder == SortOrder.Ascending
? ? ? ? ? ? ? ? ? ?? query.OrderBy(i => GetDoubleValue(_items[i].SubItems[columnIndex].Text))
? ? ? ? ? ? ? ? ? ?: query.OrderByDescending(i => GetDoubleValue(_items[i].SubItems[columnIndex].Text));
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case ColumnDataType.Date:
? ? ? ? ? ? ? ?query = sortOrder == SortOrder.Ascending
? ? ? ? ? ? ? ? ? ?? query.OrderBy(i => GetDateTimeValue(_items[i].SubItems[columnIndex].Text))
? ? ? ? ? ? ? ? ? ?: query.OrderByDescending(i => GetDateTimeValue(_items[i].SubItems[columnIndex].Text));
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case ColumnDataType.Text:
? ? ? ? ? ?default:
? ? ? ? ? ? ? ?query = sortOrder == SortOrder.Ascending
? ? ? ? ? ? ? ? ? ?? query.OrderBy(i => _items[i].SubItems[columnIndex].Text)
? ? ? ? ? ? ? ? ? ?: query.OrderByDescending(i => _items[i].SubItems[columnIndex].Text);
? ? ? ? ? ? ? ?break;
? ? ? ?}
? ? ? ?_sortedIndices = query.Take(MaxSortItems).ToList();
? ?}
? ?private double GetDoubleValue(string text)
? ?{
? ? ? ?return double.TryParse(text, out double result) ? result : double.MinValue;
? ?}
? ?private DateTime GetDateTimeValue(string text)
? ?{
? ? ? ?return DateTime.TryParse(text, out DateTime result) ? result : DateTime.MinValue;
? ?}
? ?public void SetItems(List<ListViewItem> items)
? ?{
? ? ? ?_items = items;
? ? ? ?_sortedIndices = Enumerable.Range(0, Math.Min(items.Count, MaxSortItems)).ToList();
? ? ? ?this.VirtualListSize = items.Count;
? ? ? ?this.Refresh();
? ?}
? ?private void UpdateColumnHeaders()
? ?{
? ? ? ?foreach (ColumnHeader column in this.Columns)
? ? ? ?{
? ? ? ? ? ?if (column == _sortedColumn)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?column.Text = column.Text.TrimEnd('▲', '▼') + (_sortOrder == SortOrder.Ascending ? " ▲" : " ▼");
? ? ? ? ? ?}
? ? ? ? ? ?else
? ? ? ? ? ?{
? ? ? ? ? ? ? ?column.Text = column.Text.TrimEnd('▲', '▼');
? ? ? ? ? ?}
? ? ? ?}
? ?}
}
public enum ColumnDataType
{
? ?Text,
? ?Number,
? ?Date
}
使用控件
public partial class Form1 : Form
{
? ?private SortableVirtualListView sortableListView;
? ?private const int ItemCount = 1000000; // 100萬條數(shù)據(jù) ?
? ?public Form1()
? ?{
? ? ? ?InitializeComponent();
? ? ? ?InitializeSortableListView();
? ? ? ?LoadLargeDataSet();
? ?}
? ?private void InitializeSortableListView()
? ?{
? ? ? ?sortableListView = new SortableVirtualListView
? ? ? ?{
? ? ? ? ? ?Dock = DockStyle.Fill,
? ? ? ? ? ?View = View.Details,
? ? ? ? ? ?FullRowSelect = true,
? ? ? ? ? ?GridLines = true
? ? ? ?};
? ? ? ?sortableListView.Columns.Add("ID", 80);
? ? ? ?sortableListView.Columns.Add("Name", 150);
? ? ? ?sortableListView.Columns.Add("Value", 100);
? ? ? ?sortableListView.Columns.Add("Date", 120);
? ? ? ?sortableListView.SetColumnDataType(0, ColumnDataType.Number);
? ? ? ?sortableListView.SetColumnDataType(1, ColumnDataType.Text);
? ? ? ?sortableListView.SetColumnDataType(2, ColumnDataType.Number);
? ? ? ?sortableListView.SetColumnDataType(3, ColumnDataType.Date);
? ? ? ?sortableListView.DataLoadCompleted += SortableListView_DataLoadCompleted;
? ? ? ?this.Controls.Add(sortableListView);
? ?}
? ?private void LoadLargeDataSet()
? ?{
? ? ? ?Random random = new Random();
? ? ? ?DateTime startDate = new DateTime(2000, 1, 1);
? ? ? ?var items = Enumerable.Range(0, ItemCount).Select(i => new ListViewItem(new[]
? ? ? ?{
? ? ? ?i.ToString(),
? ? ? ?$"Item {i}",
? ? ? ?random.Next(1, 1000).ToString(),
? ? ? ?startDate.AddDays(random.Next(0, 8000)).ToString("yyyy-MM-dd")
? ?}));
? ? ? ?sortableListView.LoadItems(items);
? ?}
? ?private void SortableListView_DataLoadCompleted(object sender, EventArgs e)
? ?{
? ? ? ?this.Text = "Large Data Set Loaded";
? ?}
}
?
結論
通過以上步驟,我們成功創(chuàng)建了一個可點擊列頭排序的ListView控件。這個自定義控件不僅支持點擊列頭進行排序,還能顯示排序方向,極大地提高了用戶體驗。您可以根據(jù)需要進一步擴展這個控件,例如添加自定義的排序算法或者支持不同數(shù)據(jù)類型的排序。
該文章在 2024/10/9 12:29:20 編輯過