LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

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

admin
2024年10月9日 7:49 本文热度 517

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

  1. 构造函数接受三个参数:

    • columnIndex:要排序的列的索引

    • sortOrder:排序顺序(升序或降序)

    • dataType:列数据的类型(数字、日期或文本)

  2. Compare 方法实现了实际的比较逻辑:

    • 对于数字类型,尝试将文本转换为 double 进行比较

    • 对于日期类型,尝试将文本转换为 DateTime 进行比较

    • 对于文本类型或无法转换的情况,使用字符串比较

    • 根据指定的列索引获取要比较的文本

    • 根据数据类型进行相应的比较:

  3. 比较结果会根据指定的排序顺序进行调整(升序或降序)

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}

  • 虚拟模式:使用 ListView 的虚拟模式来处理大量数据,提高性能。

  • 异步数据加载:

    • 使用 BackgroundWorker 在后台异步加载数据。

    • 包含进度条显示加载进度。

    • 提供 DataLoadCompleted 事件通知数据加载完成。

  • 自定义排序:

    • 支持点击列头进行排序。

    • 可以为每列指定数据类型(文本、数字、日期)。

    • 使用索引排序而不是直接排序项目,提高大数据集的排序效率。

  • 列头排序指示:

    • 在列头显示排序方向(升序/降序)。

  • 性能优化:

    • 使用双缓冲减少闪烁。

    • 限制最大排序项目数量(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(),        $"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 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved