List- und Comboboxen

Einführung

Die List- und ComboBoxen in Visual FoxPro sind sehr komplexe Controls, die vielfältige Möglichkeiten in sich bergen. Dabei sind folgende prinzipielle Neuigkeiten gegenüber FoxPro für Windows 2.6 zu verzeichnen:

zumindest teilweiser objektorientierter Ansatz beim programminternen Umgang mit List- und ComboBoxen

neue Möglichkeiten der Datenquellen für List- und ComboBoxen (mit einer diesbezüglichen Gleichsetzung von List- und ComboBox)

Mehrspaltigkeit bei List- und ComboBoxen

Möglichkeit des Einbeziehens von BMPs in List- und ComboBoxen

Ein programmtechnischer Wermutstropfen ist dabei die Tatsache, daß das List- bzw. ComboBox-Control im Gegensatz zum Grid kein Container mit eigenständigen Elementen ist. Das hat zur Folge, daß bestimmte Funktionalitäten wie z.B. Mehrspaltigkeit nur über etwas "ungewöhnliche" Wege erreicht und programmtechnisch gesteuert werden können.

Übersetzungsliste "VFP-englisch" in "VFP-deutsch"

Die folgenden mit List- und ComboBoxen in Zusammenhang stehenden Begriffe tauchen in der deutschen Version von Visual FoxPro auf und werden aus Eindeutigkeitsgründen im vorliegenden Artikel ausschließlich in ihren englischen Entsprechungen verwendet:

VFP-deutsch VFP-englisch
Listenfeld ListBox
Kombinationsfeld ComboBox
Datenfeld Array
ElementNr ItemId
Index Index
Eigenschaft Property

Unterschiede zwischen List- und ComboBox

Die funktionellen Unterschiede zwischen List- und ComboBox drücken sich in einer Reihe von Unterschieden bzgl. der Properties, Events und Methoden aus:

nur ComboBox nur ListBox
Alignment MoverBars
BackColor MultiSelect
DropDown-Event
ForeColor
Margin
SelectedBackColor
SelectedForeColor
SelLength
SelStart
SelText
Style

Die comboBox-spezifischen Eigenschaften hängen im wesentlichen mit dem textBox-artigen Teil der ComboBox zusammen (ohne daß man allerdings vollständig alle äquivalenten Eigenschaften einer TextBox wiederfindet, es fehlen z.B. InputMask, HideSelection u.a.).

Die listBox-spezifischen Eigenschaften beziehen sich ausschließlich auf die Mehrfachauswahl und die Möglichkeit, Einträge einer ListBox per Maus umzusortieren.

Programmtechnische Zugriffsmechanismen auf Einträge in List-/ComboBoxen

Auf die Einträge in List-/ComboBoxen kann über zwei Wege zugegriffen werden:

über den sogenannten "Index" (die Position bzgl. der aktuellen optischen Anzeigereihenfolge)

über die sogenannte "ItemId" (eine eindeutige, von der optischen Position unabhängige Identifikation)

Für die meisten Methoden und Properties, die eine Spezifizierung des zu bearbeitenden Eintrags erfordern, existiert jeweils eine Variante, bei der der "Index" anzugeben ist und eine Variante, die eine "ItemId" erwartet:

List ListItem

arbeitet mit Index

arbeitet mit ItemId

AddItem

AddListItem

IndexToItem

ItemToIndex

ItemData

ItemIdData

ListIndex

ListItemId

NewIndex

NewItemId

Picture

<keine Entsprechung>

RemoveItem

RemoveListItem

Selected

SelectedId

TopIndex

TopItemId

Die Pendants der jeweiligen Properties und Methoden funktionieren in den meisten Fällen identisch (Ausnahme: AddItem fügt in jedem Fall Einträge hinzu, während AddListItem unter bestimmten Bedingungen bestehende Einträge ersetzt).

Die Properties ItemData/ItemIdData, List/ListItem, Picture und Selected/SelectedId beinhalten array-ähnliche Strukturen, auf die mit "Index" oder "ItemId" zugegriffen werden kann.

Auf die vorgenannten Properties kann nicht(!) mit den VFP-Array-Befehlen und -Funktionen (z.B. ASCAN() usw.) zugegriffen werden.

Einsatzfälle für List- und ComboBoxen

List- und ComboBoxen können für folgende Zwecke eingesetzt werden:

als Auswahl-Elemente (ListBox und ComboBox)

als reines Anzeige-Element (ListBox).

Bei dem Einsatz von List- und ComboBoxen als Auswahl-Element unterscheidet man:

Auswahl mit dadurch direkt ausgelöster Aktivität (hauptsächlich ListBox)

Auswahl als Voreinstellung für später auszuführende Aktivitäten (mehr die ComboBox, weniger die ListBox).

Beispiele für Listboxen für Auswahlprozesse mit direkt ausgelöster Aktivität:

Anzeige von Detail-Informationen zum gewählten ListBox-Eintrag:
für diese Zwecke ist der InteractiveChange-Event der geeignete Platz zum Aktualisieren der Detail-Informationen

soll per Mausklick eine Aktivität auch dann ausgelöst werden, wenn auf den eigentlich schon aktiven Eintrag geklickt wird, so ist der Click-Event zu verwenden:

wird auf einen Eintrag geklickt, der nicht der aktive Eintrag war, so wird der Click-Event ein Mal ausgelöst

klickt man dagegen auf den aktiven Eintrag, so wird der Click-Event zwei Mal durchlaufen)

soll per Doppelklick bzw. per RETURN-Taste eine Aktivität ausgelöst werden, so kann der DblClick-Event oder der Valid-Event benutzt werden

es ist zu beachten, daß beim Drücken der RETURN-Taste zwischen DblClick und Valid auch der When-Event erneut durchlaufen wird

wenn es lt. Definition der betreffenden Form möglich ist, die ListBox mit der Tabulator-Taste zu verlassen, dann führt auch ein Drücken der RETURN-Taste (im Gegensatz zum Doppelklick) nach Abarbeitung der notwendigen Events zum Verlassen der ListBox

Datenquellen von List- und ComboBoxen

Die Datenquelle von List- und ComboBoxen wird in dem RowSourceType-Property definiert. In VisualFoxPro stehen für List- und ComboBoxen folgende 10 Datenquellen zur Verfügung:
0 keine Rowsource: die Einträge müssen in diesem Fall zur Laufzeit mit der AddItem/AddListItem-Methode der List-/ComboBox hinzugefügt und ggf. mit RemoveItem/RemoveListItem wieder entfernt werden

alternativ zu RowSourceType = 1 ist RowSourceType = 0 die Voraussetzung für MoverBars = .T. und Sorted = .T.

1 als Einträge in die ListBox explizit angegebene Werte: Beispiel: "Dauerkunde,Laufkunde,schlechter Kunde"

je nach Anzahl der durch Komma zu trennenden Datenwerte und der in ColumnCount eingetragenen Spaltenanzahl ergibt sich daraus die Anzahl der Zeilen in der ListBox / in der Combobox

die Länge von RowSource ist in diesem Fall auf 255 begrenzt

alternativ zu RowSourceType = 0 ist RowSourceType = 1 die Voraussetzung für MoverBars = .T. und Sorted = .T.

2- es wird ein Alias-Bereich angegeben und es werden von links die ersten Datenfelder angezeigt: die Anzahl der anzuzeigenden Felder ergibt sich aus ColumnCount

SET FIELDS TO ... hat keine verändernde Wirkung auf die angezeigten Felder

bei ca. 10.000 Datensätzen liegt die Grenze der Performance-Erträglichkeit eines ListBox mit RowSourceType = 3, darüber hinaus sind die Reaktionen zu langsam (DX2/66, kein weiterer Code in Methoden oder Events)

