Listbox problems

J

John Dann

[Originally posted to m.p.d.l.v.windowsforms.controls but seems to be
v low traffic there, so hope no-one minds if I repost here.]

I'm still struggling to find a way of reordering the items within the
same single listbox with drag and drop. I think I've got the drag
working but it's the drop code I can't figure out. What I have
currently is (with the listbox set to AllowDrop):

Sub MyListBox_MouseDown(args etc...)
DoDragDrop(MyListBox.SelectedItem, DragDropEffects.Move)
End Sub

Sub MyListBox_DragEnter(args etc...)
e.effect=DragDropEffects.Move
End sub

Sub MyListBox.DragDrop(args etc...)
'Here's where I'm stuck! Presumably I need:
MyListBox.Items.Insert(???, MyListBox.SelectedItem)
End Sub

Obviously I need some code in the last sub to insert the
currently-selected item (ie the one picked up by the Drag) immediately
before the item over which the mouse is currently hovering. But how
can I get the index of this item and then drop the selected item.
Possibly/probably I need to explicitly delete the source item?

Thanks for any help.
JGD
 
J

John Dann

OK, many thanks - I'm making some progress I think, but it's not
working yet. I seem to be able to drag an item (or at least the faint
grey dragging rectangle appears as the cursor), but the final step of
performing the insert isn't there yet. Here's the code I currently
have:

Private Sub lbxSelFields_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles lbxSelFields.MouseDown
Dim lb As ListBox = CType(sender, ListBox)
Dim pt As New Point(e.X, e.Y)
Dim index As Integer = lb.IndexFromPoint(pt)
If index >= 0 Then
lb.DoDragDrop(lb.Items(index).ToString(),
DragDropEffects.Move)
End If
End Sub

