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

这是一个简单的过程吗?

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

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

【问题讨论】:

    标签: c# winforms listbox


    【解决方案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 控件,这是资源管理器用来显示文件夹内容的控件。它更复杂,但为您实现了项目拖动。

        【讨论】:

        • ...并且不支持简单的事情,例如数据绑定列表项:(
        • ...在列表或详细视图中显示时拖动也不起作用。
        猜你喜欢
        • 1970-01-01
        • 2012-05-31
        • 1970-01-01
        • 2012-06-19
        • 2012-05-08
        • 2020-04-05
        • 2010-12-28
        • 1970-01-01
        • 2013-05-11
        相关资源
        最近更新 更多