RSS
StartseiteKnowledge LibraryTop 10Impressum

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.