Unterabschnitte

Dialoge

Dialoge sind Fenster, die als Kindfenster Kontrollelemente enthalten, über die sie mit dem Anwender kommunizieren. Das Dialogfenster stellt dabei eine Infrastruktur zur Verfügung, die die korrekte Funktion der Kontrollelemente im Kontext des Dialogs ermöglicht.

In der libCUI gibt es aus technischer Sicht keinen Unterschied zwischen Fenstern und Dialogen, da das Standardverhalten eines libCUI-Fenster das eines Dialogs ist. Der folgende Abschnitt geht auf ein paar Besonderheiten ein, die es trotzdem zu beachten gilt.

Modale und nichtmodale Dialoge

Ein Dialog sollte grundsätzlich ein Popup-Fenster sein. Für Popup- Fenster gilt dabei generell, dass es für sie zwei Ausführungsmodi gibt: Modal und nichtmodal.

Ein nichtmodales Popup-Fenster überlagert das Anwendungsfenster und wird immer über diesem angeordnet. Allerdings kann nach wie vor auch das Hauptfenster (bzw. seine Kindfenster) Windows-Nachrichten empfangen und den Eingabefokus erhalten. Das Popupfenster unterbindet also nicht den Zugriff auf den Teil der Anwendung der darunter liegt. Diese Form der Popup-Fenster macht nur in den seltensten Fällen wirklich Sinn.

Ein modales Popup-Fenster liegt ebenfalls immer über dem Anwendungsfenster, sperrt aber zudem den Zugriff darauf, solange bis das Popup-Fenster geschlossen wird. Dialogfenster sind in der Regel immer modal.

Beispiel für ein nichtmodales Popup-Fenster:

   dlg = MyWindowNew(win, 10, 2, 40, 10, CWS_POPUP, CWS_NONE);
   WindowCreate(dlg);
   WindowSetFocus(dlg);   
   ...

Beispiel für ein modales Popup-Fenster:

   dlg = LoginDlgNew(win, show_server, CWS_NONE, CWS_NONE);
   if (dlg)
   {
      WindowCreate(dlg);
      if (WindowModal(dlg) == IDOK)
      {
         /* process dialog data */
      }
      WindowDestroy(dlg);      
   }

Der entscheidende Unterschied im zweiten Beispiel ist der API Aufruf ''WindowModal'' über den das Fenster ''dlg'' in einer modalen Schleife ausgeführt wird. Der Programmablauf wird scheinbar beim Funktionsaufruf von ''WindowModal'' angehalten bis der Dialog vom Anwender geschlossen wurde. Der Rückgabewert der Funktion ist der Result-Code, der der Funktion ''WindowClose'' innerhalb des Dialogs übergeben wurde.

Obwohl es für den Rückgabewert eines Dialogs keine funktionale Festlegung gibt, sollte für Standardfälle eine der von der libCUI definierten Konstanten verwendet werden:

   #define IDOK              0x00000001
   #define IDCANCEL          0x00000002
   #define IDYES             0x00000003
   #define IDNO              0x00000004
   #define IDRETRY           0x00000005

Im Folgenden wird immer von modalen Dialogen ausgegangen, wenn von Dialogen die Rede ist.

Dialogdaten

Dialoge sind in der Regel so aufgebaut, dass ihre Datenfelder vom aufrufenden Programmteil initialisiert werden und nach dem Schließen des Dialogs dort auch wieder ausgewertet werden. Eine mögliche Vorgehensweise dazu ist, die Instanzdaten des Dialogfensters von über einen Zeiger sichtbar zu machen. Oder die Daten über eine Austauschstruktur in den Dialog zu kopieren. Auch der Zugriff auf einzelne Datenfelder über separate Zugriffsfunktionen ist denkbar, jedoch recht aufwändig. Die gewählte Vorgehensweise ist Geschmacksache und bleibt dem Programmierer überlassen.

Hier als Beipiel ein Auszug aus der Header-Datei eines Login-Dialogs:

   typedef struct
   {
      char Host[128 + 1];
      char Port[32 + 1];
      char Username[64 + 1];
      char Password[64 + 1];
      int  ShowServer;
   } LOGINDLGDATA;

   CUIWINDOW* LoginDlgNew(CUIWINDOW* parent, 
                          int show_server, 
                          int sflags, int cflags);
   LOGINDLGDATA* LoginDlgGetData(CUIWINDOW* win);

In der Funktion ''LoginDlgNew'' wird dabei eine Instanz des Datentyps ''LOGINDLGDATA'' angelegt und dem ''InstData'' Zeiger der Fensterstruktur zugewiesen. Die Funktion ''LoginDlgGetData'' erlaubt den typsicheren Zugriff auf das Datenelement.

    dlg = LoginDlgNew(win, show_server, CWS_NONE, CWS_NONE);
    if (dlg)
    {
       LOGINDLGDATA* dlgdata = LoginDlgGetData(dlg);
       if (dlgdata)
       {
          strcpy(dlgdata->Username, "postgres");
          strcpy(dlgdata->Password, "");
       
          WindowCreate(dlg);
          if (WindowModal(dlg) == IDOK)
          {
             Login(dlgdata->Username, dlgdata->Password);
          }
       }
       WindowDestroy(dlg);
    }

Hingewiesen werden sollte noch auf die Tatsache, dass der Aufruf von ''WindowCreate'' erst erfolgt, nachdem die Datenfelder initialisiert wurden, da hier im CreateHook die Kontrollelemente angelegt und mit den Werten aus den Dialogdaten gefüttert werden. Sollte nach dem Erzeugen des Dialogs ein Datentransfer in die Kontrollelemente stattfinden, dann muss der Dialog eine geeignete Update-Funktion bieten.

Dialogfenster anlegen

Wie bereits bei den Kontrollelementen gezeigt, können alle zur Anlage und zur Spezialisierung erforderlichen Schritte auch bei Dialogen in einer gemeinsamen Funktion zusammengelegt werden, so dass das resultierende Fenster nurnoch erzeugt werden muss, bevor es an ''WindowModal'' übergeben werden kann. Welche Schritte dabei i.d.R. unternommen werden, zeigt das folgende Beispiel:

CUIWINDOW*
LoginDlgNew(CUIWINDOW* parent, int show_server, 
            int sflags, int cflags)
{
   if (parent)
   {
      CUIWINDOW* dlg;
      int flags = sflags | CWS_POPUP | CWS_BORDER | CWS_CENTERED;
      flags &= ~(cflags);

      if (!show_server)
      {
         dlg = WindowNew(parent, 0, 0, 40, 9, flags);
      }
      else
      {
         dlg = WindowNew(parent, 0, 0, 40, 13, flags);
      }
      dlg->Class = "LOGIN_DLG";
      
      WindowColScheme(dlg, "DIALOG");
      WindowSetCreateHook(dlg, LoginDlgCreateHook);
      WindowSetDestroyHook(dlg, LoginDlgDestroyHook);

      dlg->InstData = (LOGINDLGDATA*) 
          malloc(sizeof(LOGINDLGDATA));
      ((LOGINDLGDATA*)dlg->InstData)->Username[0] = 0;
      ((LOGINDLGDATA*)dlg->InstData)->Password[0] = 0;
      ((LOGINDLGDATA*)dlg->InstData)->Host[0] = 0;
      ((LOGINDLGDATA*)dlg->InstData)->Port[0] = 0;
      ((LOGINDLGDATA*)dlg->InstData)->ShowServer = 
          show_server;

      WindowSetText(dlg, "Login");
      return dlg;
   }
   return NULL;
}

Zunächst wird ein normales libCUI Popup-Fenster angelegt, das es, abhängig vom Parameter 'show_server' in zwei unterschiedlichen Varianten geben kann. Zudem wird dem neuen Fenster der Klassenname ''LOGIN_DLG'' zugewiesen.

Anschließend wird das Farbschema für Dialoge zugeordnet und zwei Hook- Funktionen angemeldet - auf die später noch eingegangen wird. Dann erfolgt die Instanzierung und Initialisierung des Datentyps ''LOGINDLGDATA'' sowie die Zuweisung an den Instanzdatenzeiger des Dialogfensters.

Zuletzt wird dem Dialogfenster der Fenstertext ''Login'' zugewiesen, der in der Titlezeile des Fensters angezeigt werden wird.

