Virtual OS/2 International Consumer Education
VOICE Homepage: http://de.os2voice.org
März 2003

[Inhaltsverzeichnis]
[Vorherige Seite] [Nächste Seite]
[Artikelverzeichnis]

editor@os2voice.org


DrDialog, oder wie ich lernte, REXX zu lieben - Teil 6

Von Thomas Klein © März 2003

Entschuldigen Sie bitte das Fehlen der Fortsetzung in der Ausgabe vom letzten Monat, aber nun geht's weiter: Mit dem container control.
Wie ich bereits anklingen ließ, ist dieses Steuerlement ein Sonderfall. Es beherbergt eine Fülle an GUI-Komfort und ist unglaublich vielseitig und nützlich, vom visuellen Standpunkt her sehr "anziehend" und bietet dem Endbenutzer sehr komfortable Funktionalität.
Leider fordern all diese Vorteile ihren Preis, nämlich ein entsprechend hohes Volumen an Informationen, Daten und Steuerung, damit es das tut, was man möchte. Einen guten Einstieg in diese Fülle von Methoden, Ereignissen und Befehlen zu finden, die unterstützt bzw. benötigt werden, ist auf den ersten Blick tatsächllich nicht gerade leicht. Ich möchte daher versuchen, diesen Artikel als eine Art strukturierten Führer durch das Online-Hilfematerial anzulegen. Außerdem ist zu diesem Artikel eine Beispielanwendung über die VOICE-Website verfügbar (s. Quellenhinweis am Artikelende), die Ihnen einen ersten Blick hinter die Kulissen gestattet, wenn es um das Arbeiten mit container controls geht. (Diese Beispielanwendung diente auch zur Erstellung der meisten Bildschirmabbildungen in diesem Artikel.) [Die auf der VOICE-Webseite zum Herunterladen verfügbaren Archive enthalten die Beispielanwendung in einer ZIP-Datei. - Anm.d.Red.]

container control Darf ich vorstellen: Das container control

Im Prinzip handelt es sich beim container control - aus der Sicht des Endanwenders - um eine Art "Ordner". Enthaltene Objekte können (unter anderem) als Symbole, mehrspaltig oder auch als hierarchische "Baum"-Struktur angezeigt werden. Das besondere daran ist, daß Sie (der Entwickler) totale Kontrolle über alle Eigenschaften haben: Was wird angezeigt, wie läuft es ab und wie reagiert es auf Benutzeraktionen. Der Haken an der Sache ist andererseits, daß Sie sich mit diesen Sachen leider beschäftigen müssen, da das container control absolut nichts von alleine macht. ;)
Es besitzt keinen Automatismus mit dem man z.B. lediglich auf ein Verzeichnis verweisen kann und schon läuft's - nö. In solchen Fällen wäre der Entwickler (also Sie) dazu gezwungen, zunächst alle Dateien und Unterverzeichnisse des Verzeichnisses "manuell" zu ermitteln und damit den container entsprechend zu füllen.
Um Ihnen einen kurzen Überblick darüber zu geben, was man mit dem container control anstellen kann, nehmen wir als Beispiel ein fiktives Adreßbuchprogramm (oder eine "Buddylist", wenn Sie so wollen). Die enthaltenen Einträge bestehen aus dem Namen, einem zugewiesenen Bitmap und zusätzlichen Daten wie Telefonnummer und E-Mail-Adresse. Die unterschiedlichen Ansichten ("view"-Typen), die mit dem container control zur Verfügung stehen, sind...

text view
TEXT - Einträge werden in einer einzelnen Spalte angezeigt, die nur den jeweiligen Namen enthält.


 

column view
COLUMN - Wie TEXT, aber mehrspaltig.


 

name view
NAME - Einspaltige Darstellung, die sich aus dem Namen und - links davon - dem Bitmap des Eintrags zusammensetzt.


 

flowename view
FLOWEDNAME - Wie NAME, aber mehrspaltig.


 

bitmap view
BITMAP - Wie die Symbolansicht eines WPS-Ordners: Die Einträge bestehen aus dem Bitmap und dem darunter zentriert angezeigten Namen. Zur Anordnung dient ein gitterähnliches Verfahren.


 

detail view
DETAIL - Wie die Detailansicht eines WPS-Ordners: Zu jedem Eintrag existiert eine Zeile in einer tabellenähnlichen Struktur.
Diese kann aus einer beliebigen Anzahl von Spalten bestehen, die sich wahlweise in einem einzelnen Fenster oder in einer zweiteiligen Darstellung befinden, bei der eine vertikale, verschiebbare Trennlinie zur Größenänderung verwendet wird.


 

outline view
OUTLINE - Die Einträge haben den gleichen Umfang wie in NAME, jedoch wird zusätzlich eine Eltern/Kind-Beziehung zwischen ihnen angezeigt, indem untergeordnete Einträge jeweils nach rechts eingerückt werden. Übergeordnete Einträge erhalten zusätzlich ein Symbol, mit dem die Ansicht um die untergeordneten Einträge (soweit vorhanden) erweitert oder reduziert werden kann.


 

hierarchy view
HIERARCHY - Wie die Strukturanzeige in WPS-Ordnern und eigentlich wie OUTLINE, jedoch erweitert um Linien, die vom übergeordneten Eintrag zu dessen untergeordneten Einträgen führen.


 

