C#开发可点击列头排序的ListView控件

C#开发可点击列头排序的ListView控件

编码文章call10242025-08-30 1:29:4610A+A-

在Windows Forms应用程序中,ListView是一个非常实用的控件,用于显示数据列表。但默认情况下,ListView并不支持点击列头进行排序。本文将介绍如何开发一个可点击列头排序的ListView控件。

创建自定义比较器

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;
    }
}

  1. 构造函数接受三个参数: columnIndex:要排序的列的索引 sortOrder:排序顺序(升序或降序) dataType:列数据的类型(数字、日期或文本)
  2. Compare 方法实现了实际的比较逻辑: 根据指定的列索引获取要比较的文本 根据数据类型进行相应的比较: 对于数字类型,尝试将文本转换为 double 进行比较 对于日期类型,尝试将文本转换为 DateTime 进行比较 对于文本类型或无法转换的情况,使用字符串比较
  3. 比较结果会根据指定的排序顺序进行调整(升序或降序)

重写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
}
  1. 虚拟模式:使用 ListView 的虚拟模式来处理大量数据,提高性能。
  2. 异步数据加载: 使用 BackgroundWorker 在后台异步加载数据。 包含进度条显示加载进度。 提供 DataLoadCompleted 事件通知数据加载完成。
  3. 自定义排序: 支持点击列头进行排序。 可以为每列指定数据类型(文本、数字、日期)。 使用索引排序而不是直接排序项目,提高大数据集的排序效率。
  4. 列头排序指示: 在列头显示排序方向(升序/降序)。
  5. 性能优化: 使用双缓冲减少闪烁。 限制最大排序项目数量(MaxSortItems)以处理超大数据集。

使用控件

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(),
        #34;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控件。这个自定义控件不仅支持点击列头进行排序,还能显示排序方向,极大地提高了用户体验。您可以根据需要进一步扩展这个控件,例如添加自定义的排序算法或者支持不同数据类型的排序。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4