Kontrollelemente verwalten

Wie aus dem vorigen Abschnitt ersichtlich wird, werden die Kontrollelemente nicht schon bei der Anlage des Dialogs erzeugt. Vielmehr wird dazu der CreateHook verwendet, der im obigen Beispiel unter dem Namen ''LoginDlgCreateHook'' rangiert. Eine Implementierung kann dabei wie folgt aussehen:

   static void
   LoginDlgCreateHook(void* w)
   {
      CUIWINDOW* win = (CUIWINDOW*) w;
      CUIWINDOW* ctrl;
      LOGINDLGDATA* data = (LOGINDLGDATA*) win->InstData;

      ctrl = LabelNew(win, "User name:", 
                      2, 1, 12, 1, 0, 
                      CWS_NONE, CWS_NONE);
      WindowCreate(ctrl);

      ctrl = LabelNew(win, "Password:", 
                      2, 3, 12, 1, 0, 
                      CWS_NONE, CWS_NONE);
      WindowCreate(ctrl);

      ctrl = EditNew(win, data->Username, 
                      14, 1, 20, 1, 64, 
                      IDC_EDUSERNAME, 
                      CWS_NONE, CWS_NONE);
      WindowCreate(ctrl);

      ctrl = EditNew(win, data->Password, 
                      14, 3, 20, 1, 64, 
                      IDC_EDPASSWD, 
                      EF_PASSWORD, CWS_NONE);
      WindowSetFocus(ctrl);
      WindowCreate(ctrl);

      ctrl = ButtonNew(win, "&OK", 
                      7, 5, 10, 1, 
                      IDOK, 
                      CWS_DEFOK, CWS_NONE);
      ButtonSetClickedHook(ctrl, LoginDlgButtonHook, win);
      WindowCreate(ctrl);

      ctrl = ButtonNew(win, "&Cancel", 
                      19, 5, 10, 1, 
                      IDCANCEL, 
                      CWS_DEFCANCEL, CWS_NONE);
      ButtonSetClickedHook(ctrl, LoginDlgButtonHook, win);
      WindowCreate(ctrl);
   }

Hingewiesen sei darauf, dass die Konstanten ''IDC_EDUSERNAME'' etc. innerhalb des Dialogmoduls z.B. per define angelegt sein müssen. Zudem ist erwähnenswert, dass der Dialog sich keine Zeiger auf die Kontrollelemente ''merkt''. Er kann später jederzeit über die ID des Kontrollelements auf dieses zugreifen:

   ctrl = WindowGetCtrl(win, IDC_EDUSERNAME);
   if (ctrl)
   {
      EditGetText(ctrl, data->Username, 64);
   }

Da die Kontrollelemente Kindfenster des Dialogs sind, müssen sie nicht explizit nach dem Schließen des Dialogs zerstört werden. Dies geschieht automatisch dann, wenn das Dialogfenster selbst entfernt wird. Daher reduziert sich die Implementierung des DestroyHooks im vorliegenden Fall auf das Freigeben der Instanzdaten des Dialogs:

   static void
   LoginDlgDestroyHook(void* w)
   {
      free(((CUIWINDOW*) w)->InstData);
   }

Dialoge schließen

Ein Dialog wird über die Funktion ''WindowClose'' geschlossen, wobei an an diese Funktion der Result-Code übergeben wird, der später der Rückgabewert der Funktion ''WindowModal'' sein wird. Üblicherweise wird der Aufruf von ''WindowClose'' aus einem Callback-Hook eines Button- Kontrollelements heraus aufgerufen:

static void
LoginDlgButtonHook(void* w, void* c)
{
   CUIWINDOW* win = (CUIWINDOW*) w;
   CUIWINDOW* ctrl = (CUIWINDOW*) c;
   LOGINDLGDATA* data = (LOGINDLGDATA*) win->InstData;

   if (ctrl->Id == IDOK)
   {
      /* update data structures */
      WindowClose(win, IDOK);
   }
   else
   {
      WindowClose(win, IDCANCEL);
   }
}

