Drag and Drop mit dem ListView-Steuerelement

Zusammenfassung

Erfahren Sie, wie Sie mit Drag and Drop im ListView m:n-Beziehungen verwalten und individuelle Reihenfolgen anpassen können.

Techniken

ListView-Steuerelement, VBA

Voraussetzungen

Access 2000 und höher

Beispieldateien

ListView.mdb, MSCOMCTL.msi

André Minhorst, Duisburg

Listenfelder sind eine schöne Einrichtung. Wie schade, dass sie kein Drag and Drop erlauben. Aber vielleicht haben Sie Glück und setzen Access 2002 oder höher ein oder besitzen ein passendes Entwicklerpaket: Dann können Sie nämlich das ListView-Steuerelement verwenden und mit diesem die gewünschte Drag and Drop-Funktion realisieren.

Beispiele für zwei Listenfelder, zwischen denen Daten hin und her verschoben werden sollen, gibt es wie Sand am Meer. Oft handelt es sich dabei um die Abbildung von m:n-Beziehungen. Warum also nicht mal wieder das gute alte Verteiler-Beispiel aufgreifen Hier geht es darum, für eine Publikation eine Reihe Empfänger aus den vorhandenen Kontakten auszuwählen.

Das Beispiel basiert auf drei Tabellen: Die Tabelle tblPublikationen enthält die zu veröffentlichenden Werke, die Tabelle tblEmpfaenger die Adressen und die Tabelle tblVerteiler verknüpft die Einträge der einen mit denen der anderen Tabelle. Im Beziehungsfenster sieht das wie in Bild 1 aus.

Bild 1: Datenmodell der Beispieldatenbank

Das Formular soll in zwei ListView-Steuerelementen die Empfänger und die Nicht-Empfänger der Publikation anzeigen (siehe Bild 2).

Bild 2: So soll das Formular zum Hin- und Herschieben von Empfängern aussehen.

Quellcode 1: Diese Routine füllt eine Liste mit den Daten einer Tabelle

Private Sub ListeAktualisieren(objListView As ListView, strSQL As String)
    Dim db As DAO.Database
    Dim rst As DAO.Recordset
    Set db = CurrentDb
    Set rst = db.OpenRecordset(strSQL, dbOpenDynaset)
    objListView.ListItems.Clear
    Do While Not rst.EOF
        objListView.ListItems.Add , "a" & rst(0), rst(1)
        rst.MoveNext
    Loop
    rst.Close
    Set rst = Nothing
    Set db = Nothing
End Sub

Quellcode 2: Aufruf der Routine zum Füllen der beiden ListView-Steuerelemente

Private Sub Form_Current()
    Dim strSQLEmpfaenger As String
    Dim strSQLKeinEmpfaenger As String
    If Not IsNull(Me.Publikation) Then
        strSQLEmpfaenger = "SELECT tblEmpfaenger.EmpfaengerID, tblEmpfaenger.Empfaenger " _            & "FROM tblEmpfaenger INNER JOIN tblVerteiler ON tblEmpfaenger.EmpfaengerID = " _            & " tblVerteiler.EmpfaengerID WHERE tblVerteiler.PublikationID = " _            & Me.PublikationID & " ORDER BY tblEmpfaenger.Empfaenger"
        strSQLKeinEmpfaenger = "SELECT tblEmpfaenger.EmpfaengerID, tblEmpfaenger.Empfaenger " _            & "FROM tblEmpfaenger WHERE tblEmpfaenger.EmpfaengerID NOT IN " _            & "(SELECT tblEmpfaenger.EmpfaengerID FROM tblEmpfaenger INNER JOIN tblVerteiler " _            & "ON tblEmpfaenger.EmpfaengerID = tblVerteiler.EmpfaengerID WHERE " _            & " tblVerteiler.PublikationID = " & Me.PublikationID _            & ") ORDER BY tblEmpfaenger.Empfaenger"
        ListeAktualisieren Me.lvwZugeordnet.Object, strSQLEmpfaenger
        ListeAktualisieren Me.lvwNichtZugeordnet.Object, strSQLKeinEmpfaenger
    End If