Um Ihnen einen kleinen "Appetitanreger" für die Arbeit mit containern zu geben: Von einer der oben abgebildeten Ansichten zu einer anderen zu wechseln (unter Beibehaltung aller Einträge) erfordert nur eine Zeile Programmcode - um genau zu sein: Eine einzige Anweisung... ;) Natürlich erfordert das ein wenig Arbeit im Vorfeld, zum Beispiel das eigentliche "Füllen" des containers.
Der Arbeitsaufwand ergibt sich dabei in erster Linie aus der/den Ansicht(en), die Sie dem Endbenutzer zur Verfügung stellen möchten: Eine "einfache" Darstellung wie BITMAP erfordert lediglich eine einzelne und relativ simple Anweisung, um dem container ein Objekt hinzuzufügen... während - wie Sie sich sicherlich vortsllen können - die Darstellungsform DETAIL einiges mehr an Daten erfodert, weil damit ja auch eine größere Menge an Daten dargestellt werden kann und das Anzeigeformat weitaus umfangreicher ist. Dasselbe gilt auch für die beiden Strukturansichtsformen HIERARCHY und OUTLINE. Obwohl hierbei ebenfalls nur Bitmap und Text eines Eintrags angezeigt werden, benötigt man zusätzliche Informationen um die Zuordnungen zwischen "Eltern" und "Kindern" darstellen zu können. Wenn man aber erst einmal alle benötigten Daten bereitgestellt hat, macht das Arbeiten mit Container-Elementen wirklich Spaß.
Aber bevor wir uns anschauen, wie man Einträge in einem container anlegt, sollten wir ein paar Dinge klären...

Grundlagen der "Endbenutzung" eines containers

Bevor Sie ein Container-Element auf Ihren Dialog ziehen, sollten Sie sich darüber im klaren sein, welches Maß an Arbeit auf Sie als Entwickler zukommt, je nach dem, wie der Endbenutzer es verwendet... um das etwas zu verbildlichen: Stellen Sie sich eine Adreßverwaltung auf Basis eines containers vor, wie Sie oben abgebildet ist. Was passiert zum Beispiel, wenn der Anwender einen neuen Eintrag anlegen will? Gut, das wäre relativ einfach: Sie zeigen einen neuen Dialog an, in dem die Daten des neuen Eintrags erfaßt werden und fügen ihn nach Bestätigung in den container ein - so weit, so gut. Aber... wenn der Benutzer jetzt einen Eintrag löschen will? Das könnte zum Beispiel geschehen, indem ein Kontextmenü benutzt wird, die Taste <Entf> gedrückt wurde oder eine andere Aktion durchgeführt wird, die Sie dem Endanwender anbieten. Zusätzlich kann der Anwender durch Anklicken eines Spaltenwerts mit gedrückter Taste <Alt> diesen ändern - sofern die Spalte nicht als "schreibgeschützt" definiert wurde (dazu kommen wir später). Das Container-Element kümmert sich zwar selbsttätig um die notwendigen Schritte (Umschaltung von Anzeige- auf Eingabefeld und Übernahme der Eingabe), aber ungeachtet dessen, was auf der Benutzeroberfläche passiert, sind Sie dafür zuständig, die Eingaben und Änderungen des Benutzers in der Datei bzw. Datenbank zu speichern...
Um es zusammenzufassen: Sie sollten den Funktionsumfang des Steuerelements "container" kennen, die dem Benutzer damit zur Verfügung stehenden Möglichkeiten und daraus eine Art Benutzerführung für Ihren Anwendungszweck entwickeln: Wie soll der Anwender Einträge hinzufügen, löschen oder ändern können? Okay - fangen wir einmal mit den Ereignissen und Methoden (Funktionen, Befehlen) eines Containerelements an:

Ereignisse (events) für ein Containerelement

Die einzelnen nachfolgenden Ereignisse treten jeweils für das Container-Element als solches auf. Da ein container jedoch - wie der Name es vermuten läßt - zur Aufnahme mehrerer Objekte dient, ist es manchmal nötig zu wissen, welches der enthaltenen Objekte eigentlich betroffen ist. Diese Informationen sind zwar verfügbar, jedoch muß dafür eine zusätzliche Funktion namens EventData verwendet werden. Die Syntax für EventData lautet

call EventData (stamm)

wobei stamm der Name einer Stammvariable ("stem variable") ist. Wenn die Informationen von EventData beispielsweise in eine Stammvariable namens "meinstamm" abgelegt werden sollen, muß die Anweisung dafür lauten:

call EventData ("meinstamm")

Ja - einschließlich der Anführungszeichen!
Wenn Sie bisher nichts von Stammvariablen gehört haben, werden Sie sich bestimmt fragen, um was es dabei eigentlich geht. Ich versuch's 'mal so: Eine Stammvariable ist im Prinzip eine eindimensionale Tabelle (quasi eine Liste) von Variablen, die alle den gleichen Namen haben und sich nur durch einen zusätzlichen Index (wie eine laufende Nummer) unterscheiden. Die Schreibweise für die Verwendung solcher Stammariablen ist dabei relativ einfach. Am Anfang steht der "Stamm"-Name der Variable, dann folgt ein Punkt und danach der Index des jeweiligen Elements - entweder direkt als Ziffer oder durch Angabe einer Variable, die den Wert für den Index enthält. Wenn Sie eine Stammvariable namens "meinstamm" anlegen wollen, die drei Einträge enthält, würden Sie das so codieren:

meinstamm.1 = "erster"
meinstamm.2 = "zweiter"
meinstamm.3 = "dritter"

Danach verwenden Sie den Eintrag Nummer 0 (NULL), um darin die Größe (Anzahl der Einträge) der Stammvariable abzulegen:

meinstamm.0 = 3