Zu erwähnen ist dabei, dass ''WindowClose'' den Aufruf der Hook-Funktion ''CanClose'' bewirkt. Nur wenn das Dialogfenster und alle seine Kindfenster (falls sie diesen Hook überhaupt implementieren) einen Rückgabewert von ''TRUE' zurückgeben, wird das Dialogfenster auch tatsächlich geschlossen.

Bevor ein Dialog mit ''IDOK'' (oder einem anderen Wert der einen Erfolg signalisiert) beendet wird, müssen die Kontrollelemente ausgelesen und in die Instanzdaten des Fensters übertragen werden. Dabei erfolgt auch eine logische Prüfung der Daten in den Eingabefeldern. Notfalls kann man den Aufruf von ''WindowClose'' vermeiden, falls die Anwendereingaben unzufriedenstellend sind. Der Ort an dem eine solche Bearbeitung stattfinden sollte ist oben mit ''update data structures'' gekennzeichnet.

Vordefinierte Dialoge der libCUI

Damit häufig verwendete Dialoge, wie z.B. der Dialog zum Öffnen und Speichern von Dateien, nicht immer wieder neu implementiert werden müssen, wird eine Liste von Standarddialogen in der libCUI hinterlegt. Bislang ist die Auswahl noch recht bescheiden, wird aber in naher Zukunft wachsen.

Die bereits implementierten Dialoge werden hier kurz vorgestellt:

MessageBox

Die MessageBox wird verwendet um dem Anwender kurze Mitteilungen oder Fehlermeldungen anzuzeigen oder eine Auskunft von ihm zu verlangen, die sich mit ''Yes'', ''No'' oder mit ''Retry'', ''Cancel'' beantworten lässt.

Entgegen der üblichen Praxis wird die MessageBox nicht separat angelegt, erzeugt und anschließend modal ausgeführt. Vielmehr reicht ein einziger Funktionsaufruf aus, um den Dialog anzuzeigen und den Ergebniswert zurückzuliefern:

   int MessageBox(CUIWINDOW* parent, 
                  const char* text, 
                  const char* title, 
                  int flags);

Als Vaterfenster kann ein beliebieges Fenster aus dem aktuellen Kontext angegeben werden, der Parameter ''text'' erhält die Textmeldung, im Parameter ''title'' wird der Fenstertitle übergeben und der Parameter ''flags'' steuert das Erscheinungsbild der MessageBox. Dabei können die folgenden Konstanten verwendet und mit dem Oder- Operator verknüpft werden:

Flag Bedeutung
MB_NORMAL MessageBox mit grauem Hintergrund (Standard)
MB_INFO MessageBox mit einem cyanfarbigen Hintergrund (Info-Modus)
MB_ERROR MessageBox mit einem roten Hintergrund (Fehler-Modus)
MB_OK Nur eine Schaltfläche mit ''Ok' Beschriftung (Standard). Der Rückgabewert des Dialogs ist immer IDOK.
MB_OKCANCEL Zwei Schaltflächen beschriftet mit ''Ok'' und ''Cancel''. Der Rückgabewert des Dialogs ist entweder IDOK oder IDCANCEL.
MB_YESNO Zwei Schaltflächen beschriftet mit ''Yes'' und ''No''. Der Rückgabewert des Dialogs ist entwerder IDYES oder IDNO.
MB_YESNOCANCEL Drei Schalflächen beschriftet mit ''Yes'', ''No'' und ''Cancel''. Der Rückgabewert ist entwerder IDYES, IDNO oder IDCANCEL.
MB_RETRYCANCEL Zwei Schaltflächen beschriftet mit ''Retry'' und ''Cancel''. Der Rückgabewert ist entwerder IDRETRY oder IDCANCEL.
MB_DEFBUTTON1 noch nicht implementiert.
MB_DEFBUTTON2 noch nicht implementiert.

 
Beispiele für Message Boxen:

   MessageBox(win, "File saved successfully!", "Message", MB_OK); 

   MessageBox(win, "File not found!", "Error", MB_ERROR); 

   MessageBox(win, "I\nam\na\nmultiline\nMessage!", 
              "Message", MB_OK); 
   
   if (MessageBox(win, "Really exit?", 
                  "Question", MB_YESNO) == IDYES)
   {
      WindowClose(win, IDOK);
   }
Holger Bruenjes 2016-12-12