Dialogboxen

Dialogboxen sind zusammengesetzte Objekte, die der Eingabe dienen. Man kann sie als Fenster mit eingeschränkten Möglichkeiten ansehen, die in einer bestimmten Programmsituation kurzfristig erscheinen und direkt nach erfolgter Bearbeitung wieder verschwinden. Sie sind mit vom System vorgefertigten Eingabeelementen wie Eingabefeldern und Druckknöpfen versehen. Dialogboxen sind bei Macintosh und GEM die typischen Elemente für die Parametereingabe an das Programm, da diese Oberflächen die genannten Eingabeelemente nicht in einem gewöhnlichen Fenster verwalten können.

Die Programmierersicht einer Dialogbox stellt sich bei den Systemen unterschiedlich dar. Während unter OSF/Motif die Unterschiede zu einem normalen Fenster minimal sind, hat unter GEM eine Dialogbox mit einem Fenster nahezu gar nichts gemein. Für OSF/Motif wird zum Erzeugen einer Dialogbox lediglich ein besonderes Managementwidget verwendet, das die Eigenschaft der Dialogbox unterstützt, nur kurzfristig zu erscheinen und nach erfolgter Bearbeitung wieder zu verschwinden. Der Inhalt wird wie bei einem Fenster aus Elementen zusammengesetzt. GEM versteht unter einer Dialogbox eine Sammlung von Objekten, die durch Funktionsaufrufe in zwei getrennten Schritten dargestellt und abgearbeitet werden.

Während der Kontakt zum Inhalt der Dialogbox unter PM und MS-Windows durch einen regen Nachrichtenverkehr stattfindet, wird dem GEM-Programmierer die Datenstruktur der Objekte zur Verfügung gestellt. Er kann dann nach Ende der Eingabe aus dieser Struktur die vom Benutzer veränderten Einträge direkt auslesen.

Ressourcen

Dialogboxen verändern sich während des Programmlaufs in ihrem Erscheinungsbild nicht. Aus diesem Grund bieten die meisten Oberflächen die Möglichkeit, Dialogboxen außerhalb des Programms zu entwerfen und die daraus entstandene Datenstruktur durch Systemfunktionen zur Darstellung und Abarbeitung als Parameter mitzugeben. Als Hilfsmittel zur Erstellung der Datenstruktur kann vielfach ein Ressourceneditor verwendet werden, der es möglich macht, die Gestaltung am Bildschirm zu entwerfen und die vom Programm später benötigten Daten zu erzeugen. Zur Erstellung von Dialogboxen bietet der Ressourceneditor zu Anfang ein leeres Rechteck an, in das Elemente wie Buttons, Eingabefelder und Texte hineingesetzt werden. Die einzelnen Elemente erhalten eindeutige Kennzahlen, anhand derer man auf die einzelnen Elemente vom Programm aus zugreifen kann.

Die externen Ressourcendateien machen es möglich, daß man ein Programm nur durch Ändern der Ressourcendatei in einer anderen Sprache verwenden kann, da alle Benutzerdialoge, also Dialogboxen und Menüs, hier definiert werden können. Allerdings erfordert dies die konsequente Einhaltung der Regel, daß alle Ausgaben des Programms in den Ressourcen festgelegt werden. Bei GEM und X-Windows ist dies durch externe Dateien möglich, die während des Programmlaufs geladen werden. Bei Macintosh, PM und MS-Windows werden die Ressourcedaten zum ausführbaren Programm gebunden. Trotzdem ist hier eine Veränderung der Texte auch ohne die Verfügbarkeit des Sourcecode möglich, indem die eingebundenen Datenstrukturen durch andere mittels eines speziellen Linkers ersetzt werden.

Die entstandenen Daten haben einen baumartigen Charakter. Der Baum wird von außen nach innen aufgebaut. Die Wurzel entspricht dem äußeren Rahmen. Alle Elemente innerhalb des Rahmens sind die Unterknoten. Ist dieser ebenfalls ein Rahmen, kann er wiederum Unterknoten enthalten. Diese weitere Unterteilung des Baums ist erforderlich, wenn Teile einer Dialogbox separat neugezeichnet werden sollen oder wenn Elemente logisch voneinander getrennt werden müssen.

Unter GEM ist es möglich, Unterbäume neu zeichnen zu lassen. Werden durch Aktivierung von Knöpfen die Inhalte anderer Elemente verändert, lassen sich diese unauffälliger aktualisieren, wenn nur ein Teil der Dialogbox neu gezeichnet wird.

Zu den zu trennenden Elementen gehören mehrere Sätze Radio-Buttons. Bei allen Radio-Buttons einer Ebene darf immer nur ein Knopf aktiviert werden. Um mehrere Sätze von Radio-Buttons in einer Dialogbox zu realisieren, umgibt man jeden der Sätze mit einem eigenen Rahmen.


X hat eine andere semantische Vorstellung von Ressourcen. Hier beschreiben die Ressourcen nicht den Aufbau einer Dialogbox, sondern sind eine textuelle Auflistung der Eigenschaften von Widgets. In den Ressourcen finden sich die Texte, die in Knöpfen, Labeln und Menüleisten verwendet werden. Dadurch ist eine Umstellung der Sprache eines Programmes relativ einfach. Es wird dort eigentlich alles abgelegt, was für den Anwender in irgend einer Weise konfigurierbar sein soll. Dies sind auch Farben und Darstellungsarten, ob Managementwidgets in erster Linie horizontal oder vertikal ausrichten sollen und die Tastaturkürzel, mit denen Aktionen ausgelöst werden können.

Aufbau einer Ressource

Unter den meisten Oberflächen ist die interne Struktur einer Ressource für den Programmierer nicht wichtig, da z. B. unter PM für die Programmierung die gleichen Verfahren abgewickelt werden wie bei Fenstern. Hier ist zur Definition einer Dialogbox lediglich die Kenntnis der Beschreibungssprache für den Ressourcen-Compiler notwendig, sofern man kein Tool besitzt, mit dem Dialogboxen erstellt werden können. Zur Bearbeitung der Dialogboxen bildet man eine Fensterfunktion und empfängt dort die Ereignisse, die durch die Aktivitäten des Benutzers ausgelöst wurden. Will man Daten aus den Elementen auslesen oder Daten in die Elemente hineinlegen, sendet man diesen eine entsprechende Nachricht zu.

Objektliste

