Unexplained behaviour of Listbox selected items

johnbirt

New member
Joined
Aug 11, 2009
Messages
2
Programming Experience
5-10
A very puzzling issue occurred when working on an application which involved Drag & Drop between listboxes with Multiextended selection. The main issue is that any attempt to a drag a selection from one list immediately deselects all but the currently selected item. There are workarounds and a number of attempts of varying degrees of sophistication available on the Internet but none giving exactly what I wanted. In the course of reinventing the wheel I came across a very puzzling issue which took me ages to isolate and I still don't understand it.

I have produced very simple bare bones code which isolates the problem.

Briefly a listbox is produced with all 4 items selected. Click the button and a msgbox confirmation that there are

indeed 4 items selected is produced. Then a standard iteration through the selected items outputs a string with

all 4 items joined. Exactly as it should be.

If you now manually select all 4 items and then use the button, you again get confirmation there are 4 items selected but the joined string now only produces 1 item! This I simply cannot fathom.

The culprit is clearly the parameter lbox.text of the DODragDrop call. It shouldn't really matter what is passed

there as far as selected items are concerned but it clearly does. If you change it to "Anything you like" or

lbox.items(0) which is what lbox.text amounts to anyway, if you select all 4, there is no problem. Go figure!

I don't really need a fix for this - don't do it this way is the obvious advice I guess!. However it is really annoying that I can't figure why such a seemingly innocuous setting can produce such a weird outcome. It remains in my mind that I haven't understood something well enough.

Maybe a VB.Net guru can throw some light on it. Am I missing something really obvious? If I'm not, how reliable is VB2008 interrupt programming? In trying to fathom this from a far more complex situation (many hours of debugging later) I have found some very unpredictable outcomes with no obvious explanation. Non-firing events etc.. Have not got Mouseclick to work reliably for instance.

I am using VS2008 Enterprise and have Coderush/Refactor installed on a Vista Ultimate machine for what its worth. Don't think any of that is an issue though.


Public Class Form1

Private Sub Lbox_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles lbox.MouseDown
REM Replace lbox.text as first param to DODragDrop by "Anything you like" and there is no problem!
lbox.DoDragDrop(lbox.Text, DragDropEffects.Copy Or DragDropEffects.Move)

Debug.Print(Str(lbox.SelectedItems.Count))
Debug.Print(lbox.Text)


End Sub



Public WithEvents lbox As ListBox = New ListBox

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Controls.Add(lbox)
lbox.Location = New System.Drawing.Point(50, 50)
lbox.Show()
lbox.Items.Add("apples")
lbox.Items.Add("peaches")
lbox.Items.Add("oranges")
lbox.Items.Add("bananas")
lbox.SelectionMode = SelectionMode.MultiExtended
For i As Integer = 0 To lbox.Items.Count - 1
lbox.SelectedIndices.Add(i)
Next

End Sub
 
MouseDown conflicts with how items are selected with mouse, this is quite obvious IMO and not unexpected. One solution is to cache the indexes selected and use the MouseMove event to initiate the drag operation. Since now the selection is lost again (*MouseDown*-MouseMove-MouseUp) it can be reselected with the cached indexes afterwards. This solution is derived from article CodeProject: Drag-and-Drop ListBox which I didn't read too much of, so there could be more to it. Here the sample code I tested with:
VB.NET:
Private drop() As Integer

Private Sub SourceListBox_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles SourceListBox.SelectedIndexChanged
    drop = Me.SourceListBox.SelectedIndices.Cast(Of Integer).ToArray
End Sub

Private Sub SourceListBox_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles SourceListBox.MouseMove
    If e.Button = Windows.Forms.MouseButtons.Left Then
        SourceListBox.DoDragDrop(drop, DragDropEffects.Copy)
    End If
End Sub

Private Sub TargetListBox_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles TargetListBox.DragEnter
    If e.Data.GetDataPresent(GetType(Integer())) Then
        e.Effect = DragDropEffects.All
    End If
End Sub

Private Sub TargetListBox_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles TargetListBox.DragDrop
    Me.TargetListBox.Items.Clear()
    Dim idx() As Integer = CType(e.Data.GetData(GetType(Integer())), Integer())
    For Each ix In idx
        Me.SourceListBox.SelectedIndices.Add(ix)
        Me.TargetListBox.Items.Add(Me.SourceListBox.Items(ix))
    Next
End Sub
What you said about listbox.Text I didn't understand. "Non-firing events" could be a result of the blocking dragdrop operation or other thread blocking code, hard to say.
 
Thanks for the reply. I was aware of the link you provided for listbox ddrop code. In fact I looked at that (very nice) C# code a while ago and have rewritten it for VB.Net and simplified the visual cue aspect of it. It certainly does the job nicely.

However, maybe I didn't explain things well enough. Agreed, mousemove does change selection and it is certainly expected behaviour and is the problem the above code solves by saving indices. However the snippet I provided was to expose some peculiar behaviour not to look for a fix which I have via the code you recommended.

