Drag and Drop mit TreeView und ListView

Wenn Sie mit ListView- und TreeView-Steuerelementen arbeiten, sind Drag-and-Drop-Operationen ein tolles Hilfsmittel, um Daten zwischen den Steuerelementen hin- und herzuschieben. In der Anwendung aiuTimeplanner gibt es gleich zwei ListView- und ein TreeView-Steuerelement – genügend Möglichkeiten also, um Drag and Drop einmal anhand eines Praxisbeispiels zu demonstrieren. Dabei sollen Aufgaben aus dem TreeView in beide ListViews verschoben werden sowie von ListView zu ListView – und das Verschieben und Kopieren von Einträgen innerhalb eines ListView-Steuerelements ist ebenfalls gefragt.

Das Hin- und Herschieben von Elementen zwischen verschiedenen ListView- und TreeView-Einträgen ist prinzipiell nicht schwierig. Beim Start des Drag-and-Drop-Vorgangs wird immer das OLEStartDrag-Ereignis ausgelöst, dem Sie mit dem Parameter Data Informationen über die zu verschiebenden Daten mitgeben. Beim Überfahren des Drop-Ziels und beim Fallenlassen werden die beiden Ereignisprozeduren OLEDragOver und OLEDragDrop ausgelöst, denen Sie wiederum über den gleichen Parameter die beim Start übergebenen Daten entnehmen können.

Sie starten also beispielsweise von einem ListView-Steuerelement einen Drag-and-Drop-Vorgang und speisen den Key-Wert des gezogenen Elements in den Parameter Data ein, beispielsweise t123. Dann sollte t einen Hinweis auf das Steuerelement geben, von dem aus der Vorgang gestartet wurde, und 123 entspricht dem Primärschlüsselwert des Datensatzes, der per Drag and Drop bewegt werden soll.

Im Beispiel, das im Beitrag Tagesablauf verwalten mit dem aiuTimeplanner (www.access-im-unternehmen.de/839) genauer erläutert wird, gibt es drei Steuerelemente, die Drag-and-Drop-Operationen unterstützen:

  • das ListView-Steuerelement lvwDailyTasks,
  • das TreeView-Steuerelement tvwTasks und
  • das ListView-Steuerelement lvwTimeplan.

Es gibt gleich eine ganze Reihe möglicher Drag-and-Drop-Operationen, die durch die Pfeile in Bild 1 verdeutlicht werden:

pic001.png

Bild 1: Mögliche Drag-and-Drop-Vorgänge in der Beispielanwendung

  • Vom TreeView zur Tagesliste: Stellt die Eigenschaft HeuteErledigen auf True ein und fügt den Eintrag zur Tagesliste hinzu.
  • Vom TreeView zu einem anderen TreeView-Element: Ordnet die Aufgabe einer anderen Aufgabe unter.
  • Vom TreeView an eine leere Stelle im TreeView: Löscht den Eintrag nach Rückfrage. Bei gedrückter Umschalttaste wird ohne Rückfrage gelöscht.
  • Vom TreeView zur Tätigkeitsliste: Fügt eine neue Tätigkeit auf Basis der gezogenen Aufgabe hinzu.
  • Von der Tagesliste zur Tätigkeitsliste: Fügt eine neue Tätigkeit auf Basis der gezogenen Aufgabe hinzu.
  • Von der Tagesliste in einen leeren Bereich der Tagesliste: Stellt die Eigenschaft HeuteErledigen auf False ein und entfernt den Eintrag aus der Liste.
  • Von der Tätigkeitsliste an eine andere Stelle der Tätigkeitsliste: Verschiebt die Tätigkeit.
  • Von der Tätigkeitsliste an eine andere Stelle der Tätigkeitsliste, diesmal mit gedrückter Strg-Taste: Kopiert die Tätigkeit.

Gezogene Elemente identifizieren

Die Elemente können über den Wert der Eigenschaft Key dem jeweiligen Steuerelement zugeordnet werden, also beispielsweise d für lvwDailyTasks, t für tvwTasks und p für lvwTimeplan. So kann man über den Wert des Parameters Data jeweils am ersten Buchstaben erkennen, von welchem Steuerelement aus der Drag-and-Drop-Vorgang gestartet wurde, die folgenden Zahlen liefern die ID des Elements aus der jeweiligen Tabelle.

Dies wird natürlich etwas schwieriger, wenn Sie etwa in einem TreeView-Steuerelement die Daten aus mehreren Tabellen speichern und beispielsweise Kunden mit dem Präfix k und Artikel mit dem Präfix a versehen.

Dies erhöht jedoch nur die Anzahl der möglichen Kombinationen und damit die verschiedenen Arten, einen Drag-and-Drop-Vorgang anhand von Quelle und Ziel zu verarbeiten. Sie können nach wie vor mit einem Präfix-Buchstaben arbeiten, der die Art der Daten beschreibt, und daran den Primärschlüsselwert des entsprechenden Elements anhängen.