Unter GEM ist ohne Kenntnis des Aufbaus einer Ressource nicht viel zu erreichen. Einen Nachrichtenverkehr zwischen Applikation und Dialogbox kennt GEM nicht und die Aufruffunktion der Dialogbox liefert lediglich die Information, durch welches Element die Dialogbox beendet wurde.

Will man etwas über die sonstigen Aktivitäten des Benutzers in der Dialogbox wissen oder Elemente mit Standardwerten vorbesetzen, muß auf die interne Datenstruktur zugegriffen werden. Dies ist auch der Grund, warum in den GEM-Dokumentationen die Erläuterung der Struktur einen großen Raum einnimmt.

Die Beschreibung der Dialogbox wird in einem Array von Objekten angelegt. Der Ressourceneditor gibt jedem Element eine Konstante, die dessen Index in diesem Array darstellt. Die Struktur eines solchen Elementes enthält die Art des Objekts (Rahmen, Eingabefeld, Druckknopf etc.) und die Zustände der Elemente, also bei Knöpfen, ob sie angewählt sind oder nicht. Für Elemente, die ihre Informationen nicht direkt vollständig in der Struktur ablegen können, wie zum Beispiel der Text eines Eingabefeldes, gibt es einen Zeiger, der auf Strukturen zeigt, die ausführlichere Informationen enthält. Schließlich sind in der Struktur Indizes als Zeiger auf andere Elemente vorgesehen, mit denen die Baumstruktur der Dialogbox realisiert ist.

Der Zeiger auf externe Informationen ist ein mächtiges Mittel. Für jeden Elementtyp gibt es eine eigene Struktur, auf die dieser Zeiger verweisen kann. Ein Eingabefeld benötigt unter GEM Informationen wie zum Beispiel den Inhalt des Eingabefeldes, die Maske, die die zulässigen Eingaben beschreibt und den Titel des Eingabefeldes. Es ist sogar möglich, benutzerdefinierte Objekte in einer Dialogbox abzulegen. Dazu verweist der Zeiger auf eine Struktur, die auf eine Funktion des Programmes zeigt. Man könnte hier auch von einer Callbackfunktion sprechen. Diese Funktion kann alles mögliche tun. Sie könnte eine kleine Grafik animieren oder eine kleine Melodiefolge spielen. Fast nichts ist unmöglich. Im folgenden ist die Definition für ein Element dargestellt.


struct object {
   int ob_next, ob_head, ob_tail;         /* Verzeigerung im Baum */
   int ob_type;    /* Bezeichnet den Elementtyp, z.B. Eingabefeld */
   int ob_flags, ob_state;                  /* Stati des Elements */
   void * ob_spec;        /* der Zeiger auf eine externe Struktur */
   int ob_x, ob_y, ob_width, ob_height; /* Dimension des Elements */
} OBJECT;

Der Zeiger ob_spec zeigt auf die eben genannte externe Struktur. Es handelt sich hier um einen typfreien Zeiger. Also muß der Programmierer in Abhängigkeit der benötigten Struktur eine Typanpassung durchführen. Zur Illustration sei hier die entsprechende Struktur für ein Eingabefeld angedeutet.


struct tedinfo {
   char * te_ptext;
   char *te_ptmplt;
   char *te_pvalid;
   /* Diverse Eigenschaften, wie Zeichensatz, Farbe, etc... */
   ...
} TEDINFO;

Die Daten werden in einer Datei abgelegt, die bei GEM während des Programmlaufs geladen werden muß. In ihr befinden sich alle Ressourcen hintereinander. Der Ressourceneditor erzeugt zusätzlich eine Header-Datei, die die Indexkonstanten für alle definierten Elemente enthält, die in den Quelltext eingebunden wird. Mit deren Hilfe kann auf die Wurzeln der einzelnen Bäume und bei Bedarf auch auf die Teilbäume zugegriffen werden.

Quelltext für den Ressourcen-Compiler

Um eine Dialogbox für MS-Windows oder Presentation Manager zu definieren, wird in einer speziellen Sprache die Box beschrieben und anschließend mit dem Ressourcen-Compiler übersetzt. Das Ergebnis ist eine Datei mit der Endung RES, die mit dem Programm zusammengebunden wird. Das gleiche Verfahren wird zur Definition von Menübäumen verwendet. Eine Dialogbox könnte wie folgt definiert werden:


DLGTEMPLATE DIABOX LOADONCALL MOVEABLE DISCARDABLE
BEGIN
    DIALOG "", DIABOX,
          10, 10, 280, 100,
          FS_NOBYTEALIGN  | FS_DLGBORDER | WS_VISIBLE |
          WS_CLIPSIBLINGS | WS_SAVEBITS
    BEGIN
        CONTROL "", DIA_LISTE,
               10, 40, 250, 50,
                WC_LISTBOX, WS_TABSTOP | WS_VISIBLE
        CONTROL "Ok", DIA_OK,
               10, 10, 50, 12,
               WC_BUTTON, BS_PUSHBUTTON | BS_DEFAULT |
               WS_TABSTOP | WS_VISIBLE
        CONTROL "Abbruch", DIA_CANCEL,
               75, 10, 50, 12,
               WC_BUTTON, BS_PUSHBUTTON | WS_TABSTOP | WS_VISIBLE
    END
END

In dem obigen Beispiel ist eine Dialogbox mit einer Liste und zwei Knöpfen definiert. Hinter dem Schlüsselwort DIALOG bzw. CONTROL steht zunächst die Beschriftung des Kontrollelementes, gefolgt von der Konstanten, über die auf dieses Element später zugegriffen wird. Als nächstes erscheinen X- und Y-Position, gefolgt von Breite und Höhe. Bei den Kontrollelementen folgt darauf der Kontrollelementetyp und schließlich erscheint ein Wert, der die Darstellungsart angibt. Hierzu werden vom System definierte Konstanten mit Oder verknüpft.

Bei der Betrachtung fällt sofort auf, daß hier mit konstanten Positionen gearbeitet wird, was dem Prinzip der Koordinatenunabhängigkeit klar widerspricht. Sofern man unter diesen Systemen die Dialogboxen nicht durch das Erzeugen der Kontrollelemente als Fenster selbst durchführen will, wird man an dieser Stelle keine andere Wahl haben. In dem besonderen Fall einer Dialogbox ist dies allerdings auch nicht so problematisch wie bei einem Fenster. Die Dialogbox bleibt in ihrer Größe konstant. Damit kann und soll sie höhere Auflösungen nicht nutzen. Die Maßzahlen in der Definition beziehen sich allerdings nicht auf Bildschirmpunkte, sondern werden durch die verwendete Standardschrift festgelegt. Damit ist zumindest eine gewisse Koordinatenunabhängigkeit gewährleistet.

Widgeteigenschaften

Wie schon erwähnt, sind Ressourcen unter X die textuelle Beschreibung von Widgeteigenschaften. Die einfachsten Eigenschaften, die auch fast jeder Programmierer in Ressourcen ablegen wird, sind diejenigen, die die Darstellung betreffen, wie Farben, Zeichensätze und Beschriftungen.

Es gibt mehrere Ressourcedateien, die für eine Anwendung relevant werden können. Dabei sind grundsätzlich die Ressourcen für das X-System von den applikationsspezifischen zu unterscheiden. So kann in der Datei Xconfig beispielsweise die Hintergrundfarbe für alle Widgets festgelegt werden. In der applikationsspezifischen Datei können diese Werte wieder überschrieben werden. Als Name der Datei wird der Name verwendet, den das Programm bei der Applikationsanmeldung angibt. Hier werden die speziellen Eigenschaften der im Programm verwendeten Widgets abgelegt. Der Ort dieser Dateien hängt von der Installation, der Release und verschiedenen Umgebungsvariablen ab, so daß zum Auffinden der Dateipfade ein Blick in die Systemdokumentation unumgänglich ist. In den meisten Fällen befinden sich die Ressourcendateien im Verzeichnis /usr/lib/X11/app-defaults.

Jeder Benutzer kann in seinem Heimatverzeichnis eigene Ressourcedateien anlegen. Dies hat den Vorzug, daß man frei experimentieren und in der allerletzten Not einfach die entsprechenden Dateien löschen kann. Außerdem kann sich so jeder Anwender sein Programm nach eigenen Vorstellungen anpassen. Dies ist in einer Multiuser-Umgebung schon allein deswegen sinnvoll, weil die farbliche Präferenz des Superusers nicht unbedingt den Geschmack der gesamten Firma darstellt.

Um die Eigenschaft eines Widgets in der Ressourcedatei zu verändern, werden die Namen der Widgets in hierarchischer Reihenfolge aufgezählt und die gewünschte Eigenschaft angehängt. Alle Angaben werden durch Punkte getrennt. Danach folgt ein Doppelpunkt mit dem neuen Wert, den die Eigenschaft annehmen soll. Da die Ressourcedatei zur Laufzeit des Programmes ausgeführt wird, müssen hier natürlich die Widgetkennungen, die bei Erzeugung als Zeichenkette übergeben werden und nicht die Variablennamen innerhalb des Programmtextes angegeben werden.

Um einen Menüpunkt grün zu färben, würde man die folgende Zeile in der Datei XUnfug oder in der Datei Xconfig hinterlegen:


XUnfug.formular.menueleiste.dateimenue.laden.background: green


Hier sieht man den vollständigen Weg durch die Wigethierarchie, angefangen mit der Applikationskennung über das äußerste Widget formular bis hin zum Menüpunkt laden. Hieran schließt sich die Eigenschaft background an, die man im Programm würde mit XtNbackground bezeichnen würde. Ein Blick in die Headerdateien offenbart die Tatsache, daß alle Eigenschaften als Zeichenkettenkonstanten definiert sind, so daß sie das X-System auch während der Laufzeit interpretieren kann.

Da man auch Sterne als Platzhalter einsetzen kann, ist es möglich, mehrere Widgets mit einer Zeile zu setzen. Mit der folgenden Zeile würden alle Beschriftungen der Applikation XUnfug bis hin zu den Druckknöpfen und Menüeinträgen auf das Wort ``Bloedsinn'' geändert.


XUnfug*label: Bloedsinn


Da der Benutzer Zugriff auf jede Widgeteigenschaft hat, die nicht im Programm explizit festgelegt ist, kann er natürlich das Programm totkonfigurieren. Der Philosophie von X entspricht es allerdings, möglichst viele Ressourcen in den entsprechenden Dateien festzulegen und damit dem Benutzer soviele Variationsmöglichkeiten an die Hand zu geben wie möglich. In dem Beispielprogramm zur Erstellung des Widgetbaums mittels Form-Widget auf Seite würde der Programmierer normalerweise die Verkettungen in der Applikationsressourcedatei festlegen.

Umgang mit Ressourcen im Programm

An verschiedenen Stellen war schon die Einstellung der Ressourcen vom Programm in den Listings gezeigt worden, ohne darauf näher einzugehen. Im Mittelpunkt des Setzens und Lesens von Ressourcen steht ein Array von Records, das einerseits die Eigenschaft und andererseits den Wert der Eigenschaft aufnimmt. Aus Sicht des Programmierers ist die genaue interne Struktur allerdings von geringem Interesse. Zum Setzen der Eigenschaften wird das Array mit Hilfe der Intrinsics-Funktion XtSetArg aufgefüllt. Die Funktion XtSetValues übergibt die im Array eingestellten Werte.

Arg arg[MAX];
Cardinal n;

    n=0;
    XtSetArg(arg[n], Eigenschaft,    Inhalt); n++;
    XtSetArg(arg[n], XmNlabelString, LabelText); n++;
    XtSetValues(Widget, arg, n);

Für das Auslesen von Widget-Eigenschaften wird wiederum das Array gefüllt. Allerdings wird diesmal nicht der Wert als dritter Parameter übergeben, sondern die Adresse der Variablen, die die Eigenschaft des Widgets aufnehmen soll.

Arg arg[1];
Boolean gesetzt;

    XtSetArg(arg[0], XmNset, &gesetzt);
    XtGetValues(meinToggleWidget, arg, 1);

In dem Beispiel wird der Status eines Toggle-Widgets ausgelesen. Nach Aufruf der Funktion XtGetValues ist in der Variablen gesetzt der Status des Knopfes zu finden.


Die Eigenschaften, die der Funktion XtSetArg übergeben werden, sind nichts anderes als durch Konstanten beschriebene Zeichenketten. So verbirgt sich hinter der Konstanten XmNlabelString nichts anderes als:


#define XmNlabelString             "labelString"

Der Wert wird in der Arg-Struktur als Zeiger gehalten, der auf einen beliebigen Wert deuten kann. Die Typenanpassung wird zur Laufzeit durchgeführt.


