使用拖放重新排序winforms列表框?

Posted

技术标签:

【中文标题】使用拖放重新排序winforms列表框?【英文标题】:Reorder a winforms listbox using drag and drop? 【发布时间】:2010-10-22 18:05:49 【问题描述】:

这是一个简单的过程吗?

我只是为内部工具编写一个快速的 hacky 用户界面。

我不想花太多时间在上面。

【问题讨论】:

【参考方案1】:

这是一个快速而肮脏的应用程序。基本上,我创建了一个带有按钮和 ListBox 的表单。单击按钮时,ListBox 将填充下一个 20 天的日期(必须使用某些东西来进行测试)。然后,它允许在 ListBox 中拖放以进行重新排序:

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
            this.listBox1.AllowDrop = true;
        

        private void button1_Click(object sender, EventArgs e)
        
            for (int i = 0; i <= 20; i++)
            
                this.listBox1.Items.Add(DateTime.Now.AddDays(i));
            
        

        private void listBox1_MouseDown(object sender, MouseEventArgs e)
        
            if (this.listBox1.SelectedItem == null) return;
            this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
        

        private void listBox1_DragOver(object sender, DragEventArgs e)
        
            e.Effect = DragDropEffects.Move;
        

        private void listBox1_DragDrop(object sender, DragEventArgs e)
        
            Point point = listBox1.PointToClient(new Point(e.X, e.Y));
            int index = this.listBox1.IndexFromPoint(point);
            if (index < 0) index = this.listBox1.Items.Count-1;
            object data = e.Data.GetData(typeof(DateTime));
            this.listBox1.Items.Remove(data);
            this.listBox1.Items.Insert(index, data);
        

【讨论】:

这太好了,谢谢。有两个(非常小的)陷阱。在 _MouseDown 中,选择不会在事件触发之前切换,因此您需要调用 IndexFromPoint 来获取当前选择(并检查列表底部的 -1 含义)。但是,在这里,X 和 Y 已经是客户端坐标,因此您无需调用 PointToClient 在 _DragDrop 中,您还需要检查索引是否为 -1,表示从列表底部删除,然后忽略该放置或将项目移动到底部您认为合适的列表。这两件事不同,这正是我所追求的简单解决方案。 也喜欢这个小代码示例。发现与 Gareth 相同的错误并编辑答案以删除它们,希望如此。 效果很好! “Datatme”需要改成对应的类型 我认为 DragOver 事件还应该包括对数据类型的检查,如果我们不想为 eerything 和任何东西显示放置光标。 ... 如果 e.Data.GetDataPresent(GetType(DateTime)) 那么 ... 请注意,由于处理 MouseDown,这样做会弄乱 SelectedIndexChanged 和 DoubleClick 事件(可能还有其他事件); SelectedIndexChanged 不再因鼠标点击而触发(但仍响应键盘),并且 DoubleClick 变得非常挑剔且难以触发。【参考方案2】:

晚了 7 年。但是对于任何新人,这里是代码。

private void listBox1_MouseDown(object sender, MouseEventArgs e)
    
        if (this.listBox1.SelectedItem == null) return;
        this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
    

    private void listBox1_DragOver(object sender, DragEventArgs e)
    
        e.Effect = DragDropEffects.Move;
    

    private void listBox1_DragDrop(object sender, DragEventArgs e)
    
        Point point = listBox1.PointToClient(new Point(e.X, e.Y));
        int index = this.listBox1.IndexFromPoint(point);
        if (index < 0) index = this.listBox1.Items.Count - 1;
        object data = listBox1.SelectedItem;
        this.listBox1.Items.Remove(data);
        this.listBox1.Items.Insert(index, data);
    

    private void itemcreator_Load(object sender, EventArgs e)
    
        this.listBox1.AllowDrop = true;
    

【讨论】:

“itemcreator_Load”函数是否相关?还是只是一个错字?【参考方案3】:

如果您从未实施拖放操作,第一次需要几个小时,想要正确完成它并且必须通读文档。尤其是用户取消操作时的即时反馈和恢复列表需要一些思考。将行为封装到可重用的用户控件中也需要一些时间。

如果您从未做过拖放操作,请查看来自 MSDN 的 drag and drop example。这将是一个很好的起点,您可能需要半天时间才能完成工作。

【讨论】:

【参考方案4】:

这依赖于上面@BFree 的回答 - 感谢它提供了很多帮助。 我在尝试使用该解决方案时遇到了一个错误,因为我正在为我的列表框使用数据源。出于完整性考虑,如果您尝试直接将项目删除或添加到列表框,则会出现此错误:

// Causes error
this.listBox1.Items.Remove(data);

错误: System.ArgumentException: '设置 DataSource 属性时无法修改项目集合。'

解决方案:更新数据源本身,然后重新绑定到您的列表框。 Program.SelectedReports 是一个 BindingList。

代码:

    private void listboxSelectedReports_DragDrop(object sender, DragEventArgs e)
    
        // Get the point where item was dropped.
        Point point = listboxSelectedReports.PointToClient(new Point(e.X, e.Y));
        // Get the index of the item where the point was dropped
        int index = this.listboxSelectedReports.IndexFromPoint(point);
        // if index is invalid, put item at the end of the list.
        if (index < 0) index = this.listboxSelectedReports.Items.Count - 1;
        // Get the item's data.
        ReportModel data = (ReportModel)e.Data.GetData(typeof(ReportModel));
     

        // Update the property we use to control sorting within the original datasource
        int newSortOrder = 0;
        foreach (ReportModel report in Program.SelectedReports) 
            // match sorted item on unique property
            if (data.Id == report.Id)
            
                report.SortOrder = index;
                if (index == 0) 
                    // only increment our new sort order if index is 0
                    newSortOrder += 1;
                
             else 
                // skip our dropped item's index
                if (newSortOrder == index) 
                    newSortOrder += 1;
                
                report.SortOrder = newSortOrder;
                newSortOrder += 1;
            
        

        

        // Sort original list and reset the list box datasource.
        // Note:  Tried other things, Reset(), Invalidate().  Updating DataSource was only way I found that worked??
        Program.SelectedReports = new BindingList<ReportModel>(Program.SelectedReports.OrderBy(x => x.SortOrder).ToList());
        listboxSelectedReports.DataSource = Program.SelectedReports;
        listboxSelectedReports.DisplayMember = "Name";
        listboxSelectedReports.ValueMember = "ID";
    

其他说明: BindingList 在这个命名空间下:

using System.ComponentModel;

在向列表动态添加项目时,请确保填充排序属性。我使用了一个整数字段“SortOrder”。

当您删除一个项目时,我不必担心更新 Sorting 属性,因为它只会产生一个数字间隙,这在我的情况下是可以的,YMMV。

说实话,除了 foreach 循环之外,可能还有更好的排序算法,但在我的情况下,我处理的项目数量非常有限。

【讨论】:

【参考方案5】:

另一种方法是使用the list-view 控件,这是资源管理器用来显示文件夹内容的控件。它更复杂,但为您实现了项目拖动。

【讨论】:

...并且不支持简单的事情,例如数据绑定列表项:( ...在列表或详细视图中显示时拖动也不起作用。

以上是关于使用拖放重新排序winforms列表框?的主要内容,如果未能解决你的问题,请参考以下文章

tkinter 列表框用 python 拖放

一般拖放 ListBoxItems

WPF MVVM C#​​:列表框拖放,无需代码

WPF MVVM C#:列表框拖放而没有代码

如何通过拖放同步两个列表框元素?

Winforms - 列表框项目悬停并选择颜色