Starten eines Drag-and-Drop-Vorgangs

Der Start eines Drag-and-Drop-Vorgangs beginnt immer mit dem Herunterdrücken der Maustaste auf einem Element eines ListView- oder TreeView-Steuerelements und dem Bewegen des Mauszeigers bei gedrückter Maustaste. Dies ist der Moment, in dem Sie Informationen über das gezogene Element aufnehmen und speichern müssen.

Das passende Ereignis lautet OLEStartDrag und kann über die beiden Kombinationsfelder des Klassenmoduls des Formulars hinzugefügt werden (s. Bild 2).

pic002.png

Bild 2: Hinzufügen des OLEStartDrag-Ereignisses

Damit dieses Ereignis und die beiden im Verlauf dieses Beitrags vorgestellten Ereignisse nach Wunsch ausgelöst werden, müssen Sie für die entsprechenden Objektvariablen außerdem noch je zwei Eigenschaften einstellen – dies geschieht in Konfigurationsprozeduren, die im Beitrag Tagesablauf verwalten mit aiuTimeplanner (www.access-im-unternehmen.de/839) beschrieben werden:

objTasks.OLEDragMode = ccOLEDragAutomatic
objTasks.OLEDropMode = ccOLEDropManual

Die drei Ereignisprozeduren objTasks_OLEStartDrag, objDailyTasks_OLEStartDrag und objTimeplan_OLEStartDrag werden dann wie in Listing 1 angelegt und wie die Prozedur objTasks_OLEStartDrag gefüllt.

Listing 1: Diese Prozeduren werden beim Starten eines Drag-and-Drop-Vorgangs ausgelöst.

Private Sub objTasks_OLEStartDrag(Data As MSComctlLib.DataObject, AllowedEffects As Long)
    Dim objNode As MSComctlLib.Node
    Set objNode = objTasks.SelectedItem
    If objNode Is Nothing Then Exit Sub
    Data.Clear
    Data.SetData objNode.Key, ccCFText
End Sub
Private Sub objTimeplan_OLEStartDrag(Data As MSComctlLib.DataObject, AllowedEffects As Long)
    Dim objListitem As MSComctlLib.ListItem
    Set objListitem = objDailyTasks.SelectedItem
    If objListitem Is Nothing Then Exit Sub
    Data.Clear
    Data.SetData objListitem.Key, ccCFText
End Sub
Private Sub objDailyTasks_OLEStartDrag(Data As MSComctlLib.DataObject, AllowedEffects As Long)
    ... wie objTimeplan_OLEStartDrag
End Sub

Die Prozeduren ermitteln jeweils das beim Start des Drag-Vorgangs markierte Element. Ist kein Element markiert, wird die Prozedur beendet. Anderenfalls wird der Parameter Data erst geleert und dann mit dem Wert der Key-Eigenschaft des betroffenen Elements gefüllt, also beispielsweise t123 beim Ziehen eines Elements des Aufgabenbaums, d234 beim Ziehen eines Elements der Tagesliste oder p345 beim Ziehen eines Elements der Tätigkeitenliste.

Damit enthält der Parameter Data genaue Informationen über den Ursprung der Drag-and-Drop-Operation und über den Primärschlüsselwert des gezogenen Elements.

Ziel beim Überfahren markieren

Damit der Benutzer erkennt, wo er ein Element fallenlassen kann und wo nicht, markieren wir potenzielle Ziele beim Überfahren. Dies erledigen die Ereignisprozeduren objTasks_OLEDragOver, objDailyTasks_OLEDragOver und objTimeplan_OLEDragOver. Die Prozeduren sind für alle drei Steuerelemente sehr ähnlich aufgebaut, sodass wir nur eines in Listing 2 komplett abbilden.

Listing 2: Ereignisprozedur beim Überfahren des Zielelements

Private Sub objTasks_OLEDragOver(Data As MSComctlLib.DataObject, Effect As Long, _
        Button As Integer, Shift As Integer, x As Single, y As Single, State As Integer)
    Dim strData As String
    Dim strDatatype As String
    If Data.GetFormat(ccCFText) = False Then Exit Sub
    strData = Data.GetData(ccCFText)
    strDatatype = Left(strData, 1)
    Select Case strDatatype
        Case "t"
            Set objTasks.DropHighlight = objTasks.HitTest(x, y)
    End Select
End Sub

Dort wird zunächst das Format des gezogenen Elements geprüft, das den Wert ccCFText aufweisen sollte – anderenfalls wird die Prozedur abgebrochen. Ist das Format korrekt, liest die Prozedur den Inhalt des Parameters Data in eine String-Variable namens strData ein, also beispielsweise t123.