3 SQL-Anweisung: die in RowSource als Zeichenkette einzutragende SQL-Anweisung muß als Zielangabe "INTO CURSOR ..." haben

bei ca. 10.000 Datensätzen liegt die Grenze der Performance-Erträglichkeit eines ListBox mit RowSourceType = 3, darüber hinaus sind die Reaktionen zu langsam (DX2/66, kein weiterer Code in Methoden oder Events)

4 eine Query (RQBE - .QPR): in RowSource ist der Dateiname der .QPR-Datei anzugeben

bei ca. 10.000 Datensätzen liegt die Grenze der Performance-Erträglichkeit eines ListBox mit RowSourceType = 4, darüber hinaus sind die Reaktionen zu langsam (DX2/66, kein weiterer Code in Methoden oder Events)

5 ein Array (Datenfeld): der Arrayname ist als Zeichenkette(!) in der RowSource anzugeben

die Zeilen des Arrays bilden die Zeilen der List-/ComboBox

ColumnCount bestimmt die Anzahl der ab Spalte 1 anzuzeigenden Spalten

RowSourceType = 5 ist die Voraussetzung für eine Reihe arrayspezifischer Properties und Methoden (z.B. FirstElement, NumberOfElements...)

6 Feldliste: durch Kommata getrennte Liste aller Felder einer Tabelle / einer View, die in einer Listbox angezeigt werden sollen (z.B. "name,geb_dat,geb_ort")

es sind nur Felder aus genau einer Tabelle / einer View möglich

der Feldliste kann einmalig(!) ein Aliasbezeichner vorangestellt sein (z.B. "personal.name,geb_dat,geb_ort")

bei ca. 10.000 Datensätzen liegt die Grenze der Performance-Erträglichkeit eines ListBox mit RowSourceType = 6, darüber hinaus sind die Reaktionen zu langsam (DX2/66, kein weiterer Code in Methoden oder Events)

7 Dateien eines Datenträger-Inhaltsverzeichnisses: in RowSource kann eine Datei-Maske (ggf. mit "*" und "?") eingetragen werden, die auch Laufwerks- und Verzeichnisangabe enthalten darf

der Typ 7 ist eigentlich ein Backward-Relikt und kann durch den GETFILE()-Dialog ersetzt werden

er bietet allerdings die Möglichkeit, unter Nutzung von MultiSelect = .T. eine Mehrfachauswahl von Dateien zu realisieren

außerdem kann es im Gegensatz zu GETFILE() als Dialogelement in eine Maske direkt eingebunden werden

8 Struktur einer DBF-Datei: in RowSource ist der Name der DBF-Datei einzutragen, deren Struktur-Informationen anzuzeigen sind (es werden nur die Feldnamen angezeigt)
9 ListBox aus einer POPUP-Definition erstellen diese Backward-Relikt sollte nach Möglichkeit nicht weiter verwendet werden

Die Beschreibung des konkreten Inhalts einer List- bzw. Combobox entsprechend RowSourceType wird mit dem RowSource-Property festgelegt.

Bei RowSourceType = 2, 3, 4, 5, 6 können die Datenquellen neben dem Datentyp "C" auch vom Datentyp "N" oder "L" sein (sie werden dann automatisch in den Datentyp "C" gewandelt).

Diese automatische Datentyp-Wandlung von "N" zu "C" ist allerdings problematisch, wenn die gewandelten Zahlen mehr oder weniger viele führende Leerzeichen haben. Da die Texte in den Spalten linksbündig angezeigt werden, führen diese Leerzeichen u.U. zu nicht untereinanderstehenden Dezimalstellen. Einziger Ausweg ist die Verwendung eines nichtproportionalen Fonts.

Memo- oder General-Felder sind als Datentypen für List-/ComboBoxen nicht möglich.

