Kombinationsfelder (Combo-Boxen) sind die Arbeitspferde jeder Access-Oberfläche: Sie zeigen eine Liste, geben aber im Hintergrund oft einen Schlüsselwert weiter. Mit VBA machst du sie flexibel — dynamische Listen, neue Einträge on-the-fly und Auswahl, die von einer anderen Auswahl abhängt.
RowSource dynamisch setzen
Die angezeigte Liste steuert die Eigenschaft RowSource. Je nach RowSourceType
ist das eine Tabelle/Abfrage („Table/Query") oder eine feste Werteliste
(„Value List"). Beides kannst du zur Laufzeit setzen:
' Als SQL-Abfrage
Me!cboKunde.RowSource = _
"SELECT KundenID, Firma FROM tblKunde " & _
"WHERE Aktiv = True ORDER BY Firma"
' Als feste Werteliste
Me!cboStatus.RowSourceType = "Value List"
Me!cboStatus.RowSource = "offen;in Arbeit;erledigt"
Nach dem Setzen aktualisiert Access die Liste automatisch. Baust du die
Datengrundlage anderweitig um, hilft ein Me!cboKunde.Requery.
Gebundene Spalte und Spaltenbreiten
Ein Kombifeld kann mehrere Spalten anzeigen, gibt als Value aber nur eine zurück
— die gebundene Spalte (BoundColumn). Typisch: Spalte 1 ist die ID (gebunden,
aber ausgeblendet), Spalte 2 der lesbare Name.
| Eigenschaft | Bedeutung |
|---|---|
ColumnCount | Anzahl der Spalten in der Liste |
BoundColumn | welche Spalte den Value liefert (1-basiert) |
ColumnWidths | Breiten je Spalte (0cm blendet aus) |
With Me!cboKunde
.RowSource = "SELECT KundenID, Firma FROM tblKunde ORDER BY Firma"
.ColumnCount = 2
.BoundColumn = 1 ' Value = KundenID
.ColumnWidths = "0cm;5cm" ' ID unsichtbar, Firma sichtbar
End With
Me!cboKunde liefert jetzt die KundenID, obwohl der Anwender die Firma liest.
Auf einzelne Spalten zugreifen mit Column()
Manchmal brauchst du eine andere als die gebundene Spalte — etwa den angezeigten
Namen, um ihn in ein Textfeld zu schreiben. Dafür gibt es Column(Index), 0-basiert:
Private Sub cboKunde_AfterUpdate()
' Spalte 0 = KundenID, Spalte 1 = Firma
Me!txtFirma = Me!cboKunde.Column(1)
End Sub
Achtung, zwei Zählweisen:
BoundColumnundColumnWidthszählen ab 1,Column()zählt ab 0. Die gebundene Spalte 1 ist alsoColumn(0). Das ist die häufigste Fehlerquelle bei Kombifeldern.
Optional nimmt Column(Spalte, Zeile) auch eine Zeilennummer — nützlich, um die
ganze Liste durchzugehen, ohne ein Recordset zu öffnen.
Neue Einträge zulassen: das NotInList-Ereignis
Tippt der Anwender in ein gebundenes Kombifeld einen Wert, der nicht in der Liste
steht, meckert Access. Damit das nicht als Fehler endet, brauchst du zweierlei:
Die Eigenschaft Nur Listeneinträge (LimitToList) muss True sein, und du
behandelst das Ereignis NotInList. Dort legst du den neuen Eintrag an und meldest
Access über Response zurück, wie es weitergehen soll.
Private Sub cboKategorie_NotInList(NewData As String, Response As Integer)
Dim antwort As VbMsgBoxResult
antwort = MsgBox("'" & NewData & "' ist neu. Anlegen?", _
vbYesNo + vbQuestion, "Neue Kategorie")
If antwort = vbYes Then
' Neuen Datensatz per DAO in die Quelltabelle schreiben
CurrentDb.Execute _
"INSERT INTO tblKategorie (Bezeichnung) VALUES ('" & _
Replace(NewData, "'", "''") & "')", dbFailOnError
' Access anweisen: Eintrag wurde hinzugefügt -> Liste neu laden
Response = acDataErrAdded
Else
' Abbrechen ohne Meldung, Eingabe zurücknehmen
Response = acDataErrContinue
Me!cboKategorie.Undo
End If
End Sub
Die drei möglichen Rückgabewerte für Response:
| Konstante | Wirkung |
|---|---|
acDataErrAdded | Eintrag wurde angelegt, Access requert die Liste selbst |
acDataErrContinue | keine Fehlermeldung, Wert wird verworfen |
acDataErrDisplay | Access zeigt seine Standard-Fehlermeldung (Vorgabe) |
SQL-Injection & Hochkommas: Übernimm Benutzereingaben nie ungeprüft in SQL. Das
Replace(NewData, "'", "''")verdoppelt einfache Anführungszeichen, damit Namen wieO'Briennicht die Anweisung zerlegen. Bei mehr als Kleinkram nutze eine parametrisierteQueryDefstatt Stringverkettung.
Reaktion auf die Auswahl: AfterUpdate
AfterUpdate läuft, nachdem der Anwender einen Wert ausgewählt (oder eingegeben)
hat. Das ist der Ort für Folgeaktionen — Felder füllen, filtern, ein zweites Kombifeld
nachladen:
Private Sub cboKunde_AfterUpdate()
Me!txtFirma = Me!cboKunde.Column(1)
Me!ctlUnterBestellungen.Form.Requery
End Sub
Kaskadierende Kombifelder
Ein Klassiker: Das zweite Kombifeld zeigt nur, was zum ersten passt — Land → Stadt,
Kunde → Projekt. Der Trick besteht aus zwei Teilen. Erstens filtert die RowSource
des zweiten Felds über das erste, zweitens requerst du das zweite Feld im
AfterUpdate des ersten.
Die RowSource von cboProjekt verweist im Entwurf auf das erste Feld:
SELECT ProjektID, Titel FROM tblProjekt
WHERE KundenID = Forms!frmAuftrag!cboKunde
ORDER BY Titel
Im Code sorgst du dafür, dass die Liste bei jeder neuen Kundenauswahl neu ausgewertet wird:
Private Sub cboKunde_AfterUpdate()
' Zweite Liste an die neue Kundenauswahl anpassen
Me!cboProjekt.Requery
' Alte Auswahl verwerfen, falls sie nicht mehr passt
Me!cboProjekt = Null
End Sub
Ohne das Requery würde cboProjekt die alte Projektliste behalten. Das = Null
verhindert, dass ein Projekt des vorigen Kunden stehen bleibt, das nun gar nicht mehr
in der Liste ist.
Tipp: Setze
cboProjekterst aufNullund dann Requery, wenn dein Feld an eine Spalte gebunden ist — sonst kann ein ungültiger Restwert kurz Probleme machen. In der Praxis funktioniert beide Reihenfolgen meist; teste mit echten Daten.
Zusammengefasst
RowSourcesetzt die Liste zur Laufzeit — als SQL (RowSourceType„Table/Query") oder als Werteliste; einRequeryfrischt sie auf.BoundColumnbestimmt, welche Spalte derValueist; mitColumnWidths(0cm) blendest du z. B. die ID aus.Column(Index)liest einzelne Spalten — 0-basiert, währendBoundColumn1-basiert zählt (häufige Fehlerquelle).- Im
NotInList-Ereignis legst du neue Einträge an und meldest mitResponse = acDataErrAddedzurück, dass die Liste neu geladen werden soll. - Kaskadierende Kombifelder verknüpfst du über die gefilterte
RowSourceplusRequery(und= Null) imAfterUpdatedes übergeordneten Felds.