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
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:
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:
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