Änderungen von RowSourceType zur Laufzeit bewirken eine sofortige Umdefinition der List-/ComboBox, u.U. mit Syntaxerror-Folgen, wenn die RowSource noch nicht umgestellt war, deshalb beim Umdefinieren von RowSourceType zur Laufzeit wie folgt vorgehen:

  1. RowSourceType = 0 setzen
  2. RowSource auf den gewünschten Inhalt setzen
  3. jetzt RowSourceType auf den zur RowSource passenden Wert einstellen

Das ControlSource-Property und verwandte Properties

Je nach Verwendungszweck einer List-/ComboBox ist eine ControlSource notwendig oder auch nicht.

Allgemein laßt sich sagen, daß bei konsequenter Verwendung objektorientierter Programmiermethoden eine ControlSource nur noch in Ausnahmefällen benötigt wird.

Wird jedoch eine ControlSource verwendet, so hat sie einige Wechselwirkungen mit den Properties Value, DisplayValue, ListIndex und ListItemId.

Value repräsentiert den aktuellen Eintrag der ListBox (unabhängig davon, ob dieser Eintrag ausgewählt ist oder nicht)

Das BoundColumn-Property bestimmt ggf. die Spalte, aus der das Value-Property seinen Wert bezieht

Im Gegensatz zu der Aussage in der Dokumentation ist das Value-Property nicht(!) readonly

DisplayValue repräsentiert den Inhalt der ersten Spalte einer List-/ComboBox

beinhaltet bei einer ComboBox vom Style = 0 den im Eingabeteil angezeigten/eingetippten Wert

ListIndex beinhaltet die Nummer des aktuellen Eintrags, bezogen auf die aktuelle optische Reihenfolge der Einträge
ListItem beinhaltet die eindeutige Identifikations-Nummer des aktuellen Eintrags, die nicht abhängig ist von der optischen Reihenfolge der Einträge

Zugelassene Datentypen für eine ControlSource sind "C" und "N", andere Datentypen führen zu einem Laufzeitfehler.

Der Datentyp und der Inhalt der ControlSource bestimmt den Datentyp und den Inhalt des Value-Property (wird der Datentyp oder der Inhalt der ControlSource geändert, so ändert sich der Datentyp bzw. der Inhalt des Value-Property und umgekehrt).

Das DisplayValue-Property verwaltet seinen Datentyp und seinen Inhalt allerdings unabhängig von Value und ControlSource.

Für ControlSource, Value und DisplayValue gilt:

Wird die ControlSource, das Value-Property oder das DisplayValue-Property auf einen Wert gesetzt, zu dem kein passender Eintrag existiert, so stehen ListIndex und ListItemId auf 0, die ControlSource sowie Value und DisplayValue werden je nach Datentyp auf 0 oder "" (leere Zeichenkette) gesetzt.

Folgende Besonderheit von Controlsource und DisplayValue- bzw. Value-Property ist zu beachten:

Ein weiterer eigenartiger Zustand kann entstehen, wenn man in eine ListBox hineinklickt und bei gedrückter Maustaste den Mauscursor wieder aus der ListBox herauszieht. In diesem Fall werden ListIndex und ListItemId mit 0 belegt, aber die ControlSource sowie das Value- und das DisplayValue-Property behalten die Werte bei, die sie vor dieser Operation hatten (was wiederum zu überraschenden Konstellationen führen kann)!

Achtung! In dieser Situation wird kein InteractiveChange- oder ProgrammaticChange-Event ausgelöst!

Mehrspaltige List-/ComboBoxen

Zur Gestaltung mehrspaltiger List-/ComboBoxen sind folgende Properties relevant:

