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)