Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListBox MultiSelect Drag and Drop problem

I'm trying to do a drag and drop of multiple items between ListBox in windows forms. The problem I'm having is if I select multiple items holding the Shift key and try to drag and drop it without release the the key I get an error. Actually the SelectedIndices and SelectedItems just show 1 item, the one I clicked first, even though multiple items are highlighted in the ListBox.

I'm using 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);
        }
    }
}

It seems that if I don't release the left mouse button before I do "DoDragDrop", the items aren't selected and also if I try to get the SelectedIndices from the other ListBox, the Count is the number of "selected items", but when I try to navigate the list, I get a IndexOutOfRangeException.

enter image description here

Is there any work around for it?

Sample code to reproduce the issue: (To reproduce: 1- Select an item 2- Hold shift and click in another item, than without release shift and mouse button, drag this item (if you have a breakpoint inside the 'if', you'll see just 1 item on SelectedItems))

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; }
        }
    }
like image 423
Nandoviski Avatar asked Mar 05 '23 15:03

Nandoviski


2 Answers

Another example, in case you need to know what Items were selected in a ListBox, using the SHIFT key to create an extended selection, even if you don't need to initiate a Draq&Drop operation:

Using the data sample you provided in the question, a List.

In the example, a List<int> (lbSelectedIndexes) is used to keep track of what items are currently selected in the ListBox. This List is filled only when the selection is performed using the SHIFT key, or after a Drag&Drop operation is initiated. This can be useful to determine the type of selection.

In all other cases the List<int> is empty and the SelectedItems and SelectedIndices collections can be used to determine the items currently selected.

The SystemInformation.DragSize value is also used to determine if the drag operation should be initiated when the Mouse Pointer is moved while the Left Button is pressed.
When the Drag&Drop operation is started, a new DataObject is filled with ListBox Items corresponding to the current selection, no matter how the selection was performed.
The DragDropEffects is set to 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);
    }
}

To test the results, drop another ListBox (listBox2, here) on the Form, set its AlloDrop property to true and subscribe to the DragEnter and DragDrop events.

When the Mouse Pointer enters the second ListBox client area, the DragDropEffects.Copy effect is triggered if the e.Data.GetDataPresent() method detects that the dragged object contains a List<ListItemsTest>.

If the Data format is accepted, the Data Object is transformed back to a List<ListItemsTest> - using the IDataObject.GetData() method - and set as the DataSource of listBox2.

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;
    }
}
like image 80
Jimi Avatar answered Mar 10 '23 23:03

Jimi


I see the issue now. The funny thing is, pressing the Ctrl key to select items in the list works normal, but the Shift key doesn't. My solution would be to recreate your own SelectedItems collection:

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);
    }
  }
}
like image 40
LarsTech Avatar answered Mar 10 '23 23:03

LarsTech