ListBox MultiSelect 拖放问题

ListBox MultiSelect Drag and Drop problem

我正在尝试在 windows 表单的 ListBox 之间拖放多个项目。我遇到的问题是,如果我 select 多个项目按住 Shift 键并尝试拖放它而不释放该键,我会收到错误消息。实际上 SelectedIndices 和 SelectedItems 仅显示 1 个项目,即我首先单击的项目,即使在列表框中突出显示了多个项目。

我正在使用 SelectionMode = MultiExtended

void ZListBox_MouseMove(object sender, MouseEventArgs e)
{
    if (isDraggingPoint.HasValue && e.Button == MouseButtons.Left && SelectedIndex >= 0)
    {
        var pointToClient = PointToClient(MousePosition);

        if (isDraggingPoint.Value.Y != pointToClient.Y)
        {
            lastIndexItemOver = -1;
            isDraggingPoint = null;

            var dropResult = DoDragDrop(SelectedItems, DragDropEffects.Copy);
        }
    }
}

似乎如果我在 "DoDragDrop" 之前不松开鼠标左键,则不会 select 编辑这些项目,而且如果我尝试获取 SelectedIndices 来自另一个 ListBox,计数是 "selected items" 的数量,但是当我尝试浏览列表时,我得到一个 IndexOutOfRangeException。

有解决办法吗?

重现问题的示例代码: (重现: 1- Select 一项 2- 按住 shift 键并单击另一个项目,而不是不释放 shift 键和鼠标按钮,拖动该项目(如果 'if' 内有断点,您将在 SelectedItems 上看到只有 1 个项目) )

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Load += Form1_Load;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var someList = new List<ListItemsTest>();
            someList.Add(new ListItemsTest() { ID = 1, Name = "Name 1" });
            someList.Add(new ListItemsTest() { ID = 2, Name = "Name 2" });
            someList.Add(new ListItemsTest() { ID = 3, Name = "Name 3" });
            someList.Add(new ListItemsTest() { ID = 4, Name = "Name 4" });
            someList.Add(new ListItemsTest() { ID = 5, Name = "Name 5" });
            listBox1.DisplayMember = "Name";
            listBox1.ValueMember = "ID";
            listBox1.DataSource = someList;
            listBox1.SelectionMode = SelectionMode.MultiExtended;
            listBox1.MouseMove += ListBox1_MouseMove;
            listBox1.AllowDrop = true;
        }

        void ListBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && listBox1.SelectedIndex >= 0)
            {
                var dropResult = DoDragDrop(listBox1.SelectedItems, DragDropEffects.Copy);
            }
        }

        public class ListItemsTest
        {
            public int ID { get; set; }
            public string Name { get; set; }
        }
    }

我现在明白了。有趣的是,按 Ctrl 键到列表中的 select 项可以正常工作,但按 Shift 键则不行。我的解决方案是重新创建您自己的 SelectedItems 集合:

void listBox1_MouseMove(object sender, MouseEventArgs e) {
  if (e.Button == MouseButtons.Left && listBox1.SelectedItems.Count > 0) {
    int mouseIndex = listBox1.IndexFromPoint(e.Location);
    if (mouseIndex > -1) {
      ListBox.SelectedObjectCollection x = new ListBox.SelectedObjectCollection(listBox1);
      if (Control.ModifierKeys == Keys.Shift) {
        int i1 = Math.Min(listBox1.SelectedIndex, mouseIndex);
        int i2 = Math.Max(listBox1.SelectedIndex, mouseIndex);
        for (int i = i1; i <= i2; ++i) {
          x.Add(listBox1.Items[i]);
        }
      } else {
        x = listBox1.SelectedItems;
      }
      var dropResult = DoDragDrop(x, DragDropEffects.Move);
    }
  }
}

另一个例子,如果您需要知道在列表框中选择了哪些项目,使用 SHIFT 键创建扩展选择,即使您不需要启动 Draq&Drop 操作:

使用您在问题中提供的数据样本,列表