Wenn Sie eigene Stammvariablen verwenden (also "manuell" anlegen), dann ist diese letzte Anweisung eigentlich nicht erforderlich, jedoch handelt es sich dabei um ein gängiges Verfahren in REXX - auch die Systemfunktionen wie z.B. das Suchen von Dateien auf der Platte verwenden dieses Verfahren, um zu kennzeichnen, wie viele Einträge zu einer Stammvariable vorhanden sind. Auf diese Weise kann der Programmierer mittels Eintrag Nummer 0 nach dem Aufruf der Funktion ermitteln, aus wie vielen Einträgen die Stammvariable besteht. Das ist unbedingt nötig, um beispielsweise in einer Schleife alle Einträge der Stammvariable zu verarbeiten. Na, egal - das sollte für's erste jedenfalls reichen, damit Sie einen ersten Eindruck von Stammvariablen gewinnen und wir mit der Funktion EventData fortfahren köennen (wir kommen in einem späteren Teil der Serie nochmals auf die Stammvariablen zurück).
Wohlan, denn: Lassen Sie uns die Syntax von EventData nochmals anschauen... in der Online-Hilfe von DrDialog steht dazu:

EventData( [stamm] )

Liefert die Liste von Einträgen, auf die sich das Ereignis bezieht, in die durch [stamm] angegebene Stammvariable. Wird [stamm] nicht angegeben, wird dafür EVENTDATA verwendet.

Die Anzahl der betroffenen Einträge wird in stamm.0 abgelegt. stamm.1 bis stamm.n enthält jeweils ein vom aktuellen Ereignis betroffenes Element. Für eine detailierte Beschreibung der zu einem bestimmten Ereignis für ein bestimmtes Steuerelement zurückgegebenen Daten beachten Sie den Abschnitt "Steuerelemente".
[..]

Daraus ergibt sich...
Erstens: Es muß keine Stammvariable angegebgen werden. In diesem Fall wird vom System ein "eingebauter" Vorgabewert benutzt, der eine Stammvariable namens "EVENTDATA" für diesen Zweck anlegt und verwendet.
Zweitens: Wie wir bereits weiter oben gesehen haben, wird die Anzahl der zurückgegebenen Einträge im Stammvariableneintrag Nummer 0 abgelegt.

Bevor wir jetzt endlich zu den Ereignissen kommen, müssen wir noch eine Kleinigkeit klären:
Während die Einträge in einer Liste oder Combobox durch einen simplen, fortlaufenden Index identifiziert werden (1 = erster, 2 = zweiter usw.), werden die Einträge eines containers gänzlich anders verwaltet. Jeder Eintrag besitzt eine eindeutige "ID" ("Schlüssel", wenn Sie so wollen), der zum Zeitpunkt des Anlegens des Eintrags vom System generiert wird. Diese IDs beginnen nicht bei 1, noch sind sie fortlaufend oder gar sortiert. Verwenden Sie sie keinesfalls, um irgend etwas zu berechnen. Sie dienen ausschließlich dazu, Objekte (Einträge) innerhalb des containers zu identifizieren und haben "außerhalb" des containers keinerlei Bedeutung. Außerdem werden Sie feststellen, daß für dasselbe Objekt eines container bei einem nächsten Programmlauf eine vollkommen andere ID generiert wird, wodurch die IDs also auch beispielsweise für direkte Datensatzreferenzen oder andere "statische" Zwecke gänzlich unbrauchbar sind... wie man damit umgeht, besprechen wir später. Dieser Aschnitt sollte Ihnen nur klar machen, was es mit den IDs auf sich hat und Sie einen besseren Einstieg in die folgende Thematik finden lassen. Beachten Sie, daß die folgenden Beispiele darauf basieren, daß keine eigene Stammvariable verwendet wird, und daher die vom System bereitgestellte Stammvariable "EVENTDATA" verwendet wird.

für das Container-Ereignis... liefert CALL EVENTDATA Ihnen folgende Informationen...
Changed
Der Anwender hat ein Feld eines Containereintrags (Objekts) geändert
  • EVENTDATA.1 enthält die ID des geänderten Eintrags
  • EVENTDATA.2 enthält entweder "VALUE" wenn der Eintragsname geändert wurde oder einen Wert von 1 bis n um zu kennzeichnen, welcher Spaltenwert in der Detailansicht geändert wurde, also in welcher Spalte eine Wertänderung auftrat (n ist dabei die Nummer der letzten Spalte in der Detailansicht).
Enter
Der Anwender hat entweder die Eingabetaste gedrückt oder innerhalb des Containers einen Doppelklick mit der Maus durchgeführt.
  • EVENTDATA.1 enthält die ID des Eintrags, der doppelt angeklickt wurde, bzw. der beim Drücken der Eingabetaste angewählt war.
Select
Der "Zustand" eines Containerelements (Auswahl, Markierung, Cursor) hat sich geändert.
  • EVENTDATA.1 enthält die ID des Eintrags, dessen Zustand sich geändert hat
  • EVENTDATA.2 enthält eine Zeichenkette, die die Art der Änderung angibt.
    Diese Zeichenkette besteht aus einem oder mehreren der folgenden Teile:
    +SELECT
    Der Eintrag wurde ausgewählt.
    -SELECT
    Der Eintrag wurde abgewählt.
    +MARK
    Der Eintrag wurde markiert.
    -MARK
    Der Eintrag wurde demarkiert.
    +CURSOR
    Der Cursor wurde gerade auf diesen Eintrag gesetzt.
    -CURSOR
    Der Cursor wurde von diesem Eintrag fortbewegt.
ShowMenu
Der Anwender hat (durch Klicken von Maustaste 2 im container) ein Kontextmenü angefordert.
  • EVENTDATA.1 enthält entweder die ID des Eintrags, auf dem das Kontextmenü angefordert wurde oder 0 (null) wenn der Mauszeiger sich nicht über einem Eintrag befand, als im container das Kontextmenü angefordert wurde.

Natürlich gibt es noch mehr Ereignisse für einen Container, aber diese sind eher "einfacher" Natur und erfordern kein zusätzliches Ermitteln weiterer Information:

Init Der Container wird initialisiert (während des OPEN-Ereignis des Dialogs).
Scroll Der Inhalt des Conatiners wurde geblättert.
GetFocus Der Container hat den "Fokus" erhalten (= ist das "aktive" Element im Dialog geworden).
LoseFocus Der Container hat den "Fokus" verloren (= ein anderes Element im Dialog ist jetzt das "aktive").
Drop Ein Objekt wurde auf den Container gezogen und abgelegt.
Dies erfodert die Verwendung von EVENTDATA in Zusammenhang mit dem DROP-Ereignis, um nähere Informationen zu erhalten - wir besprechen Drag-and-Drop-Ereignisse (Ziehen und Übergeben) in einem späteren Artikel...

Ein letztes Beispiel zur Verwendung des EVENTDATA-Krempels: Wenn Sie beispielsweise auf ein CHANGED-Ereignis reagieren wollen, müssen Sie:

Keine Panik - wir kommen noch darauf zu sprechen, wie es in solchen Fällen eigentlich weitergeht...
Okay, super. Jetzt wo wir wissen, was es mit den Ereignissen eines Containers auf sich hat... wie zum Teufel kriegen wir denn jetzt Einträge hinein? Kommen wir also zu den Methoden (oder "Funktionen" wenn Sie's so wollen) - beachten Sie, daß es noch weitere Methoden gibt, die für einen Container verwendet werden können, es sich bei diesen aber um gängige Methoden handelt, die auch bei anderen Steuerelementen verwendet werden. Und da wir diese Methoden bereits besprochen haben, konzentrieren wir uns nun nur auf die Container-spezifischen Methoden:

Die Funktion VIEW

Fangen wir doch mit der Funktion VIEW an. Damit wird die Darstellung des Containers ausgewählt. (Erinnern Sie sich noch an die Abbildungen zu Beginn des Artikels? Genau dafür wird VIEW verwendet.) Um beispielsweise für einen Container namens "meincontainer" die einspaltige "nur-Namen"-Anzeige auszuwählen, müßten Sie codieren

call meincontainer.view "Text"

Beachten Sie, daß es für den view-Typ (die Darstellungsform) auch genügt, nur den ersten Buchstaben anzugeben - so würde also die Anweisung

call meincontainer.view "T"

dasselbe tun, wie die Anweisung vorher. Das Auswählen der anfänglichen Darstellung erledigt man idealerweise im Ereignis INIT für den Container. In diesem Fall braucht man dann auch - wie wir noch wissen - nicht den Namen des controls anzugeben, da man sich innerhalb des INIT event handlers befindet und somit "programmierungsmäßig" im Kontext des controls selbst.  Man kann das ganze also abkürzen in

call view "T"

Nun, da Sie gesehen haben, wie einfach die Funktion VIEW sein kann, werfen wir doch 'mal einen Blick auf deren vollständige Syntax:

oldview = container.view([type], [title], [bitmapwidth, bitmapheight], [expandbitmap, collapsebitmap])

Uff! Lassen Sie uns zunächst einmal wieder darauf besinnen, daß solche Funktionen verwendet werden können, um Werte zu setzen, abzufragen oder beides gleichzeitig. Man könnte die "Funktionsschreibweise" (die einen Wert zurückgibt) einsetzen, um die aktuellen Darstellungsdaten eines Containers zu ermitteln. Aber mal ehrlich - das wollen wir doch im Moment gar nicht. Wir wollen nichts abfragen, also nehmen wir einfach

call container.view [type], [title], [bitmapwidth, bitmapheight], [expandbitmap, collapsebitmap]

Wie wir bereits gesehen haben, handelt es sich beim Parameter [type] um die Darstellungsform (die Ansicht) des Containers ("text" im obigen Beispiel).
[title] bestimmt den Titel des Containers ("buddylist" in den Bildschirmabbildungen). Das ist eine Zeichenkette, die sich als Überschrift über dem eigentlichen Inhalt des Containers - den Einträgen also - befindet. Der Titel kann bestimmte Zeichen enthalten, mit denen das Format der Überschrift beeinflußt werden kann:

< Titel wird linksbündig dargestellt.
> Titel wird rechtsbündig dargestellt.
| ("pipe"-Symbol, AltGr + "<") Der Titel wird zentriert (ist übrigens auch der Vorgabewert).
_ (Unterstrich) Zwischen dem Titel und dem Inhaltsbereich des Containers wird eine Trennlinie gestellt.
; Beendet die Sequenz der Formatsteuerung, alle folgenden Zeichen werden für die eigentliche Überschrift verwendet.
Das ist optional - wird keine Semikolon verwendet, beginnt die Überschrift mit dem ersten Zeichen, das nicht als Formatzeichen erkannt wird (also jedes Zeichen, das nicht in dieser Liste enthalten ist).

Werden keine Formatzeichen im Titel verwendet, so wird die Überschrift zentriert und ohne Trennlinie dargestellt. Wenn Sie "Mein erster Container" als Überschrift in Ihrem Container sehen wollen, dazu linksbündig und mit einer Trennlinie, muß der Wert für den Parameter [title] in Ihrem VIEW-Befehl lauten:

"<_Mein erster Conainer"

Gut, der nächste Parameter lautet [bitmapwidth, bitmapheight]. Damit kann die Größe bestimmt werden, mit der alle Bitmaps im Container dargestellt werden sollen. Wird dieser optionale Parameter nicht verwendet, werden alle Bitmaps in ihrer jeweiligen Größe angeziegt. Wird für beide Werte des Parameters 0 (null) angegeben, wird die Systemvorgabe verwendet. Ich denke, daß als Einheit für diese Werte "Pixel" verwendet wird, bin mir aber in Ermangelung klarer Angaben im Hilfematerial nicht sicher. Kreiden Sie's mir bitte nicht an, daß ich es nicht getestet habe...;) Um alle Bitmaps in der Größe 24 mal 24 Pixel anzuzeigen, lautet der Wert für diesen Parameter "24,24" ...so weit ich das begriffen habe...
Dann hätten wir noch [expandbitmap, collapsebitmap]. Mit diesem optionalen Parameter können Sie alternative Symbole angeben, die in den Strukturansichten OUTLINE und HIERARCHY verwendet werden, um einzelne "Zweige" zu öffnen oder zu schließen. Sie kennen das doch wahrscheinlich aus einem WPS-Laufwerksordner mit Strukturansicht: Durch Klicken auf ein "+" könen Sie die Strukur eines Unterverzeichnisses einblenden. Wird dieser Parameter nicht angegeben, werden die Vorgabewerte verwendet (eine Schaltfläche mit einem PLUS- bzw. MINUS-Zeichen).
In DrDialog werden Bitmaps auf zwei unterschiedliche Arten angegeben - entweder über Ihren Dateinamen, wie z.B. "c:\mybmps\bitmap01.bmp", oder durch eine spezielle Schreibweise, um Bitmaps aus einer DLL zu laden - dazu kommen wir in einem späteren Teil. Im Lieferumfang von DrDialog befindet sich zum beispiel die Datei BITMAP.DLL, die eine große Zahl an Bitmaps enthält. Um das 52. Bitmap aus dieser DLL zu verwenden, würde man anstelle eines Dateinamens einfach "bitmap:#52" angeben.

Aber damit das hier nicht ausartet, vertrauen wir den Vorgabewerten für die optionalen Parameter und lassen diese unter den Tisch fallen.  Damit sähe ein vollständiger Aufruf der VIEW-Funktion wie folgt aus:

call meincontainer.view "Text", "<_Mein erster Container"

Mittlerweile wissen wir ja, daß eckige Klammern in einem Syntaxdiagramm so viel bedeuten wie "optional".  Wenn Sie also beispielsweise den Titel eines Containers ändern wollen, ohne die aktuelle Darstellungsform zu ändern, lassen Sie einfach den ersten Parameter weg, aber nicht dessen Komma damit der "Parser" der Programmiersprache versteht, daß Sie einen Parameter ausgelassen haben. Das würde dann so aussehen:

call meincontainer.view , "<_Mein erster Container"

Sieht schon etwas seltsam aus, hm? Wenn Sie aber das vereinsamte Komma auch weglassen, nimmt der Parser an, daß es sich bei"<_Mein erster Container" um den ersten Parameter - die Darstellungsart - handelt... daher ist das Komma ziemlich wichtig. ;)
Und da sind wir schon: Jetzt können Sie sich schon mal austoben im Ausprobieren von Darstellungsarten und dem Format von Containertiteln. Als nächstes schauen wir uns die Methode an, auf die schon alle sehnsüchtig warten...