Es sollte an dieser Stelle nicht unerwähnt bleiben, daß es eine zweite, neuere Methode gibt, die ohne das Array auskommt und mit der es möglich ist, mehrere Widget-Eigenschaften mit einem Funktionsaufruf festzulegen. Tatsächlich spart diese Alternative ein wenig Schreibarbeit, läuft ein wenig langsamer und arbeitet ansonsten nach dem gleichen Prinzip.

Steuerung einer Dialogbox über Dialogfunktionen

Der GEM-Programmierer ruft zur Steuerung einer Dialogbox eine Systemfunktion auf, die die Eingabekontrolle übernimmt. Das bedeutet, daß die Applikation für diese Zeit keine Ereignisse mehr empfangen und bearbeiten kann. Aus den vom Ressourceneditor vergebenen Indexkonstanten können mit Hilfe der Funktion rcsc_gaddr Zeiger auf die Objektstrukturen errechnet werden, die dann je nach Anwendung als Parameter an die Funktionen objc_draw (zum Zeichnen einer Dialogbox), form_do (zum Abarbeiten einer Dialogbox) oder menu_bar (zum Erstellen eines Menübaums) übergeben werden. Oder sie können als Basiszeiger auf das Objektarray verwendet werden, auf das mit den Konstanten der Elemente als Index zugegriffen wird. Letzteres ist hier an einer Funktion zum Belegen eines Default-Wertes in ein Eingabefeld gezeigt.


BelegeEingabeFeld (OBJECT *Dialogbox, int ObjektIndex, char *Inhalt)
{
TEDINFO *Eingabefeld;

   Eingabefeld = (TEDINFO *) Dialogbox[ObjektIndex].ob_spec;
   strcpy(Inhalt, Eingabefeld->te_ptext);
}

    . . .
    BelegeEingabeFeld(Dialogadresse, EINGABE, "Vorgabetext");
    . . .

Der Zugriff auf die eingebene Zeile im Eingabefeld erfolgt, indem der Zeiger auf die TEDINFO-Struktur ermittelt wird. In der TEDINFO-Struktur wird der Zeiger auf den Inhalt des Eingabefeldes verwendet, um den übergebenen String einzukopieren. Die Konstante EINGABE ist diejenige, die bei der Erstellung der Dialogbox durch den Ressourceneditor für das Eingabefeld bestimmt wurde. Sie wird als Parameter ObjektIndex übergeben.

Laden der Ressourcen

Die Ressourcendatei wird zur Laufzeit geladen und die Zeiger auf die Grundobjekte ermittelt. Meist erfolgt zu diesem Zeitpunkt auch der Aufbau der Menüleiste.


OBJECT Dialogadresse;

   if ( ! rcs_load("MEINPRG.RSC") ) {
      /* Datei fehlt... */
   } else {
      rcsc_gaddr( 0, DIALOGBOX, &Dialogadresse);
   }

Zeichnen und Abarbeiten

Soll die Dialogbox im Programm abgearbeitet werden, wird sie gezeichnet und anschließend durch Aufruf der Funktion form_do dem System die Bearbeitung der Eingaben überlassen. Während dieser Zeit übernimmt das System die Kontrolle über das Programm. Die Funktion form_do endet dann, wenn der Benutzer ein Element angesprochen hat, das einen sogenannten Exitstatus besitzt. Mit diesem muß mindestens ein Element bei der Definition im Resouceneditor versehen worden sein, sonst endet die Dialogbox nie. Im Normalfall hat der OK- und der Abbruch-Knopf jeweils den Exitstatus. Der Index des angewählten Exitelemtes ist der Rückgabewert der Funktion form_do. Mit einer anschließenden Fallunterscheidung kann festgestellt werden, ob der Benutzer die Eingabe übernehmen oder verwerfen möchte.


Auf den ersten Blick scheint es keine Möglichkeit zu geben, auf die Dialogbox während ihrer Abarbeitung Einfluß zu nehmen. Tatsächlich ist dies aber möglich, da fast jedem Element der Dialogbox der Exitstatus gegeben werden kann. Man kann sogar einen Touch-Exit-Status vergeben, der form_do bereits unterbricht, wenn nur der Mauszeiger über dem Element steht. Da die Ausführung und die Darstellung unabhängig voneinander laufen, steht die Dialogbox immer noch auf dem Bildschirm, da sie noch nicht explizit entfernt wurde. Hier kann nun der Programmierer diverse Aktionen ablaufen lassen und anschließend form_do erneut starten. Aus Benutzersicht ist diese Unterbrechung nicht wahrnehmbar.

Dieses Vorgehen wird zum Beispiel benutzt, wenn durch einen Knopfdruck Standardvorgabewerte in anderen Elementen gesetzt werden sollen. Der Standardknopf erhält den Exitstatus, wodurch form_do unterbrochen wird. Anhand des Rückgabewertes erkennt das Programm den Druckknopf für die Standardvorgabe und belegt die entsprechenden Elemente. Die betroffenen Teilbäume werden mit objc_draw neu gezeichnet und die Funktion form_do erneut aufgerufen. Dieser Vorgang ist hier im Beispiel skizziert:


   objc_draw (&DruckPara, . . .);
   do {
      exitbutton = form_do(DruckPara);
      switch (exitbutton) {
      case OK:  /* uebernehme Eingabe */
         /* hier erfolgt die normale Beendigung und das Auslesen
          * des Textfeldes */
         . . .
         break;
      case DINA4: /* Standardknopf */
         BelegeEingabeZeile(ZEILEN, DINA4TEXT);
         objc_draw(&InnenRahmen, . . . );
         break;
      case DINA5: /* Standardknopf */
         BelegeEingabeFeld(ZEILEN, DINA5TEXT);
         objc_draw(&InnenRahmen, . . .);
         break;
      case CANCEL: /* vergiss alle Eingaben */
         break;
      }
      . . .
   } while (exitbutton != OK) && (exitbutton != CANCEL);

Mit derselben Strategie sind auch geschachtelte Dialogboxen möglich, obwohl GEM diese eigentlich nicht unterstützt. Soll ein Element eine weitere Dialogbox auslösen, wird es im Ressourceneditor mit dem Exitstatus versehen. Stellt das Programm anhand des Rückgabewertes von form_do fest, daß dieses Element angewählt wurde, startet es die nächste Dialogbox. Nach Beendigung der inneren Dialogbox muß die äußere Dialogbox allerdings mit objc_draw neu gezeichnet werden, da es für Dialogboxen unter GEM keine REDRAW-Nachricht gibt.

