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

54

这是一个简单的过程吗?

我只是为内部工具编写一个快速且不太正式的用户界面。

我不想在上面花费太长时间。

5个回答

101

这是一个快速的简单应用程序。基本上,我创建了一个带有按钮和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);
        }

6
非常感谢,这很棒!只有两个小问题。在 _MouseDown 中,在事件触发之前选择不会切换,因此您需要调用 IndexFromPoint 来获取当前的选择(并检查 -1 是否表示列表底部)。在这里,X 和 Y 已经是客户端坐标,因此不需要调用 PointToClient。在 _DragDrop 中,你还需要检查 index 是否为 -1,表示将项拖放到列表底部,并根据需要忽略该项或将其移动到列表底部。除了这两个问题外,这正是我想要的简单解决方案。 - Gareth Simpson
5
我很喜欢这个小代码示例。像 Gareth 一样发现了相同的 bug,并编辑了答案以删除它们,希望如此做可以解决问题。 - Leon Bambrick
运行得很好!“Datatme”需要更改为相应类型。 - camino
1
我认为DragOver事件还应该包括对数据类型的检查,如果我们不想为每个东西都显示一个放置光标的话。 ... 如果e.Data.GetDataPresent(GetType(DateTime)) Then ... - bkqc
1
请注意,这样做会影响SelectedIndexChanged和DoubleClick事件(以及可能的其他事件),因为它涉及MouseDown的处理;由于鼠标单击不再触发SelectedIndexChanged(但仍然响应键盘),而DoubleClick变得非常棘手和难以触发。 - Setsu

14

迟了七年,但对于新手来说,这里有代码。

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”函数是否相关?还是只是一个打字错误? - Human Wannabe
是的,这确实很重要。itemcreator_Load 基本上就是在新项目中的 Form1_Load。如果没有允许拖放(AllowDrop),拖放操作将无法正常工作。 - adiuva
谢谢,当使用BindingList作为数据源时,应该在bindingList上执行remove/insert操作而不是listBox,否则会抛出错误。我还必须清除并重置所选索引以匹配拖放的项,否则第一项总是被选中。 - Jindil

3
第一次实现拖放可能需要几个小时,如果你以前从未实现过拖放并想要做到正确,必须仔细阅读文档。特别是即时反馈和在用户取消操作时恢复列表需要一些思考。将这种行为封装成可重用的用户控件也需要一些时间。
如果你完全没有做过拖放,请查看MSDN上的拖放示例。这将是一个很好的起点,并且你可能需要花费半天时间才能让它正常工作。

1
这依赖于@BFree上面的答案 - 感谢它帮了很多忙。
当我尝试使用该解决方案时,遇到了一个错误,因为我正在为我的列表框使用DataSource。 仅为完整性,如果您尝试直接从列表框中删除或添加项目,则会出现此错误:
// Causes error
this.listBox1.Items.Remove(data);

错误: System.ArgumentException:“当设置DataSource属性时,Items集合无法修改。”
解决方法:先更新数据源,然后重新绑定到listbox。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'。
当你删除一个项目时,我不需要担心更新排序属性,因为它只会创建一个数字间隔,在我的情况下这是可以的,你的情况可能有所不同。
老实说,除了foreach循环之外,可能还有更好的排序算法,但在我的情况下,我处理的项目数量非常有限。

0

另一种选择是使用列表视图控件,这是资源管理器用来显示文件夹内容的控件。它更复杂,但可以为您实现项目拖动。


...并且不支持像数据绑定列表项这样的简单功能 :( - nathanchere
在列表或详细视图中显示时,拖动也无法工作。 - nathanchere

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接