Die Funktion ADD

...wird verwendet, um einen neuen Eintrag in einen Container aufzunehmen. Es handelt sich wiederum um eine Funktion. Natürlich kann man sie auch in der CALL-Schreibweise verwenden, wenn man einfach nur einen Eintrag hinzufügen will und sich nicht für den zurückgegebenen Wert in der Funktions-Schreibweise interessiert, aber wir sollten uns dieses mal anschauen, was da zurückgegeben wird.
Beim Rückgabewert handelt es sich nämlich um die ID des neuen Eintrags, die in anderen Funktionen/Methoden Verwendung findet - sogar in anderen ADD-Anweisungen: Wenn Sie mit hierarchischen Darstellungen arbeiten, muß beispielsweise mittels der entsprechenden ID der Eintrag angegeben werden, zu dem untergeordnete Einträge hinzugefügt werden sollen.
Falls Sie eine "einfache" Darstellung verwenden, die nur Namen (und optional Bitmaps) anzeigt, reicht natürlich schon

call meincontainer.add "Neuer Eintrag"

oder zusammen mit einem Bitmap namens "neu.bmp", das sich in C:\mybmps befindet...:

call meincontainer.add "Neuer Eintrag", "C:\mybmps\neu.bmp"

Um mehr Kontrolle über das Hinzufügen von Einträgen zu bekommen (z.B. um bestimmte Reihenfolgen zu erzielen), müssen wir uns die Syntax der ADD-Funktion einmal näher anschauen (das ist eine Version von mir, die sich von jener der Online-Hilfe leicht unterscheidet):

neueid = meincontainer.add(name [,bitmap] [,wo] [,referenz-id] [,info])
name Das Label (oder Titel, Name, Überschrift), das für den Eintrag im Container angezeigt wird.
bitmap Die Bitmap, die zusammen mit dem Label des Eintrags in den Ansichten name, flowedname und bitmap angezeigt wird.
wo Bestimmt die Abfolge für das Einfügen des neuen Eintrags.
Dies kann einen der Werte First, Last oder Next annehmen und wird teilweise zusammen mit dem nächsten Paramter referenz-id verwendet.
Wird referenz-id nicht benutzt, so fügt First den neuen Eintrag als ersten des Containers und Last macht ihn zum letzten (dabei handelt es sich um die Voreinstellung, wenn man wo nicht angibt).
Wenn jedoch referenz-id verwendet wird, fügt Next den neuen Eintrag nach demjenigen ein, der durch referenz-id spezifiziert wurde, während der Gebrauch von First und Last zusammen mit referenz-id den neuen Eintrag zum ersten oder letzten Kind des mit referenz-id angegebenen Eintrags macht.
referenz-id Verweist auf einen bereits im Container vorhandenen Eintrag.
Hierbei muß es sich um die ID handeln, die dem vorhandenen Eintrag vom System zugewiesen wurde, als er dem Container hinzugefügt wurde.
info Ein optionaler Parameter, der nützlich zum Abspeichern beliebiger Daten zusammen mit dem neuen Eintrag ist.
Hey! Hiermit kann man einen Schlüssel für den Datensatz in der Datenbank (oder ähnliches) für den neuen Eintag abspeichern...