Selbst neue Kontrollelemente können so erzeugt werden. GEM kennt von Haus aus kein Listenelement. Die Liste selbst kann durch Radio-Buttons ohne Rand simuliert werden, denen man die Einträge als Text unterschiebt. Schwieriger ist die Simulation des Rollbalkens. Dazu werden zwei verschiedenfarbige Rechteckelemente übereinandergelegt und mit dem Exitstatus versehen. Wird der innere Schieber angeklickt, führt das Anwenderprogramm das Verschieben des Schiebers aus, bis die Maustaste losgelassen wird. Dann verändert es die Position des Rahmenelements in der Objektstruktur, baut die Listenelemente neu auf und löst ein Neuzeichnen mit obj_draw aus. Anschließend wird form_do wieder aufgerufen.

Steuerung einer Dialogbox über Nachrichten

Unter PM ist eine Dialogbox vergleichbar mit einem besonderen Fenster, das eingeschränkte Randelemente beinhaltet. Im Gegensatz zu GEM werden die Informationen über die Dialogbox normalerweise nicht aus einer separaten Datei geladen, sondern dem Programmcode hinzugebunden. Zum Erzeugen einer Dialogbox führt die folgende typische Sequenz:


HWND DialogHandle;

   DialogHandle = WinLoadDlg (HWND_DESKTOP, hwndElternFenster,
            (PFNWP) DialogFunktion, NULLHANDLE, DIALOG_ID, NULL);
   WinProcessDlg (DialogHandle);
   WinDestroyWindow (DialogHandle);

Wie bei den Fenstern wird eine Funktion angemeldet, die alle Nachrichten des Systems auffängt, die die Dialogbox betreffen. Diese sieht auch im Programmcode einer Fensterfunktion sehr ähnlich. Der auffälligste Unterschied besteht darin, daß die Erzeugungsnachricht bei Fenstern WM_CREATE und bei Dialogboxen WM_INITDLG heißt und die Standardfensterfunktion hier WinDefDlgProc heißt. Der nicht so offensichtliche Unterschied liegt darin, daß ein Standardfenster aus zwei Fenstern besteht, nämlich dem Rahmenfenster und dem Arbeitsbereich. Ein Dialogfenster ist ein Ganzes und hat entsprechend nur ein Handle.

Bei einer Dialogbox unterscheiden sich Besitzer und Eltern des Fensters. Der Besitzer eines Dialogfensters ist das Fenster, aus dessen Kontext die Dialogbox aufgerufen wird. Das Elternfenster ist dagegen der Desktop, der durch die Konstante HWND_DESKTOP angegeben wird. Da ein Kindfenster immer durch die Ausdehnung des Elternfensters beschnitten wird, ermöglicht diese Trennung, daß die Dialogbox über den Rand des Besitzerfensters hinausragt.

Die Funktion WinProcessDlg hat eine ähnliche Bedeutung wie form_do bei GEM. Die Dialogbox wird abgearbeitet und alle Aktionen in den anderen Fenstern der Applikation sind gesperrt. Allerdings wird die Auswertung der Benutzereingaben nicht im Anschluß ausgeführt, sondern werden durch die dialogeigene Fensterfunktion bearbeitet. Innerhalb der Dialogfensterfunktion bekommt, je nach den dort behandelten Nachrichten, die Applikation immer wieder die Kontrolle.