End Sub

Bevor überhaupt irgendein Eintrag von links nach rechts und umgekehrt gezogen werden kann, müssen erst einmal überhaupt Einträge da sein – dementsprechend folgt nun die Beschreibung des Aufbaus des Formulars.

Das Formular selbst ist an die Tabelle tblPublikationen gebunden. Bei jedem Wechsel des im Formular angezeigten Publikations-Datensatzes sollen auch die beiden ListViews aktualisiert werden. Die Routine wird daher logischerweise durch die Ereigniseigenschaft Beim Anzeigen aufgerufen.

Die beiden ListViews sind fast identisch aufgebaut, was sich auch auf das Füllen mit Daten auswirkt: Die dazu benötigte Routine sieht für beide Steuerelemente fast identisch aus. So identisch, dass man daraus eine einzige parametrisierte Routine machen kann. Als Parameter übergibt die aufrufende Prozedur nur einen Verweis auf das jeweilige Listenfeld sowie den SQL-Ausdruck, der die einzufügenden Datensätze festlegt.

Die Routine ListeAktualisieren ist für das Anlegen der Elemente der ListViews verantwortlich (s. Quellcode 1). Sie öffnet eine auf der übergebenen SQL-Anweisung basierende Datensatzgruppe und durchläuft diese. Dabei fügt sie je Datensatz ein Element zum angegebenen ListView hinzu. Das erste im SQL-Ausdruck angegebene Feld dient als Teil der Key-Eigenschaft des Elements, das zweite als angezeigter Text. Die beim Anzeigen des aktuellen Datensatzes im Formular ausgelöste Prozedur Form_Current ruft diese Routine zweimal mit verschiedenen Parametern auf (s. Quellcode 2).

Der erste dabei verwendete SQL-Ausdruck ermittelt alle Datensätze der verknüpften Tabellen tblEmpfaenger und tblVerteiler, bei denen die in der Tabelle tblVerteiler angegebene PublikationID mit der aktuell im Formular angezeigten übereinstimmt.

Quellcode 3: Der Drag-and-Drop-Vorgang startet im ListView lvwNichtZugeordnet …

Private Sub lvwNichtZugeordnet_OLEStartDrag(Data As Object, AllowedEffects As Long)
    Dim objListItem As ListItem
    Dim strData As String
    Dim strDataItems() As String
    For Each objListItem In lvwNichtZugeordnet.ListItems
        If objListItem.Selected = True Then
            strData = strData & objListItem.Key & "¦" & objListItem.Text & ";"
        End If
    Next
    Data.Clear
    Data.SetData strData, ccCFText
End Sub

Die zweite SQL-Anweisung verwendet fast die gleiche Anweisung wie die erste – allerdings nur als Unterabfrage. Damit ermittelt diese Abfrage alle Empfänger-Datensätze, die nicht durch die erste SQL-Anweisung erfasst werden und dementsprechend nicht über die Tabelle tblVerteiler mit der aktuell angezeigten Publikation verknüpft sind.

Aufbau der ListView-Steuerelemente