Zugegeben sind wo und referenz-id ziemlich "holprig" erklärt und nicht direkt einleuchtend... lassen Sie es mich so sagen: Sofern Sie es gerade nicht mit Eltern/Kind-Beziehungen in hierarchischen Darstellungen zu tun haben, benötigen Sie referenz-id eigentlich nicht. Ebensowenig benötigen Sie die first, last oder next-Operanden des Parameters wo. Wenn Sie einen sortierten Container brauchen, führen Sie das Sortieren einfach woanders durch, füllen Sie danach den Container direkt in der gewünschten Reihenfolge und sparen Sie sich die Arbeit mit wo und referenz-id.
Für gewöhnlich verwende ich in solchen Fällen eine unsichtbare Listbox, die bereits in ihrer add-Funktion eine prima Unterstützung für Sortierungen bietet. Dann verwende ich einfach die sortierte Liste, um mit deren Einträgen den container zu füllen.
Wenn es allerdings darum geht, einen bestehenden und gefüllten Container um ein weiteres einzusortierendes Element zu erweitern, ist man mit der Verwendung der hier beschriebenen Parameter besser bedient, aber darauf kommen wir noch, da das Hantieren mit den IDs der Container-Einträge zusätzliches Wissen erfordert... das gleiche gilt für das Thema "z-order", das Ihnen vielleicht bei der Lektüre des Hilfeabschnitts aufgefallen ist. Das ist erst einmal unwichtig, lassen Sie uns fortfahren.

Container in Detailansicht

In der Detailansicht haben wir es mit zwei Aufgaben mehr zu tun, als nur mit der view und add Funktion... wir müssen das Spaltenlayout bestimmen und die Daten bereitstellen, die in den Tabellen-"Zellen" (den Feldern der Spalten) erscheinen sollen. Beide Aufgaben werden mit Hilfe der Funktion SetStem erledigt. Das Prinzip von SetStem besteht darin, alle benötigten Daten für die Spalten (sowohl Format als auch Inhalt) in einer Stammvariablen zusammenzustellen, um sie dann auf einen Schlag an den Container zu übergeben. Nur gut, daß wir schon die Grundbegriffe der Stammvariablen kennen, hm? ;)
Wie gewöhnlich beginnen wir damit, uns die Syntax des Befehls einmal anzuschauen, dann alles wegzuwerfen was uns im Moment nicht interessiert, und danach den Rest genauer zu betrachten...

call meincontainer.setstem [stammname] [, "[+/-]SELECT"] | [,"[+/-]MARK"] | [,"FORMAT" | 0 | item]

In einer besser lesbaren Form der Syntax erkennt man, daß es sich eigentlich nur um zwei Parameter handelt:

call meincontainer.setstem [stammname] [,aktion]

[stammname] ist der Name der Stammvariable, die die Daten für die durchzuführende aktion enthält. Wenn Sie keine Stammvariable angeben, wird vom System eine Stammvariable namens "STEM" erwartet - daher ist dieser Parameter als optional (in eckigen Klammern) dargestellt.
Für die durchzuführende aktion stehen folgende Parameterwerte zur Verfügung:

"[+/-]SELECT" Bedeutet: Entweder +SELECT oder -SELECT und wird dazu verwendet, alle Container-Einträge an- bzw. abzuwählen, deren IDs in den Variablen STEM.1 bis STEM.n enthalten sind.
STEM.0 enthält die Anzahl der Elemente der Stammvariablen.
"[+/-]MARK" Bedeutet: Entweder +MARK oder -MARK und dient dazu, alle Containereinträge zu (de-)markieren, deren IDs in den Variablen STEM.1 bis STEM.n enthalten sind - dabei gibt STEM.0 wieder an, aus wie vielen Elementen die Stammvariablen besteht.
"FORMAT" | 0 | item

Dieser Parameter bestimmt die eigentliche aktion, die mit Hilfe der Daten in der Stammvariablen ausgeführt werden soll.

  • FORMAT bewirkt die Formatierung der Spalten der Detailansicht. Mehr dazu nach dieser Tabelle...
  • 0 gibt an, daß die Spaltentitel der Detailansicht festgelegt werden sollen.
  • item ist die ID eines Containereintrags, dessen Spaltenwerte mit der Stammvariablen festgelegt werden sollen.

Wir werden uns vorerst nicht um die Themen SELECT und MARK kümmern, sondern uns ausschließlich dem dritten Punkt widmen. Um also einen Container in Detailansicht zu erhalten, vergeben wir ihm mittels der Funktion VIEW einen Titel (oder Überschrift) und bewirken die eigentliche Umstellung der Darstellung auf Detailansicht. Nachdem wir das erledigt haben, sind noch drei Schritte zu erledigen, um den Container fertigzustellen:

  1. Anzahl und Format der Spalten festlegen.
  2. Die Titel der Spalten (die Spaltenüberschriften) festlegen.
  3. Container-Einträge anlegen und Spaltenwerte festlegen.

Und genau das ist es, wofür die dritte Form von setstem verwendet wird:

  1. call setstem "formate", "FORMAT"
    verwendet die Elemente einer Stammvariable namens "formate", um die Anzahl und das Anzeigeformat der Spalten festzulegen.

  2. call setstem "titel", 0
    verwendet die Elemente einer Stammvariable namens "titel", um die Spaltenüberschriften festzulegen.

  3. call setstem "daten", itemid
    verwendet die Elemente einer Stammvariable namens "daten", um die Werte in den Spalten des Containereintrags mit der ID itemid festzulegen.