BoundColumn gibt an, aus welcher Spalte das Value-Property seinen Inhalt beziehen soll (ist nur für den Fall relevant, daß das Value-Property vom Datentyp "C" ist)
wird BoundColumn größer eingestellt als die verfügbare Anzahl von Spalten, dann bleibt das Value-Property leer
ColumnCount bestimmt die Anzahl anzuzeigender Spalten
wenn sich aus RowSource und RowSourceType eine größere Menge Spalten ergibt als in ColumnCount definiert ist, dann hat ColumnCount den Vorrang (wobei die Definitionen für die "überzähligen" Spalten nicht gelöscht werden, d.h. wird ColumnCount in diesem Fall nachträglich erhöht, werden die vorher unterdrückten Spalten sichtbar)
wird ColumnCount höher gesetzt als die sich aus RowSource und RowSourceType ergebende Spaltenanzahl, so werden die überzähligen Spalten unterdrückt
ColumnLines legt fest, ob die einzelnen Spalten durch senkrechte Linien getrennt werden sollen
ColumnWidths diese Zeichenkette(!) bestimmt die Breite der einzelnen Spalten in Form einer durch Kommata getrennte Liste von Spaltenbreiten (z.B. "50,30,120")
durch Angabe von 0 als Spaltenbreite wird die entsprechende Spalte in der Anzeige unterdrückt (zwei aufeinanderfolgende Kommata werden ebenfalls wie 0 interpretiert)
die Maßeinheit der Breitenangaben entspricht dem ScaleMode-Property der entsprechenden Form
gibt es für anzuzeigende Spalten keine definierte Spaltenbreite (wenn ColumnWidths leer ist oder zu wenig Einträge besitzt), so werden die "überzähligen" Spalten in jeder Zeile der List-/ComboBox in genau der Breite angezeigt, die der Textlänge in dem jeweiligen Eintrag entspricht (was zu unterschiedlichen Spaltenbreiten in den einzelnen Zeilen führen kann)
DisplayValue beinhaltet bei List-/ComboBoxen den Inhalt der ersten Spalte des ausgewählten Eintrages (dadurch besonders relevant bei mehrspaltigen List-/ComboBoxen, wenn BoundColumn > 1 ist)
DisplayValue wird bei der ComboBox außerdem zur Anzeige des Textes in dem Eingabebereich der ComboBox verwendet
tippt der Nutzer in den Eingabebereich einer ComboBox (mit Style = 0) einen Wert ein, so kann der eingetippte Text dem DisplayValue-Property entnommen werden (bekommt DisplayValue einen Wert, zu dem es keinen Eintrag in der ComboBox gibt, bleibt demgegenüber das Value-Property leer)
RowSource bei mehrspaltigen ListBoxen existiert eine Wechselwirkung zwischen RowSource und ColumnCount
erste Variante: die RowSource legt fest, wieviele Spalten für die List-/ComboBox existieren; und ColumnCount begrenzt die Anzahl der tatsächlich angezeigten Spalten (RowSourceType = 2, 3, 4, 5 ,6 oder 8)
zweite Variante: die ColumnCount legt fest, auf wieviele Spalten die RowSource zu verteilen ist (RowSourceType = 1)
dritte Variante: RowSourceType = 0
in diesem Fall werden die Einträge per AddItem/AddListItem erzeugt und durch die dabei übergebenen Parameter eingeordnet, wobei ColumnCount die Anzahl der anzuzeigenden Spalten bestimmt
bei allen drei Varianten kann die Anzahl tatsächlich sichtbarer Spalten durch auf "0" gesetzte ColumnWidth-Einträge noch zusätzlich reduziert werden
RowSourceType bestimmt die zu verwendende Datenquelle

1:n-Beziehungen mit List-/ComboBoxen

Unter Beachtung bestimmter Voraussetzungen können List-/ComboBoxen zur Visualisierung von 1:n-Beziehungen benutzt werden.

Hinter dem Hilfe-Topic "Aktualisieren einer 1:n-Anzeige, die auf einem Listenwert basiert" verbirgt sich die Beschreibung, wie man mit einer List-/ComboBox einen Datensatz in einer Masterdatei auswählt und alle zugehörigen Detail-Datensätze in "anderen" Dialogelementen angezeigt bekommt.