Daraus entnimmt sie den ersten Buchstaben, der einen Hinweis auf die Herkunft des gezogenen Elements liefert. t steht wie erwähnt für objTasks, p für objTimeplan und d für objDailyTasks. Anhand dieses Wertes entscheidet die Prozedur, ob ein Element beim Überfahren als Ziel markiert werden soll.

Dies geschieht, indem mit der HitTest-Funktion des Zielsteuerelements ein Verweis auf das Zielelement ermittelt wird.

Dieser wird anschließend der DropHighlight-Eigenschaft des jeweiligen Steuerelements zugeordnet, was dazu führt, dass das Zielelement durch einen blauen Hintergrund markiert wird.

Für das Objekt objDailyTasks sieht die Prozedur genauso aus wie die für objTasks. Die Prozedur objTimeplan_OLEDragOver enthält jedoch eine andere Bedingung zur Prüfung des Datentyps des gezogenen Elements:

Select Case strDatatype
    Case "t", "d", "p"
        Set objTimeplan.DropHighlight = objTimeplan.HitTest(x, y)
End Select

Das bedeutet, dass ein Element nicht nur beim Ziehen einer Aufgabe aus dem TreeView-Objekt objTasks als Ziel angeboten wird, sondern auch beim Ziehen von Elementen aus der Tagesliste und aus der Tätigkeitsübersicht selbst.

An dieser Stelle könnten Sie noch verschiedene Symbole einblenden, die einen Hinweis auf die geplante Aktion liefern. Dazu stellen Sie den Parameter Effect auf einen der folgenden Werte ein:

  • ccOLEDropEffectCopy
  • ccOLEDropEffectMove
  • ccOLEDropEffectNone
  • ccOLEDropEffectScroll

Element fallenlassen

Kommen wir zu den wirklich interessanten Aktionen – den Datenoperationen und den änderungen der Darstellung beim Fallenlassen eines Elements auf einem erlaubten Ziel.

Als Erstes sehen wir uns das Fallenlassen eines Elements auf dem TreeView-Objekt des Formulars an. Dies löst die Ereignisprozedur objTasks_OLEDragDrop aus (s. Listing 3). Diese Prozedur enthält ebenfalls einen Parameter namens Data, der den Kennbuchstaben für den entsprechenden Elementtyp und den Primärschlüsselwert liefert.

Listing 3: Beenden des Drag-and-Drop-Vorgangs

Private Sub objTasks_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, _
        Button As Integer, Shift As Integer, x As Single, y As Single)
    ''... Deklaration
    strData = Data.GetData(ccCFText)
    strDatatype = Left(strData, 1)
    lngKeyDrag = Mid(strData, 2)
    Set objNodeDrop = objTasks.HitTest(x, y)
    Select Case strDatatype
    Case "t"
        If objNodeDrop Is Nothing Then
            AufgabeLoeschen lngKeyDrag, -Shift
        Else
            strKeyDrop = objNodeDrop.Key
            lngKeydrop = Mid(strKeyDrop, 2)
            Set objNodeDrag = objTasks.Nodes(strData)
            If strKeyDrop = strKeyDrag Then
                Exit Sub
            End If
            Select Case Shift
                Case 0 ''verschieben
                    AufgabeVerschieben lngKeydrop, lngKeyDrag
                Case 1
                    AufgabenZusammenfuehren Mid(objNodeDrag.Key, 2), Mid(objNodeDrop.Key, 2)
            End Select
            FillTree
        End If
    End Select
End Sub

Dieser Wert wird zunächst ausgelesen. Die einzelnen Bestandteile landen in den beiden Variablen strDatatype und lngKeyDrag. Das TreeView-Steuerelement kann nur Elemente verarbeiten, die auch von dort aus gezogen wurden. Der Wert der Variablen strDatatype muss also t lauten, anderenfalls geschieht nichts.

Danach unterscheidet die Prozedur zwei Fälle, die davon abhängen, ob die Variable objNodeDrop einen Wert enthält oder nicht. Die Variable wird mit einem Verweis auf das TreeView-Element gefüllt, auf dem das Drag-and-Drop-Element fallengelassen wurde. Hat der Benutzer also ein Element auf ein anderes Element gezogen, wird dieses mit objNodeDrop referenziert. Hat er hingegen ein Element an eine freie Stelle innerhalb des TreeView-Steuerelements gezogen, bleibt objNodeDrop leer.

In diesem Fall wird das gezogene Element gelöscht. Es ist nur noch offen, ob es direkt gelöscht werden soll oder ob zuvor noch eine Rückfrage erfolgen soll. Wenn der Benutzer beim Ziehen des Elements in den leeren Raum die Umschalttaste gedrückt hat, wird das Element direkt gelöscht, ohne Betätigen einer Taste erscheint noch eine Rückfrage.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar