ListBox 多选拖放问题

4
我正在尝试在Windows表单中的ListBox之间拖放多个项目。我的问题是,如果我按住Shift键选择多个项目并尝试拖放而不释放该键,则会出现错误。实际上,SelectedIndices和SelectedItems只显示1个项目,即我首先点击的那个项目,即使多个项目在ListBox中被突出显示。
我使用的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”之前我不释放左键,则项不会被选中,而且如果我尝试从另一个ListBox获取SelectedIndices,则Count是“所选项”的数量,但是当我尝试遍历列表时,会出现IndexOutOfRangeException。

enter image description here

有没有解决这个问题的方法?

复现问题的示例代码: (要复现: 1- 选择一个项目 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; }
        }
    }

我已经添加了一张图片。 - Nandoviski
我编辑了已经存在的代码。 - Nandoviski
它在自己的ListBox中,就是我选择项目的那个,此时我还没有开始拖放。 - Nandoviski
你可以重写组件,也可以在前面使用Listbox1,结果是一样的。我添加了一个示例代码。 - Nandoviski
复现步骤:
  • 选择一个项目
  • 按住Shift键并单击另一个项目,然后不释放Shift和鼠标按钮,拖动此项目(如果您在“if”内部设置了断点,则在SelectedItems上只会看到1个项目)
- Nandoviski
显示剩余3条评论
4个回答

3

另一个例子,假如你需要知道在ListBox中选择了哪些项目,使用SHIFT键创建扩展选择(即使你不需要启动Draq&Drop操作):

使用你在问题中提供的数据样本,一个List

在这个例子中,使用一个List<int> (lbSelectedIndexes) 来跟踪当前在ListBox中被选中的项目。只有当使用SHIFT键进行选择或启动Drag&Drop操作后,该List才会被填充。这可以用来确定选择的类型。

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

SystemInformation.DragSize的值也用于确定当鼠标指针在按下左键时移动时是否应启动拖放操作。
当开始拖放操作时,一个新的DataObject将填充与当前选择对应的ListBox项,无论选择是如何执行的。
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);
    }
}

为了测试结果,在窗体上再拖放一个ListBox(这里是listBox2),将它的AlloDrop属性设置为true,并订阅DragEnter和DragDrop事件。
当鼠标指针进入第二个ListBox客户端区域时,如果e.Data.GetDataPresent()方法检测到拖动对象包含List,则触发DragDropEffects.Copy效果。
如果数据格式被接受,使用IDataObject.GetData()方法将数据对象“转换”回List,并将其设置为listBox2的DataSource。
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;
    }
}

2

我想告诉你我找到了另一个解决方案。如果在MouseDown事件中设置Capture = false,则Items将按预期工作,我们不需要进行手动选择。

例如:

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

最初的回答
希望对您有所帮助!

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

我现在明白了问题所在。有趣的是,按Ctrl键选择列表中的项目可以正常工作,但按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);
    }
  }
}

0
我有一个列表框,想实现拖放排序和扩展多选。我遇到了与OP相同的问题,使用shift+click选择项目时导致选择的项目崩溃。如果我添加一个检查,在鼠标按下时确保没有按下修饰键再启动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

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