Dieses "andere" Dialog-Element kann nur ein Dialogelement sein, welches Informationen aus mehreren Datensätzen anzeigen kann (also nur Grid, ListBox oder ComboBox). Die im Hilfetopic beschriebene Verfahrensweise funktioniert allerdings nur mit dem Grid, mit List- oder ComboBoxen als Child-Elementen wirkt der Mechainsmus leider nicht.

Möchte man trotzdem in einer ListBox Child-Informationen anzeigen, so kann man z.B. einen Filter (am besten rushmore-optimierbar) verwenden.

Das in der VFP-Hilfe unter dem Topic "Anzeigen von Detail-Datensätzen in einer Liste" beschriebene Verfahren unter Verwendung eines Select-Befehls ist nur bedingt geeignet, weil das Requery der Detail-ListBox je nach Datenkonstellation ziemlich lange Reaktionszeiten bedeuten kann.

Bitmaps in List- und ComboBoxen

In List- und ComboBoxen kann den eigentlichen Einträgen in jeder Zeile eine Bitmap vorangestellt werden. Zu diesem Zweck kann das Picture-Property für jeden Eintrag mit dem Namen einer BMP- oder ICO-Datei gefüllt werden (auf das Picture-Property kann analog dem List-Property mit dem "Index" zugegriffen werden).

Bei ComboBoxen wird das entsprechende Bild allerdings nur in der aufgeklappten Liste angezeigt, nicht(!) in der zugehörigen Anzeigebox.

Laut Dokumentation soll dem Picture-Property auch der Inhalt eines General-Feldes zugewiesen werden können, aber das war nicht nachzuvollziehen (Fehler 1912: "Operation ist für ein Objektfeld nicht zulässig.")

Die Bitmaps werden in ihrer vollen Größe ohne irgendwelche Anpassungen angezeigt.

Sie beeinflussen damit auch die Größe der einzelnen Einträge, die dadurch unterschiedlich hoch sein können. Das führt in bestimmten Konstellationen zu unerwünschten Überblendungseffekten, wenn eine solche ListBox gescrollt wird.

Ebenfalls ist Vorsicht geboten bei der kombinierten Verwendung von Bitmaps und MoverBars. Auch in diesem Fall kommt es zu unschönen optischen Effekten.

Events von List- und ComboBoxen

Das Event-Verhalten von List- und ComboBoxen ist ein äußerst komplizierter Ablauf.

Je nach Art der Bedien-Aktivität (Maus-Klick, Maus-Doppel-Klick, Tastatur-Navigation) unterscheidet sich das Event-Verhalten sehr stark und birgt auch so einige Überraschungen.

Für das Positionieren von Programm-Code in die richtigen Events ist es deshalb unumgänglich, sich mit der diesbezüglichen Verhaltensweise von List- und ComboBoxen experimentell vertraut zu machen.

Neben der Frage, bei welcher Aktivität welcher Event ausgelöst wird, ist außerdem noch die Auslöse-Reihenfolge und die Auslösehäufigkeit wichtig (letzteres, um unbeabsichtigte Kumulationen zu vermeiden).

Im folgenden sind einige der wesentlichsten Erkenntnisse zusammengestellt:

Je nach dem, ob man auf den schon aktiven Eintrag klickt oder nicht, wird in der ListBox bei jedem Mausklick der Click-Event zwei Mal bzw. nur einmal durchlaufen.

In der ListBox wird nach jeder Navigation (mit Maus oder Tastatur, mit Ausnahme der Scrollbars) der When-Event ausgelöst.

Bei Doppelklick in der ListBox wird vor der DblClick-Event u.a. zwei mal der Click-Event abgearbeitet.

Die ComboBox hat zwar einen DblClick-Event, der aber nie angesprochen wird.

Betätigt man in einer ListBox die RETURN-Taste, dann wird u.a. der DblClick-Event ausgelöst und die ListBox verliert zwangsweise den Focus (zumindest wenn noch ein Dialogelement vorhanden ist, welches den Focus erhalten kann).

Weitere relevante Events sind MouseDown, MousUp, InteractiveChange, When, Valid, GotFocus, LostFocus.