MRESULT EXPENTRY DialogFunktion
         (HWND hwnd, ULONG msg,
          MPARAM p1, MPARAM p2)
{
   switch (msg) {
   case WM_INITDLG:  . . .  break;
   case WM_CONTROL:
      switch (SHORT1FROMMP (p1) ) {
      case ID_ENTRY:        /* Das Eingabefeld ... */
         switch ( SHORT2FROMMP (p1 ) {
         case EN_KILLFOCUS: /* ... verliert den Fokus */
            break;
         case EN_SETFOCUS:  /* ... erhaelt den Fokus */
            break;
      }
   }
   case WM_COMMAND:
      switch (SHORT1FROMMP (p1) ) {
      case ID_OKBUTTON:
         /* An dieser Stelle werden die Daten ausgelesen */
         . . .
         WinDismissDlg ( hwnd, TRUE);
         return (MRESULT) FALSE;
      case ID_CANCEL: /* Abbruch */
         WinDismissDlg ( hwnd, TRUE);
         return (MRESULT) FALSE;
      }
   }
   return WinDefDlgProc(hwnd, msg, p1, p2);
}

Datenaustausch mit der Dialogbox

Bei Eintreffen der WM_INITDLG-Nachricht wurde die Dialogbox soeben erzeugt. Dieser Moment wird verwendet, um die Kontrollelemente mit Vorgabewerten zu besetzen.

Das Auslesen der Benutzereingaben aus den Kontrollelementen erfolgt normalerweise nach dem Drücken des Ok-Knopfes, welches sich durch eine WM_COMMAND-Nachricht bemerkbar macht. Es kann auch eine vergleichbare Operation wie beispielsweise das Doppelklicken auf ein Element einer Listbox als Bestätigung interpretiert werden. Dieses Verhalten macht Sinn, wenn die Dialogbox außer der Listbox fast keine Kontrollelemente enthält. Der Doppelklich auf ein Listenelement löst eine WM_CONTROL-Nachricht aus, in deren Parameter der Wert LN_ENTER zu finden ist.

Zum Füllen der Elemente mit Vorgabewerten oder zum Auslesen der Benutzereingaben werden den Elementen Nachrichten gesandt. Am Beispiel einer Listbox ist hier der Umgang mit einem Kontrollelement gezeigt.


  /* Loesche den Inhalt der Listbox */
  WinSendDlgItemMsg(DialogHandle, ID_LISTBOX, LM_DELETEALL, 0, 0);
  /* Haenge eine Zeile an das Ende der Liste ein */
  WinSendDlgItemMsg(DialogHandle, ID_LISTBOX, LM_INSERTITEM,
                     MPFROMSHORT(LIT_END), MPFROMP((PSZ)Zeile));

  . . .
  /* Ermittle das selektierte Element einer Listbox */
  Index = WinSendDlgItemMsg(DialogHandle, ID_LISTBOX,
                            LM_QUERYSELECTION, 0, 0);

Der erste Parameter ist das Handle der Dialogbox, der zweite ist die Konstante, die das Kontrollelement bezeichnet. Im dritten Parameter wird die Aktion mit dem Element übergeben. Hier gibt es für jedes Element spezifische Systemkonstanten. Der Präfix LM_ ist für Listboxen vorgesehen. DELETEALL löscht den Inhalt der Listbox, INSERTITEM fügt neue Elemente in die Listbox und QUERYSELECTION ermittelt das vom Benutzer markierte Element und gibt dessen Index als Rückgabeparameter der Funktion WinSendDlgItemMsg zurück. Einige Aktionen benötigen zusätzliche Parameter, wie das Eintragen neuer Zeilen. In diesem Fall wird im ersten Parameter die Art der Einsortierung angegeben und im zweiten Parameter die Adresse der Zeichenkette. Auch für die Art der Sortierung wird beim Einfügen durch Systemkonstanten festgelegt.


Es muß immer gewährleistet sein, daß ein Abbruch der Dialogbox die alten Zustände erhält. Aus diesem Grund muß das Programm auch alle Inhalte der Dialogbox noch in getrennten Speicherbereichen zur Verfügung halten.

Das Beenden der Dialogbox wird von der Dialogfensterfunktion durch den Aufruf von WinDismissDlg explizit ausgelöst. Im Gegensatz zu GEM gibt es keine Objekte, die implizit die Eigenschaften haben, eine Dialogbox automatisch zu schließen, sondern dies muß das Programm selbst übernehmen.

Reaktionen auf Kontrollelemente

Findet eine Benutzeraktionen statt, wird die Nachricht WM_CONTROL bei Betätigung eines Druckknopfes oder die Nachricht WM_COMMAND bei der Bearbeitung der anderen Kontrollelemente versandt. Die übrigen Nachrichten entsprechen denen, die auch bei Standardfenstern auftreten.

Wurde eine WM_CONTROL- oder WM_COMMAND-Nachricht empfangen, werden die Parameter der Nachricht näher betrachtet. Das erste 16-Bit-Wort des ersten Parameters enthält die vom Programmierer vergebene Kennummer des Objekts, das die Nachricht ausgelöst hat. Im Gegensatz zu GEM ist das Eintreffen einer Nachricht nicht das Zeichen, daß eine Dialogbox beendet wurde, sondern nur, daß der Benutzer an dem Objekt ``herumgefummelt'' hat.

An dem Beispiel ist zu sehen, daß einzelne Elemente auch mehrere Nachrichten auslösen können. Oben ist dies für ein Eingabefeld ausgeführt. Ein Eingabefeld sendet typischerweise zwei Nachrichten: Es ist aktiviert worden, indem der Benutzer per Maus oder Tabtaste auf das Feld gesprungen ist, oder es ist verlassen worden. Man spricht hier vom Eingabefokus. Die Nachrichten haben den Zweck, die Steuerung der Dialogbox zu variieren. Beim Verlieren des Fokus könnte eine Plausibilitätsprüfung der Eingabe erfolgen. Allerdings sollte bei einer Dialogbox die Kontrolle der Eingabe üblicherweise erst bei Bestätigung der Box erfolgen, da eine Dialogbox normalerweise nur eine beschränkte Anzahl von Eingabeelementen besitzen sollte.


Durch die Vielzahl der Ereignisse ist die freie Gestaltung von Aktionen während des Ablaufs von Dialogboxen kein Problem. So ist die Verschachtelung von Dialogboxen oder sonstige Eingriffe in den Ablauf der Dialogbox wesentlich einfacher als unter GEM möglich.

Nichtmodale Dialogboxen

Während bei GEM eine Dialogbox die Anwendung blockiert, bieten die anderen Oberflächen die Möglichkeit, eine Dialogbox nichtmodal zu verwenden. Das bedeutet, daß der Benutzer bei Erscheinen der Dialogbox in ein anderes Fenster klicken und damit die Eingabe in die Dialogbox zurückstellen kann. Die Philosophie von X-Windows geht sogar von diesem Feature als dem Normalfall aus. Beim Macintosh hat die Verwendung einer modalen Dialogbox die Konsequenz, daß andere Programme nicht aktivierbar sind, so daß man auch hier, wenn immer möglich, eine nichtmodale Dialogbox verwenden sollte. Eine Dialogbox, die die gesamte Oberfläche sperrt, nennt man systemmodal. Man kann sie beispielsweise für Passworteingaben verwenden.

Tatsächlich widerspricht eine Dialogbox, die alle anderen Aktionen blockiert, dem Prinzip, daß das Programm dem Benutzer bei der Bedienung freie Hand läßt. Dem steht die Besonderheit einer Dialogbox gegenüber, daß sie nur kurzzeitig auf dem Bildschirm eine Eingabe entgegen nehmen und gleich wieder verschwinden soll. Eine nichtmodale Dialogbox verhält sich hier mehr wie ein Fenster, das der Benutzer längere Zeit auf dem Bildschirm stehen lassen kann, bis er es nicht mehr benötigt. Die Forderung nach nichtmodalen Dialogboxen zielt eher gegen den Mißbrauch der Dialogbox als Mittel, dem Benutzer die Reihenfolge seiner Eingaben vorzuschreiben. Ein solcher Mißbrauch liegt eindeutig vor, wenn eine Funktion abläuft, die mehrfach nacheinander alle benötigten Eingaben per modaler Dialogbox einfordert, bis sie endlich alle Parameter beisammen hat und durchstartet.

Es gibt Fälle, wo die Verwendung einer Dialogbox vor dem Start einer Funktion üblich ist, wie das Erscheinen der Dateiauswahlbox vor dem Laden einer Datei. Dies ist auch sinnvoll, da es der Benutzer gewohnt ist, direkt vor dem Sichern nach dem Dateinamen gefragt zu werden.


Es ist möglich, modale Dialogboxen durch nichtmodale Dialogboxen zu ersetzen. Es gilt lediglich zu verhindern, daß der Benutzer nach Zurückstellen der Dialogbox zum Beispiel durch Aufrufen des gleichen Menüs ein zweites Mal die gleiche Dialogbox erzeugen kann. Der einfachste Weg ist das Sperren des Menüpunktes. Der andere, etwas elegantere Weg ist, daß sich das Programm merkt, daß die Dialogbox gestartet, aber noch nicht beendet ist, und bei der zweiten Aktivierung des Menüpunktes die noch im Hintergrund liegende Dialogbox wieder in den Vordergrund bringt.

Einen wichtigen Aspekt der Verwendung nichtmodaler Dialogboxen führt Marcellus Bucheit an. Er betrachtet die Zustandsänderung der Applikation und führt am Beispiel des Sicherns eines Textes aus, daß bei Verwendung einer modalen Dialogbox die Applikation zu ihrer Laufzeit immer in einem eindeutigen Zustand ist. Entweder der Text wurde gesichert oder der Anwender hat die Aktion abgebrochen und der Text ist weiterhin ungesichert. Wird dagegen eine nichtmodale Dialogbox verwendet, kann die Applikation quasi zwischen diesen beiden Zuständen reaktiviert werden und der zu speichernde Text gelöscht und ein anderer geladen und verändert werden. Beim erneuten Aufruf des Sicherns würden nun zwei Dialogboxen existieren von denen die erste einen nicht mehr vorhandenen Text betrifft. Er schlägt darum vor, in solchen Fällen eine modale Dialogbox zu verwenden, was in der Praxis auch normalerweise üblich sei.3

Tatsächlich ist die eindeutige Zustandsdefinition der Applikation bei Verwendung nichtmodaler Dialogboxen problematisch. Dies ist leicht einsehbar, wenn man sich vorstellt, daß während des Sicherns eines Textes bei einer Applikation mit mehreren Texten in unterschiedlichen Fenstern vom Benutzer ein anderer Text in den Vordergrund geholt wird und in die Dialogbox zurückgekehrt wurde. Sollte man eine solche Aktion so interpretieren, daß der Benutzer nun einen anderen Text sichern möchte? Beim im Beispiel angesprochenen Fall des Löschen eines Textes wäre eine logische Reaktion der Applikation das Entfernen der Dialogbox und die Rückfrage, ob der ungesicherte Text verworfen werden soll. Das Problem ist eigentlich nicht, daß die Applikation in undefinierte Zustände gerät, sondern daß durch die Verwendung nichtmodaler Dialogboxen einige zusätzliche Zustände der Applikation definiert und auch vom Programmierer berücksichtigt werden müssen. Die Konsequenz kann sein, daß es besser ist, auf modale Dialogboxen zurückzugreifen, um eine klare Benutzerführung zu erreichen.

Um diesen Problemen zu entgehen, empfielt sich die Trennung von Aktion und Parametereingabe. Eine durch einen Menüpunkt angewählte Aktion verwendet die zuletzt eingegebenen Parameter als Vorgabe und wird ohne Unterbrechung durchgeführt. Die Eingabe der Parameter kann dann durch eine nichtmodale Dialogbox durchgeführt werden. Solange diese Eingabe noch nicht bestätigt wurde, gelten die alten Parameter. Sobald die Eingabe bestätigt wird, werden einfach die Parameter ersetzt. In den meisten Fällen ist dieses Verfahren durchaus praktikabel. Wie schon erwähnt, ist die Trennung von Aktion und Parametereingabe allerdings im besonderen Fall des Sicherns auf Datei eher ungewöhnlich. Aus diesem Grund ist eine modale Dialogbox in solchen Fällen sicherlich sinnvoll.

Schwierigkeiten können sich ergeben, wenn eine Dialogbox Werte enthält, die an anderer Stelle vorbelegt werden. Wird eine solche Dialogbox in den Hintergrund geschickt und anschließend der Vorgabewert in einem anderen Dialog geändert, kennt die Hintergrundbox diesen Zustand nicht. Dieser müßte dann auch in einer ggf. im Hintergrund liegenden Dialogbox verändert werden.


Wird bei einem Programm für den Presentation Manager eine Dialogbox lediglich geladen, erscheint sie bereits auf dem Bildschirm und ist nicht modal. Sie arbeitet wie ein gewöhnliches Fenster, da die Dialogfensterfunktion zu diesem Zeitpunkt bereits aktiv ist. Durch den Aufruf WinProcessDlg wird das System angewiesen, nun die Bearbeitung zu übernehmen, bis die Dialogbox aus der Fensterfunktion beendet wird. Dadurch wird die Dialogbox automatisch modal, da keine anderen Fenster mehr aktivierbar sind. Eine Dialogbox kann allerdings nie hinter ihrem Elternfenster verschwinden, sondern sie liegt deaktiviert über ihrem Besitzerfenster. Aus diesem Grund sollten nichtmodale Dialoge unter PM unbedingt verschiebbar sein, da sie sonst eine Arbeit im Besitzerfenster zum Blindekuhspiel werden läßt. Es ist auch nicht möglich, das Besitzerfenster unter dem Dialog wegzuschieben, da der Dialog mitverschoben wird.


Beim Macintosh ist dagegen ein unterschiedlicher Ansatz für die Bearbeitung der Nachrichten notwendig. Bei einer modalen Dialogbox wird die Bearbeitung mit einer Art lokalen Ereignisabfrage durchgeführt bis der Benutzer die Dialogbox beendet. Bei einer nichtmodalen Dialogbox müssen dagegen auch die anderen Ereignisse, wie beispielsweise das Wechseln in andere Fenster bearbeitet werden. Man muß also entweder die Hauptereignisschleife um die Bearbeitung der Dialogboxereignisse erweitern oder die lokalen Dialogauswertung muß auch die Verarbeitung anderer Ereignisse übernehmen. Da letzterer Ansatz spätestens bei der Verwendung mehrerer nichtmodaler Dialogboxen nicht mehr durchführbar ist, wird die Bearbeitung der nichtmodalen Dialogboxen üblicherweise in der Hauptereignisschleife abgehandelt.

Der Grund für diesen Unterschied zwischen PM und Macintosh liegt darin, daß beim Macintosh das System die Verteilung der Nachrichten an bearbeitende Objekte nicht selbstständig durchführt.


Bei X stellt sich die Situation im Vergleich zum Macintosh genau umgekehrt dar. Hier ist die Erstellung einer nichtmodalen Dialogbox der Normalfall. Der Eingriff des Programmes ist notwendig, wenn man eine Dialogbox zu modalem Verhalten bewegen will. Dazu wird bei OSF/Motif die Eigenschaft XmNdialogStype der DialogShell zu verändern. Motif unterstützt hier wie PM applikationsmodale und systemmodale Dialogboxen. Ansonsten verhalten sich programmtechnisch modale und nichtmodale Dialogboxen gleich.

Wie schon beim Presentation Manager gesehen, stoppt die Erzeugung einer nichtmodalen Dialogboxen nicht den Programmablauf. Das Programm erzeugt die Dialogbox, überläßt sie ihrem Schicksal und kehrt zur Hauptereignisschleife zurück. Da bei X nun aber die modalen Dialogboxen auf die gleiche Weise behandelt werden, tritt ein Problem auf, wenn ein Programm warten soll, bis die Dialogbox beendet wurde. Bei den Oberflächen, die auf das Ergebnis einer Dialogbox warten können, stellt sich die Struktur wie in Abbildung dar.

Wartende modale Dialogbox

Die Box wird dargestellt und das Programm wartet bis der Benutzer seine Entscheidung getroffen hat. Danach wird das Programm fortgesetzt.

Die Denkweise, die für die Lösung unter X benötigt wird, ist umgekehrt. Man sieht nicht die Benutzereingabe als Folge eines Programmablaufs an, der nach erfolgter Eingabe fortgesetzt wird. Statt dessen ist der Programmablauf, nämlich das Löschen der Daten, die Folge einer Benutzeraktion. Das Starten der Dialogbox ist eine Folge der Menüanwahl. Damit ist die Menüauswahl abgeschlossen. Die Aktion des Löschens ist eine Folge des Drückens des OK-Knopfes der Dialogbox. Also gehört das Löschen der Daten in die Callbackfunktion des OK-Knopfes. Also wird die Programmsequenz, die nach Ende der Dialogbox ablaufen soll, in die Callbackfunktion derjenigen Knöpfe verlegt, die die Dialogbox beenden. Einen Überblick bietet Abbildung .

Ereignisorientierte Betrachtung einer wartenden Dialogbox

Standarddialoge

Alle Oberflächen bieten Standarddialoge an. Die einfachsten Typen sind Alarm- oder Nachrichtenboxen. Diese verfügen über ein Anzeigeelement, das den Text darstellt und ein oder mehrere Knöpfe. Mit diesen sind einfache Rückfragen von der Art ``Programm beenden?'' leicht zu realisieren. Auch für die Fehlersuche sind diese Dialogboxen sehr praktisch, da sie leicht den fehlenden Ausgabebefehl ersetzen können. Durch Verwendung der Standardfunktion sprintf kann man sich eine Zeichenkette aufbereiten, die auch andere Werte als Zeichenketten anzeigt. Die folgende Funktion zeigt eines solche Hilfsfunktion unter PM. Die anderen Systemen bieten ganz ähnliche Möglichkeiten.


int Debug(char *Ausgabe)
{
int Wahl;

    Wahl = WinMessageBox(HWND_DESKTOP, RahmenHandle,
                         Ausgabe, "Debug", 0,
                         MB_YESNO | MB_ICONHAND | MB_APPLMODAL);
    return Wahl;
}

Die Versuchung ist groß, den Ausgabetext im Programm als Konstante zu kodieren, da bei diesen Dialogboxen der Umweg über die Ressourceneditoren nicht notwendig ist. Der Nachteil ist, daß es nicht mehr durch einfache Änderung der Ressourcedateien möglich ist, das Programm auf andere Sprachen anzupassen. Soll eine solche Messagebox dauerhaft im Programm verwendet werden und nicht nur für die Fehlersuche benötigt werden, sollte der Ausgabetext deshalb in der Ressourcedatei festgelegt werden. Er kann dann über die Funktion WinLoadString in eine Variable geladen werden und so der Messagebox zur Verfügung gestellt werden.

Die Parameter der Funktion WinMessageBox bestehen zunächst aus Eltern- und Besitzerfenster. Es folgt der Text in der Box und der Text der Titelzeile. Interessant ist vor allem der letzte Parameter. Hier können mit Hilfe von Systemkonstanten diverse Variationen erzeugt werden. Oben sind drei typische Einstellungen mit einem bitmäßigen Oder verknüpft. Zunächst wird die Art und Zahl der Knöpfe festgelegt. Hier sind es zwei Knöpfe für Ja und Nein. Man kann auch nur einen Ok- oder Abbruchknopf festlegen. Die Titel der Knöpfe orientieren sich nach der vom System vorgegebenen Landessprache. Als nächstes wird das Symbol festgelegt, das neben dem Text erscheint. Hier kann z. B. ein Fragezeichen, ein Fehlersymbol oder gar kein Symbol festgelegt werden.

Zuletzt wird die Dialogbox als applikationsmodal festgelegt. Alternativ kann der Dialog als systemmodal angelegt werden. Eine nichtmodale Messagebox ist nicht vorgesehen. Diese Einschränkung liegt im Charakter der Messagebox begründet. Sie dient dazu, den Benutzer zu benachrichtigen oder zu warnen oder dazu, ihn um eine Entscheidung um den weiteren Ablauf des Programmes zu bitten. Dies sind alles Situationen, in denen es keinen Sinn macht, die Dialogbox zunächst zurückzustellen.


Ein weiterer wichtiger Standarddialog ist der zur Auswahl einer Datei. Neben dem Effekt, daß dieser Dialog viel Arbeit erspart, hat er den Vorzug, daß der Benutzer ihn in allen anderen Applikationen vorfindet und damit die Bedienung kennt. Ein weiterer Vorteil liegt darin begründet, daß Verbesserungen der Auswahlbox bei neueren Systemversionen der Applikation direkt zugute kommt, ohne daß am Programm etwas geändert werden muß. Dazu kommt, daß das Programm bei geschickter Programmierung nicht geändert werden muß, wenn die Namensgebung des Dateisystems verändert wird. Dies ist bei X ein wichtiger Aspekt, da X-Applikationen nicht nur unter UNIX eingesetzt werden. Aber auch bei anderen Systemen ist dies von Belang. So setzt OS/2 zwei verschiedene Dateisysteme ein, bei denen Unterschiede in der Namensgebung vorliegen.

Darüber hinaus werden bei einigen Systemen auch weitere Standarddialoge z. B. für die Auswahl Schriften oder Farben eingesetzt. Diese sollten, soweit sie verfügbar sind, auf jeden Fall verwendet werden.


3 vgl. Marcellus Bucheit: Windows Programmierbuch. Sybex. 1992. S. 231-234.


Homepage - Inhaltsverzeichnis (C) Copyright 1993-1999 Arnold Willemer