在示例中,List<int> (lbSelectedIndexes) 用于跟踪当前在列表框中选择的项目。此列表仅在使用 SHIFT 键执行选择时或在启动拖放操作后填充。这对于确定选择的类型很有用。

在所有其他情况下,List<int> 为空,SelectedItemsSelectedIndices 集合可用于确定当前选择的项目。

SystemInformation.DragSize 值还用于确定在按下左键的同时移动鼠标指针时是否应启动拖动操作。
当拖放操作开始时,新的 DataObject 将填充与当前选择相对应的列表框项目,无论选择是如何执行的。
DragDropEffects 设置为 DragDropEffects.Copy.


Point lbMouseDownPosition = Point.Empty;
List<int> lbSelectedIndexes = new List<int>();

private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
    var lb = sender as ListBox;
    lbMouseDownPosition = e.Location;
    lbSelectedIndexes = new List<int>();
    int idx = lb.IndexFromPoint(e.Location);
    if (ModifierKeys == Keys.Shift && idx != lb.SelectedIndex) {
        lbSelectedIndexes.AddRange(Enumerable.Range(
            Math.Min(idx, lb.SelectedIndex),
            Math.Abs((idx - lb.SelectedIndex)) + 1).ToArray());
    }
}

private void listBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left && 
        ((Math.Abs(e.X - lbMouseDownPosition.X) > SystemInformation.DragSize.Width) || 
         (Math.Abs(e.Y - lbMouseDownPosition.Y) > SystemInformation.DragSize.Height)))
    {
        var lb = sender as ListBox;
        DataObject obj = new DataObject();
        if (lbSelectedIndexes.Count == 0) {
            lbSelectedIndexes = lb.SelectedIndices.OfType<int>().ToList();
        }
        List<object> selection = lb.Items.OfType<object>().Where((item, idx) =>
            lbSelectedIndexes.IndexOf(idx) >= 0).ToList();
        obj.SetData(typeof(IList<ListItemsTest>), selection);

        lb.DoDragDrop(obj, DragDropEffects.Copy);
    }
}

要测试结果,请将另一个列表框(listBox2,此处)拖放到窗体上,将其 AlloDrop 属性 设置为 true 并订阅 DragEnterDragDrop 事件。

当鼠标指针进入第二个ListBox客户区时,如果e.Data.GetDataPresent()方法检测到拖动的对象包含List<ListItemsTest>,则触发DragDropEffects.Copy效果.

如果数据格式被接受,数据对象将转换List<ListItemsTest>——使用IDataObject.GetData()方法——并设置为DataSourcelistBox2

private void listBox2_DragDrop(object sender, DragEventArgs e)
{
    ListBox lb = sender as ListBox;
    if (e.Data != null && e.Data.GetDataPresent(typeof(IList<ListItemsTest>))) {
        lb.DisplayMember = "Name";
        lb.ValueMember = "ID";
        lb.DataSource = e.Data.GetData(typeof(IList<ListItemsTest>));
    }
}

private void listBox2_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(IList<ListItemsTest>))) {
        e.Effect = DragDropEffects.Copy;
    }
}

特此通知您,我找到了另一个解决方案。如果我在 MouseDown 事件中设置 Capture = false,项目将按预期工作,我们不需要进行手动选择。

例如:

void ZListBox_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        Capture = false;
    }
}

希望对您有所帮助!

我有一个 ListBox,我希望它可以拖放排序,并且还扩展了 multiselect。我遇到了与 OP 相同的问题,其中 selected 项目在使用 shift+click 以 select 项目时被轰炸。如果我添加一个检查以确保在启动 DoDragDrop 之前没有按下鼠标上的修改键,它就解决了问题。

(这是一个 VB.net 代码片段,但你明白了)

Private Sub lbClasses_MouseDown(sender As Object, e As MouseEventArgs) Handles lbClasses.MouseDown
    If e.Button = MouseButtons.Left AndAlso Control.ModifierKeys = 0 Then
        If Not IsNothing(lbClasses.SelectedItem) Then
            lbClasses.DoDragDrop(lbClasses.SelectedItem, DragDropEffects.Move)
        End If
    End If
End Sub