If you populate a multiselect listbox and select items in it and then click away from the list say on a button or indeed another listbox or whatever, the originally selected items still remain selected as LISTBOX mousedown is not run by such action and the items are shown still to be visually selected.

The snippet I provided was to expose an anomaly and contradiction that appeared during research of listbox issues. First, 4 items are selected and are confirmed to be so both visually and after clicking a button, which shows the items as still selected. They are concatenated in code to give expected output - no problem. The user now selects the same 4 items manually (CTRL-click, SHIFT click etc) and then clicks the button. The items remain visually selected and therefore to the observer selected! Code then also shows the 4 items to be selected at least via a COUNT but the output is incorrect showing just 1 item selected. Why the inconsistency? is what I was trying to ask.

Before I came across the code you referred to and I eventually adopted, I came across a number of articles which used listbox.text as the 1st passed parameter to dragdrop - presumably a legacy from a single select scenario, although adapted in some cases reasonably successfully for multiselect. This for a multiselect listbox I guess will be the first selected item but no guarantee. It shouldn't make any difference what it is, as long as it is some text. It does matter however. It is this dragdrop call which seems to mess up. As I said if you alter that paramater to say a simple text string like "Parameter" the code snippet works exactly as it should do, giving correct counting and output of the selected items.

The essential point I was trying to make is that there is an inconsistency. 4 items are shown selected, a code count confirms it at 4, but the output is not 4! As far as I can see, nothing whatever to do with mousedown in this case. The call to mousedown is only invoked in the multiselection process and surely code should reflect what the user can actually see and what is partially confirmed by counting from code.

As you point out the mousemove interrupt is the best way to initiate the dragdrop. I was just interested to see if anyone could explain the inconsistency other than to say that mousedown is unpredictable in a multiselect scenario. IMHO I don't think that is the issue here anyway.

As for the "non firing" - difficult to give a simple account. I have workedaround/solved most issues, but it does cause me to lose confidence when counting and visual inspection doesn't match expected output.

Thanks again.
John
 
The snippet I provided was to expose an anomaly and contradiction that appeared during research of listbox issues. First, 4 items are selected and are confirmed to be so both visually and after clicking a button, which shows the items as still selected. They are concatenated in code to give expected output - no problem. The user now selects the same 4 items manually (CTRL-click, SHIFT click etc) and then clicks the button. The items remain visually selected and therefore to the observer selected! Code then also shows the 4 items to be selected at least via a COUNT but the output is incorrect showing just 1 item selected. Why the inconsistency? is what I was trying to ask.
I think what you are trying to get through is that visually the selection is presented before it is reflected in the SelectedItems property. And that whether DoDragDrop is called affects the outcome. All this can be analysed and explained. So when does the SelectedItems actually change? What is the set of events that occur and in which order do they happen? These are the questions you have to look into to understand the control and its event model.

Below is a sample event log that just inspects the state (no DoDragDrop call), one item was initially selected, then I pressed Shift and expanded the selection to three items. If you MouseDown (and hold) you see the selection visually, the first two log lines displays in output window. When releasing the mouse button the last to log lines appear.
(MouseDown, items is visually selected.)
MouseDown, SelectedItems.Count: 1
<...I kept the button pressed for a while here...>
SelectedIndexChanged, SelectedItems.Count: 3
MouseUp, SelectedItems.Count: 3
The MouseClick event is not relevant for this control, it is marked 'Infrastructure'. Now if you put a call to DoDragDrop (using any parameters really) into MouseDown before checking SelectedItems you get this:
(MouseDown, DoDragDrop was called)
(MouseDown, items is visually selected.)
MouseDown, SelectedItems.Count: 3
As you see no SelectedIndexChanged event or MouseUp event was raised, when DoDragDrop was initiated the control was released to preform the drag operation. Since DoDragDrop is a blocking call that returns when the drag operation is finished the SelectedItems has now been updated.
which used listbox.text as the 1st passed parameter to dragdrop - presumably a legacy from a single select scenario, although adapted in some cases reasonably successfully for multiselect. This for a multiselect listbox I guess will be the first selected item but no guarantee. It shouldn't make any difference what it is, as long as it is some text. It does matter however. It is this dragdrop call which seems to mess up. As I said if you alter that paramater to say a simple text string like "Parameter" the code snippet works exactly as it should do, giving correct counting and output of the selected items.
help said:
If the SelectionMode property of the ListBox is set to SelectionMode.MultiExtended, this property returns the text of the first selected item.
The behaviour of ListBox.Text property is well documented, but I don't actually see how it has anything to do with neither multi-selection nor dragdrop. I tried the code you posted, but can't see any difference also. I think the problems you were experiencing is explained by now.

That performing a drag operation is something that interrupts the regular event flow is something I knew from previous investigations in events. I don't think this is actually documented, but it kind of makes sense, a different set of events takes place during such operation that involves all dragdrop aware controls.
 
Back
Top