So weit so gut. Bevor wir zu detaillierten Beispielen kommen. müssen wir uns noch kurz damit beschäftigen, wie man Spaltenformate definiert. Jedes Element der verwendeten Stammvariable legt das Format einer einzelnen Spalte fest und besteht dabei aus einem oder mehreren der folgenden Zeichen, die jeweils eine bestimmte Formatierung bewirken:

das Zeichen... bewirkt, daß...
= (Gleichheitsszeichen) ...in der Spalte Bitmaps (anstelle von Text) angezeigt werden.
Wenn dieses Zeichen in der Formatzeichenkette nicht enthalten ist, verwendet die Spalte Textausgabe. (Der Vorgabewert für das Ausgabeformat einer Spalte ist also grundsätzlich "Text" - es sei denn, dieses Zeichen wurde verwendet.)
~ (Tilde) ...die Spalte unsichtbar ist.
Das ist sehr nützlich, um Informationen zu einem Eintrag abzulegen, die z.B. nur für programminterne Zwecke Verwendung finden.
X ...die Spalte schreibgeschützt ist.
Standardmäßig (wenn dieses Zeichen nicht verwendet wird) kann der Spaltenwert eines Container-Eintrags vom Anwender geändert werden, indem er ihn bei gedrückter <Alt>-Taste anklickt. Das gilt jedoch nur für Spalten, deren Ausgabeformat nicht "Bitmap" ist.
. (Punkt) ...der Container durch eine verschiebbare, vertikale Trennleiste in zwei Teilfenster unterteilt wird, wobei diese Spalte die letzte (rechte) Spalte des linken Teils darstellt.
Wird dieses Formatierungszeichen für keine Spalte verwendet,  wird der Container ohne Trennleiste dargestellt.
Existiert mehr als eine Spalte mit dieser Formatierung, wird nur der davon am meisten rechts stehenden Spalte eine Trennleiste erteilt.
^ ...die Spaltenwerte in ihren Zeilen nach oben ausgerichtet werden.
V ...die Spaltenwerte in ihren Zeilen nach unten ausgerichtet werden.
- (minus) ...die Spaltenwerte vertikal zentriert ausgerichtet werden.
Das ist der Standardwert;  wird weder "^" noch "V" noch dieser Wert verwendet, erfolgt eine in der Zeile vertikal zentrierte Ausrichtung.
< ...die Spaltenwerte linksbündig ausgerichtet werden.
> ...die Spaltenwerte rechtsbündig ausgerichtet werden.
| (pipe-Symbol, AltGr + "<") ......die Spaltenwerte horizontal zentriert ausgerichtet werden.
Standardwert; wird weder "<" noch ">" noch dieser Wert verwendet, erfolgt eine horizontal zentrierte Ausrichtung.
_ (Unterstrich) ...die zwischen Spalte und Spaltenüberschrift eine Linie gezogen wird.
(Hinweis: Die Linie erstreckt sich dabei nicht über die gesamte Spaltenbreite sondern lediglich über die Breite der Überschrift.)
! (Ausrufezeichen) ...die Spalte an ihrer rechten Seite eine vertikale Trennlinie erhält.

Um eine Spalte dazu zu bewegen, ihre Inhalte als Text und sowohl horizontal als auch vertikal zentriert anzuzeigen, ist also keine besondere Formatierungssequenz erforderlich, da dies alles jeweils die Vorgabewerte sind.
Um jedoch in einer Spalte linksbündige, schreibgeschützte Textausgabe und eine vertikale Trennlinie zu erreichen, müßte die Formatierungssequenz wie folgt aussehen:

"<X!"

...sieht auf den ersten Blick eher nach einem getunten Smiley aus, ist aber eine sehr effiziente Methode! ;)

Okay, Leute. Wo wir so weit gekommen sind... laßt uns einmal sehen, wie also ein Container mit Detailansicht "hergestellt" wird. Ich werde alle nötigen Schritte durchführen, um eine Ausgabe zu erreichen, die der Detailansicht-Abbildung am Artikelanfang ähnelt. Noch ein Hinweis: Die Abschnitte, die mit einem "/*" beginnen und mit der umgekehrten Zeichenfolge "*/" enden (jeweils ohne Anführungszeichen) stellen Kommentare dar und werden vom REXX-Parser ignoriert.
Wir gehen davon aus, daß ein Container-Steuerelement existiert, dessen Name "cnt" lautet:

/* alle nachfolgenden Anweisungen gehören in den 'init' event handler des containers */
/*-----------------------------------------------------------------------------------*/
/* container einstellen: Detailansicht, Titel mit Trennlinie */
call cnt.view "D", "_Adressbuch"

/* Jetzt die Spalten */
/* Wir legen eine Stammvariable für die Spaltenformate an und nennen sie "formate" */

formate.0 = 5      /* fünf spalten = fünf Elemente in der Stammvariable */
formate.1 = "<X!"   /* 1. spalte ist linksbündig, schreibgeschützt mit trennlinie */
formate.2 = "=!"     /* 2. spalte enthält bitmaps, mit trennlinie */
formate.3 = "<."      /* 3. spalte ist linksbündig mit verschiebbarer Fenstertrennleiste */
formate.4 = ">!"     /* 4. spalte ist rechtsbündig mit trennleiste */
formate.5 = "|!"      /* 5. spalte ist zentriert mit trennleiste */

/* der eigentliche Aufruf um die Formatierung an den container zu übergeben */
call cnt.setstem "formate", "F"