Private Sub lbxSelFields_DragEnter(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragEnter
If e.Data.GetDataPresent(GetType(String)) Then
e.Effect = DragDropEffects.Move
Else
e.Effect = DragDropEffects.None
End If
End Sub

Private Sub lbxSelFields_DragDrop(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragDrop
Dim lb As ListBox = CType(sender, ListBox)
Dim pt As New Point(e.X, e.Y)
Dim index As Integer = lb.IndexFromPoint(pt)
lbxSelFields.Items.Insert(index,
e.Data.GetData("System.string", True).ToString())
End Sub

This is cobbled together from 2 or 3 examples and so maybe I'm
incorrectly mixing some parameters/variables. I'm not sure why for
example I should make the declaration:

Dim lb As ListBox = CType(sender, ListBox)

when the control itself is a listbox. Could I just say in eg internal
line 3 of the MouseDown procedure:

Dim index As Integer = lbxSelFields.IndexFromPoint(pt)

But most importantly why wouldn't the lbxSelFields.Items.Insert(...)
line in the DragDrop event be working?

Thanks
JGD
 
J

John Dann

Thanks again - making progress but not there yet:

I can now get a sensible index value in the DragDrop event, but the
overall process isn't working yet. As a next step I'd like to make
sure that I'm actually picking up a value in the initial MouseDown
event.

What I was now hoping to do here was to pick up an index value of the
list item on which the mouse button was pressed at the start of the
drag operation.

Private Sub lbxSelFields_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles lbxSelFields.MouseDown
Dim pt As New Point(e.X, e.Y)
pt=lbxSelFields.PointToClient(pt)
Dim index As Integer = lbxSelFields.IndexFromPoint(pt)

' For investigation insert:
msgbox("Index = " & index.tostring)

If index >= 0 Then
lb.DoDragDrop(lb.Items(index).ToString(),
DragDropEffects.Move)
End If
End Sub

But the value of Index stays resolutely at -1. Looks like the final pt
values are not mapping to a valid item on the list. But I can't spot
the flaw. OR maybe I can't use this same process in the MouseDown as
in the DragDrop events?

JGD
 
R

Robin Tucker

You are just effecitvely changing the index of the source item, ie. it's
location in the set. To do this yes you should delete the source item. It
won't actually be "deleted", because of course your drag/drop still holds a
reference to it. In order to discover the correct placement position, you
need to use the IndexFromPoint method, passing in the mouse coords.

There is an MSDN example using drag/drop here:

http://msdn.microsoft.com/library/d...ndowsformslistboxclassindexfrompointtopic.asp
 
J

John Dann

Thanks again for your help and progress, progress, but not quite
success ye!. I'm now getting valid indices for both source and target
items, but still nothing seems to be happening at the final drop.

The relevant procedures are now as follows:

Private Sub lbxSelFields_MouseDown(ByVal sender As Object, ByVal e
As System.Windows.Forms.MouseEventArgs) Handles lbxSelFields.MouseDown
lbxSelFields.AllowDrop() = True
Dim pt As New Point(e.X, e.Y)
Dim itemindex As Integer = lbxSelFields.IndexFromPoint(pt)
If itemindex >= 0 Then
Me.Text = "Source Index = " & itemindex.ToString()

lbxSelFields.DoDragDrop(lbxSelFields.Items(itemindex).ToString(),
DragDropEffects.Move)
End If
End Sub

Private Sub lbxSelFields_DragEnter(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragEnter
If e.Data.GetDataPresent(GetType(String)) Then
e.Effect = DragDropEffects.Move
Else
e.Effect = DragDropEffects.None
End If
End Sub

Private Sub lbxSelFields_DragDrop(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragDrop
Dim pt As New Point(e.X, e.Y)
pt = lbxSelFields.PointToClient(pt)
Dim itemindex As Integer = lbxSelFields.IndexFromPoint(pt)
Me.Text = "Target Index = " & itemindex.ToString()
lbxSelFields.Items.Insert(itemindex,
e.Data.GetData("System.string", True).ToString())
End Sub

I'm left with three thoughts as to what the remaining problem might
be:

1. Some key line of code or setting has been omitted;

2. There's something not right with the way the item string that is to
be moved is formatted or configured. ie that is to be inserted via the
e.Data.GetData("System.string", True).ToString() string, which I must
admit I don't fully understand.

3. There's some particular problem or trick to do with the fact that I
want to drag within the same listbox rather than from one control to
another.

JGD
 
R

Robin Tucker

Check the index value to make sure it is valid firstly. Although I'm sure
you would get an indexoutofrange exception if it was something like -1.
Also, did you set the "AllowDrop" property of the list box?

You don't need to write, CType(sender, ListBox), it's just an alternative
way of looking at things. You can explicitly use your list box name if you
like in that method.
 
R

Robin Tucker

Ahhh sorry, also forgot to mention you need to convert the coords to client
relative coords first when dropping, as you get screen relative coords from
the DragDrop method, like this:


Private Sub ListBox1_DragDrop(ByVal sender As Object, ByVal e As
DragEventArgs) Handles ListBox1.DragDrop

Dim thePoint As New Point(e.X, e.Y)

thePoint = ListBox1.PointToClient(thePoint)

Dim index As Integer = ListBox1.IndexFromPoint(thePoint)

If index >= 0 Then
ListBox1.Items.Insert(index, e.Data.GetData("System.String",
True).ToString())
End If

End Sub
 
R

Robin Tucker

The thing is, when you get the MouseDown event, it's relative to the list,
so no need to "PointToClient" there.... But in the DragDrop event, the
PointToClient is required. Don't ask me why ;) So.....


Private Sub lbxSelFields_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles lbxSelFields.MouseDown

Dim index As Integer = lbxSelFields.IndexFromPoint(e.X, e.Y)

' For investigation insert:

msgbox("Index = " & index.tostring)

If index >= 0 Then
lb.DoDragDrop(lb.Items(index).ToString(), DragDropEffects.Move)
End If

End Sub
 
R

Robin Tucker

Okay, what the call to "GetData" does is ask the object for a format of type
"System.string" - as basically these objects implement an interface called
IDataObject and in theory, they can present their data to you in many
different formats (perhaps a table if dragging into Excel or Word for
instance, different formats according to what is being dragged). So, you
are asking the object if it can give you it's data in string format. If it
can, it will return it, otherwise it will return nothing. I have a feeling
if you try GetData("System.String") it will work as I think it's case
sensitive.
 
J

John Dann

I have a feeling
if you try GetData("System.String") it will work as I think it's case
sensitive.
Yes indeed, that was exactly the problem. I guess I should have let
the Intellisense write it rather than a human!

Almost there now, but one remaining problem which is how to delete the
original listbox item at its original position. I've been trying a
variety of approaches but none seems to work properly - they either
remove two items (maybe because an event is being fired twice?) or
none! It seems a little like other aspects of this particular problem
in that only exactly the right solution will work robustly.

I'm continuing to experiment, but if Robin or anyone can suggest the
best removal solution then that would be incredibly welcome. Really
there are two questions:

1. Should the removal of the original item at its pre-move position be
done in the MouseDown or DragDrop events?

2. What's the best removel method to use, by index, selected item or
what?

Thanks
JGD
 
J

John Dann

Do it in the drag drop event, before you insert the "dropped" item, that way
you know the item to delete still has the same index.

OK many thanks for that.

But sadly there's a sting in the tail still. While the drag/drop
procedure now works fine, another procedure relating to the same
listbox that responded to the listbox_DoubleClick event (to delete an
item) no longer works. I guess what may be happening is that any mouse
click on the listbox is raising a MouseDown event and maybe this
blocks out other event handlers?

So I'm left with another question, ie is there any way to make the
pre-Drag Mousedown procedure more selective or somehow to turn it on
and off?

For anyone that may look at this thread in future, the overall code
I've ended up with for moving items up and down a listbox with the
mouse is as follows:

' Form declarations
Dim PreMoveIndex as Integer

Private Sub lbxSelFields_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles lbxSelFields.MouseDown
Dim pt As New Point(e.X, e.Y)
PreMoveIndex = lbxSelFields.IndexFromPoint(pt)
If PreMoveIndex >= 0 Then

lbxSelFields.DoDragDrop(lbxSelFields.Items(PreMoveIndex).ToString(),
DragDropEffects.Move)
End If
End Sub

Private Sub lbxSelFields_DragEnter(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragEnter
If e.Data.GetDataPresent(GetType(String)) Then
e.Effect = DragDropEffects.Move
Else
e.Effect = DragDropEffects.None
End If
End Sub

Private Sub lbxSelFields_DragDrop(ByVal sender As Object, ByVal e
As System.Windows.Forms.DragEventArgs) Handles lbxSelFields.DragDrop
Dim pt As New Point(e.X, e.Y)
pt = lbxSelFields.PointToClient(pt)
Dim PostMoveIndex as Integer = lbxSelFields.IndexFromPoint(pt)
lbxSelFields.Items.RemoveAt(PreMoveIndex)
lbxSelFields.Items.Insert(PostMoveIndex,
e.Data.GetData("System.String", True).ToString())
End Sub

JGD
 
R

Robin Tucker

Do it in the drag drop event, before you insert the "dropped" item, that way
you know the item to delete still has the same index.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top