Drag & Drop Unterstützung für die ListBox
Einträge einer ListBox einfach per Drag & Drop umsortieren
Drag & Drop findet man bei Windows und seinen Anwendungen an jeder Ecke, da werden Texte verschoben, Dateien kopiert und Listeneinträge umsortiert. Letzteres ist als praktisches Beispiel bei dem Windows- Explorer, genauer dem Dialog zum Anpassen der Symbolleisten zu finden. Auch wenn es auf den ersten Blick nicht so ausschaut, verbirgt sich hinter der Liste "Aktuelle Symbolleisten" eine ListBox, genauer eine DragListBox.
Um die VB ListBox zu einer DragListBox zu erweitern, bedarf es eines einzigen API Aufrufs, MakeDragList.
Allerdings möchte man auch etwas mit den Nachrichten anfangen, die von der DragListBox gesendet werden und
die den Drag Vorgang anzeigen. Dies geschieht mittels Subclassing. Die entsprechende, eindeutige Nachricht
wird durch die RegisterWindowMessage API Funktion ermittelt:
Public Function Attach(ByRef DragList As ListBox) As Boolean
Dim lRet As Long
Set mobj_DragList = DragList
lRet = MakeDragList(mobj_DragList.hWnd)
If (lRet <> 0) Then
DL_DRAGMESSAGE = RegisterWindowMessage(DRAGLISTMSGSTRING)
AttachMessage Me, mobj_DragList.Parent.hWnd, DL_DRAGMESSAGE
Attach = True
End If
End Function
In der Subclassing Empfängerprozedur können Sie nun auf die DragList- Nachricht "DL_DRAGMESSAGE" reagieren:
Private Function ISubclass_WindowProc _
(ByVal hWnd As Long, ByVal iMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Dim tDLI As DRAGLISTINFO
Select Case iMsg
Case DL_DRAGMESSAGE
Die DRAGLISTINFO Struktur enthält Informationen zum Status des Drag Vorgangs, der aktuellen Mausposition und den Handle der ListBox. Die Adresse dieser Struktur liefert der Parameter "lParam". Mithilfe der API Funktion CopyMemory wird der Inhalt in die Variable "tDLI" umkopiert:
CopyMemory tDLI, ByVal lParam, Len(tDLI)
Nun können Sie die einzelnen Notification Nachrichten auswerten, die Auskunft über den Status des Drag Vorgangs geben. Die Nachricht DL_BEGINDRAG zeigt an, dass der Drag Vorgang gestartet wurde. Hier können Sie den Index des Eintrags festhalten, den der Benutzer verschieben möchte. WindowProc muss True zurückgeben, damit der Drag Vorgang auch fortgesetzt wird:
Select Case tDLI.uNotification
Case DL_BEGINDRAG
mlng_Index = LBItemFromPt(tDLI.hWnd, tDLI.ptCursor.X, _
tDLI.ptCursor.Y, False)
ISubclass_WindowProc = True
Der laufende Drag Vorgang wird durch die Nachricht DL_DRAGGING signalisiert. Hier können Sie über die API Funktion DrawInsert die Einfügemarke zeichnen, die dem Benutzer die mögliche Position des Eintrags anzeigt. Zusätzlich wird der Cursortyp abhängig von der Mausposition verändert. Dies kann durch die Rückgabe einer DL_*CURSOR Konstante erfolgen. Da aber der Standardcursor wenig Aussagekraft hat, wurde der ListBox zuvor ein eigener, benutzerdefinierter Cursor über die MouseIcon- Eigenschaft zugewiesen (siehe Beispielprojekt), so dass dieser während des Drag Vorgangs nur noch über die MousePointer Eigenschaft aktiviert werden muss. Nur wenn sich der Cursor außerhalb der ListBox befindet, wird über die WindowProc DL_STOPCURSOR zurückgegeben:
Case DL_DRAGGING
lngIndex = LBItemFromPt(tDLI.hWnd, tDLI.ptCursor.X, _
tDLI.ptCursor.Y, True)
DrawInsert mobj_DragList.Parent.hWnd, mobj_DragList.hWnd, lngIndex
If (lngIndex <> -1) Then
mobj_DragList.MousePointer = vbCustom
Else
ISubclass_WindowProc = DL_STOPCURSOR
End If
DL_DROPPED zeigt an, dass der Eintrag fallen gelassen wurde. Hier kann jetzt der verschobene Eintrag gelöscht und an der neuen Position wieder eingefügt werden. Dies erledigt die Hilfsprozedur MoveItem:
Case DL_DROPPED
If LBItemFromPt(tDLI.hWnd, tDLI.ptCursor.X, _
tDLI.ptCursor.Y, True) <> mlng_Index Then
MoveItem LBItemFromPt(tDLI.hWnd, tDLI.ptCursor.X, _
tDLI.ptCursor.Y, True)
End If
mobj_DragList.MousePointer = vbDefault
mobj_DragList.Parent.Cls
Die Prozedur MoveItem:
Public Sub MoveItem(ByVal DestIndex As Integer)
Dim lngData As Long
Dim strCaption As String
Dim intIndex As Integer
With mobj_DragList
intIndex = .ListIndex
If intIndex >= 0 And DestIndex >= 0 Then
lngData = .ItemData(intIndex)
strCaption = .List(intIndex)
.AddItem strCaption, DestIndex
.ItemData(.NewIndex) = lngData
.ListIndex = DestIndex
If DestIndex < intIndex Then
.RemoveItem intIndex + 1
Else
.RemoveItem intIndex
End If
End If
End With
End Sub
Zu guter Letzt kann der Drag Vorgang auch vom Benutzer abgebrochen werden, was durch die Nachricht DL_CANCELDRAG angezeigt wird und hier nur dazu dient die Zeichenfläche des Form zu löschen und den Mauscursor der ListBox zurückzusetzen:
Case DL_CANCELDRAG
mobj_DragList.MousePointer = vbDefault
mobj_DragList.Parent.Cls
End Select
End Select
End Function
Das im Beispielprojekt enthaltene Klassenmodul cDragList kapselt das DragList API und kann ganz einfach in bestehende Projekte eingefügt werden. Neben den Deklarationen für die oben aufgeführten Codefragmente, finden sich hier auch eine HitTest Funktion, die den Index eines Listeneintrags unter der Cursor Position ermittelt und Methoden zum verschieben einzelner Listeneinträge.
Da die Anzeige der Einfügemarkierung immer einen Freiraum links neben der ListBox erfordert, der in
manchen Anwendungsfällen eventuell nicht gegeben ist, kann die Einfügemarkierung auch in Form einer
horizontalen Linie direkt in der ListBox erfolgen. Die Art der Einfügemarkierung kann über die Parameter
InsertIcon (Pfeil) und InserLine (Trennlinie) der Attach Methode festgelegt werden und jederzeit über die
gleichnamigen Eigenschaften der cDragList Klasse geändert werden.
- - Einfügemarkierung durch Trennlinie
- - Auswahl der Einfügemarkierung
- cDragList Klassenmodul und Beispielprojekt [VB5]
(draglist5.zip - ca. 6 KB)
Erfordert Subclassing & Timer Komponente (ssubtmr.dll) - cDragList Klassenmodul und Beispielprojekt [VB6]
(draglist5.zip - ca. 6 KB)
Erfordert VB6 Subclassing & Timer Komponente (ssubtmr6.dll)