/* Spaltenüberschriften festlegen */
/* wir verwenden eine Stammvariable namens "titel" */
titel.0 = 5   /* wieder: 5 spalten = 5 elemente in der stammvar. */
titel.1 = "#"
titel.2 = "Symbol"
titel.3 = "Name"
titel.4 = "Telefonnr."
titel.5 = "email-Adresse"

/* der eigentliche Aufruf um die Überschriften an den container zu übergeben */
call cnt.setstem "titel", 0

/* jetzt legen wir die Einträge (Objekte) im Container an */
/* stellen Sie sicher, daß sich die Datei 'bitmap.dll' im selben Verzeichnis */
/* befindet wie die .RES-Datei dieses Beispiels! */


/* die "add" Funktion legt nur das Objekte mit Namen und Bitmap an. */
/* die zugehörigen spaltenwerte müssen mittels eines zusätzlichen Aufrufs */
/* von "setstem" übergeben werden, für den die ID des Eintrags benötigt wird */
/* daher verwenden wir beim Anlegen des Eintrags die "Funktionsschreibweise" */
/* und merken uns die ID des Eintrags...
*/
neuereintrag = cnt.add("Peter", "bitmap:#66")

/* für die "einfachen" Darstellungstypen würde das schon reichen */

/* in der Detailansicht würde allerdings bis jetzt rein gar nichts auftauchen */
/* da wir den spalten noch keine Werte mitgeteilt haben, aber das machen wir */
/* jetzt mit der Funktion setstem und einer Stammvariable namens "daten" */

daten.0 = 5    /* fünf spalten = fünf elemente */
daten.1 = 1     /* wert für spalte 1 */
daten.2 = "bitmap:#66"
daten.3 = "Peter"
daten.4 = "555-12345"
daten.5 = "peter.mustermann@einefirma.biz"
/* jetzt der eigentliche Aufruf um die Daten an den Container zu übergeben. */
/* hier müssen wir jetzt mitteilen, für welchem Eintrag diese Spaltenwerte */
/* sein sollen... wir verwenden die ID, die uns der obige Aufruf der add-Funktion */
/* in "neuereintrag" abgelegt hat... */

call cnt.setstem "daten", neuereintrag

/* Das wär's für den ersten Eintrag... legen wir noch einen an: */
neuereintrag = cnt.add("Paul", "bitmap:#64")

daten.0 = 5
daten.1 = 2
daten.2 = "bitmap:#64"
daten.3 = "Paul"
daten.4 = "555-45678"
daten.5 = "paul.jonas@emailser.ve"
call cnt.setstem "daten", neuereintrag

/* ende des beispielcodes */

Anmerkungen zum obigen Beispiel

Der Grund, warum ich das alles am Stück und dafür mit so vielen Kommentaren geschrieben habe, ist, daß Sie den ganzen Abschnitt über die Zwischenablage in den code-editor von DrDialog einfügen können - und zwar in die Ereignisbehandlungsroutine "INIT" eines Containers, der "cnt" heißen muss, damit es klappt.

Als ich das erste mal mit einer Detailansicht kämpfte, zeigte mir das Ding einfach keine Bitmaps an - egal, was ich unternahm.
Ich fand dann heraus, daß es letztlich meine Schuld war - natürlich... denn da die zweite Spalte zur Anzeige eines Bitmaps gedacht war, hielt ich es nicht für notwenig, im add-Befehl auch ein Bitmap zu übergeben, da man dieses in der Detailansicht ja ohnehin nicht sehen würde. Das war ein Fehler. Gut, vielleicht nicht unbedingt ein Fehler, aber diese Sparsamkeit hatte den Nebeneffekt, daß der Container scheinbar davon ausging, es wäre kein Bitmap vorhanden. Um es zusammenzufassen: Wenn Sie ungeachtet des Darstellungstyps irgendwas mit Bitmaps in Containern machen wollen - geben Sie immer ein Bitmap in der add-Anweisung an. ;)

Anmerkungen zum herunterladbaren Beispielprogramm

Hierbei handelt es sich um eine leicht verbesserte Version des obigen Beispiels. Die komplette add/setstem-Verarbeitung wird dabei über eine eigene Subroutine abgewickelt, was anfänglich vielleicht nicht gerade unbedingt zum besseren Verständnis beiträgt, die eigentliche Lesbarkeit und Programmstruktur aber erheblich verbessert. Außerdem demonstriert die Anwendung sehr schön, wie ein einzelner add/setstem-Komplex ausreicht, um alle Darstellungstypen zu gewährleisten.


Nächsten Monat schauen wir uns den "Rest" des Containerkrams an, beispielsweise wie man Einträge löscht, selektiert oder auf bestimmte Ereignisse reagiert.
Wenn Sie zum heutigen Thema Fragen oder Anregungen haben (oder Fehler entdeckt haben), zögern Sie bitte nicht, mich anzumailen!
Es tut mir leid, daß ich Sie jetzt schon verlassen muß, aber ich schaffe es sonst nicht mehr bis zum Redaktionsschluß und möchte meine Herausgeber nicht schon wieder hängen lassen! ;)

Daten und Quellen:

GuiObjectREXX Yahoo!-Gruppe: http://groups.yahoo.com/group/GuiObjectREXX/
Newsgruppe zur GUI-Programmierung mit REXX: news://news.consultron.ca/jakesplace.warp.visualrexx
Download von Hobbes: http://hobbes.nmsu.edu/cgi-bin/h-search?key=drdialog&pushbutton=Search
Code-Beispiel: http://de.os2voice.org/VNL/past_issues_DE/VNL0303H/cntsamp2_de.zip


[Artikelverzeichnis]
editor@os2voice.org
[Vorherige Seite] [Inhaltsverzeichnis] [Nächste Seite]
VOICE Homepage: http://de.os2voice.org