在Windows Forms应用程序中,ListView是一个非常实用的控件,用于显示数据列表。但默认情况下,ListView并不支持点击列头进行排序,并增加了单元格设置颜色。本文将介绍如何开发一个可点击列头排序的ListView控件。
1. 创建自定义比较器
ListViewItemSorter
类是一个用于排序 ListView 控件中项目的自定义比较器。它实现了 IComparer<ListViewItem>
接口,可以按照指定的列、排序顺序和数据类型对 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;
}
}
构造函数接受三个参数:
Compare
方法实现了实际的比较逻辑:
根据指定的列索引获取要比较的文本
根据数据类型进行相应的比较:
比较结果会根据指定的排序顺序进行调整(升序或降序)
2. 重写SortableVirtualListView
SortableVirtualListView 类是一个扩展的 ListView 控件,它提供了高效的大数据集处理和排序功能。
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万条数据
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";
}
}
结论
通过以上步骤,我们成功创建了一个可点击列头排序的ListView控件。这个自定义控件不仅支持点击列头进行排序,还能显示排序方向,极大地提高了用户体验。您可以根据需要进一步扩展这个控件,例如添加自定义的排序算法或者支持不同数据类型的排序。
该文章在 2024/10/9 12:29:20 编辑过