Die beiden ListViews sind genau gleich aufgebaut. Die einzigen Unterschiede sind der Name und die angezeigte Spaltenüberschrift. Die Eigenschaften im Detail:

  • Name: lvwEmpfaenger beziehungsweise lvwKeinEmpfaenger
  • View: 3 – lvwReport
  • Spaltenköpfe: je einer mit einer Breite von 2500 und der Beschriftung Empfänger beziehungsweise Kein Empfänger
  • MultiSelect: aktiviert
  • OLEDragMode: 1 – ccOLEDragAutomatic
  • OLEDropMode: 1 – ccOLEDropManual
  • Mit diesen Einstellungen und den beiden beschriebenen Routinen können Sie nun bereits die Daten aus den drei Tabellen darstellen. Es fehlt also nur noch die Drag-and-Drop-Funktion …

    Beim Drag and Drop mit ungebundenen Steuerelementen, wie es die ListViews nun einmal sind, ist vor allem eines zu beachten: Neben dem Verschieben des Eintrags zwischen den Listen müssen auch die dahinter liegenden Datenbanktabellen aktualisiert werden. Für das Ziehen eines Eintrags von einem zum anderen ListView benötigen Sie nur zwei Ereignisprozeduren – vorausgesetzt, Sie verzichten auf jeglichen Schnickschnack:

  • OLEStartDrag beim Quell-ListView
  • OLEDragDrop beim Ziel-ListView
  • Die OLEStartDrag-Prozedur (s. Quellcode 3) durchläuft in einer Schleife alle Elemente des ListViews und prüft, welche Elemente markiert sind. Alle markierten Elemente werden an eine String-Variable angehängt – und zwar in der Form <Key>¦<Text>;.

    Wozu das alles Ganz einfach: Die Parameterliste der Ereignisprozedur enthält ein Objekt namens Data, das zunächst mit der Clear-Methode geleert und dann über die SetData-Methode mit der soeben zusammengestellten Liste gefüllt wird. Dieses Objekt (OLE-DataObject) ist eine Art Zwischenablage für Drag-and-Drop-Operationen und wird im Ziel-Steuerelement ausgelesen.

    Quellcode 4: … und endet im ListView lvwZugeordnet.

    Private Sub lvwZugeordnet_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, _    Shift As Integer, x As Single, y As Single)
        Dim strData As String
        Dim strDataItems() As String
        Dim strText As String
        Dim strKey As String
        Dim i As Integer
        strData = Data.GetData(ccCFText)
        If InStr(1, strData, "¦") <> 0 Then
            If Right(strData, 1) = ";" Then
                strData = Left(strData, Len(strData) - 1)
            End If
            strDataItems = Split(strData, ";")
            For i = 0 To UBound(strDataItems)
                strKey = Split(strDataItems(i), "¦")(0)
                strText = Split(strDataItems(i), "¦")(1)
                If IsNull(DLookup("EmpfaengerID", "tblVerteiler", "PublikationID = " _                & Me.PublikationID & " AND EmpfaengerID = " & Mid(strKey, 2))) Then
                    lvwZugeordnet.ListItems.Add , strKey, strText
                    lvwNichtZugeordnet.ListItems.Remove strKey
                    CurrentDb.Execute "INSERT INTO tblVerteiler(PublikationID, EmpfaengerID) " _                    & "VALUES(" & Me.PublikationID & ", " & Mid(strKey, 2) & ")"
                End If
            Next i
        End If
    End Sub

    Dies passiert dann in der OLEDragDrop-Ereignisprozedur des Ziel-ListViews (s. Quellcode 4): Hier taucht das Objekt Data erneut in der Parameterliste auf. Die GetData-Methode gibt den Inhalt dieses Objekts preis. In der String-Variablen strData angekommen, werden die Daten direkt weiterverarbeitet: Nach der Prüfung, ob mindestens ein „Pipe“-Zeichen (¦) in der übergebenen Zeichenkette enthalten ist, teilen die folgenden Anweisungen die Zeichenkette zunächst in die durch Semikola (;) getrennten Bestandteile auf und speichern sie in einem String-Array. Dieses wird in einer For Next-Schleife über alle Elemente durchlaufen, wobei die Split-Anweisung die Elemente vor und hinter dem „Pipe“-Zeichen in die Variablen strKey und strText extrahiert. Und damit lässt sich natürlich leicht ein neues Element zum ListView hinzufügen. Gleiches gilt auch andersherum: Nachdem die Add-Methode der ListItems-Auflistung des Ziel-ListViews das neue Element hinzugefügt hat, entfernt die Remove-Methode selbiges aus dem Ursprungs-ListView.

    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