Menüs

Menüs sind relativ einfach zu handhaben. Sie haben einen baumartigen Charakter und bestehen aus einer Menüleiste und für jeden Eintrag in dieser aus einer herunterklappenden Liste von Punkten, dem Klappmenü1. Der Aufbau muß dem Programm bekannt gemacht werden und anschließend empfängt das Programm für jeden angewählten Menüpunkt eine Nachricht, die das Programm weiterverarbeitet. Nach Anwahl verschwindet das Klappmenü wieder. Unter den neueren Oberflächen ist es auch möglich, den Menüpunkten weitere Klappmenüs zuzuordnen. Diese Möglichkeit sollte aber behutsam verwendet werden, da es die Übersicht über die möglichen Funktionen einschränken kann.


Ein naher Verwandter ist das Popup-Menü. Es handelt sich dabei um ein Klappmenü, das sich nicht ständig auf dem Bildschirm befindet, sondern bei Bedarf meist an der aktuellen Mausposition erscheint. Aus Programmierersicht unterscheidet sich das Popup-Menü von einem normalen Menü nur hinsichtlich der Tatsache, daß es explizit gestartet werden muß und die Position bestimmt werden muß, an der es erscheinen soll.

Definition

Unter OSF/Motif wird zunächst ein Management-Widget für die Menüleiste erzeugt. Anschließend werden je ein Management-Widget als Kind-Widget der Menüleiste für jedes Klappmenü definiert. Dann werden die einzelnen Menüpunkte in die senkrechten Menüs als Kind-Widget erzeugt. Für die Menüpunkte wird je eine Callbackfunktion definiert, die aufgerufen wird, wenn der Menüpunkt aktiviert wird. Schließlich wird für jedes Klappmenü der auslösende Druckknopf in der Menüleiste angelegt. Der Druckknopf muß nach dem Klappmenü definiert werden, da ihm als Parameter das zugehörige Klappmenü mitgegeben werden muß. Hier ein Beispiel für OSF/Motif:


void bauMenuLeiste(Widget HauptWidget)
{
Widget MenueLeiste;
Cardinal n;

    MenueLeiste=XmCreateMenuBar(HauptWidget,"menueleiste", NULL, 0);

    DropDatei=XmCreatePulldownMenu(MenueLeiste, "mdatei", NULL, 0);

    NeuPunkt=XmCreateManagedWidget("mneu",xmPushButtonWidgetClass,
                     DropDatei, NULL, 0);
    XtaddCallback(NeuPunkt, XmNactivateCallback, neuCB, NULL);

    LadenPunkt=XmCreateManagedWidget("mladen", xmPushButtonWidgetClass,
                     DropDatei, NULL, 0);
    XtaddCallback(LadenPunkt, XmNactivateCallback, ladenCB, NULL);


    n = 0;
    XtSetArg(args[n], XmNsubMenuId, DropDatei); n++;
    XtCreateManagedWidget("dateiknopf", xmCascadeButtonWidgetClass,
                          MenueLeiste, args, n);

    XtManageChild(MenueLeiste);
}

Es ensteht ein Menü, das in etwa das Aussehen wie in der Abbildung hat.

Struktur eines Menübaums

Auf diese Weise wird nacheinander der gesamte Baum für das Menü zusammengesetzt. Für das Klappmenü wird ein Widget verwendet, das nicht in die Widgethierarchie des Hauptfensters eingearbeitet wird, sondern sich wie ein Fenster über das Hauptfenster legt und nach Beendigung wieder verschwindet. Im Gegensatz zu den normalen Shellwidgets erhalten sie aber keinen Kontakt zum Window-Manager. Man spricht hier von transienten Widgets. Diese werden ebenfalls bei Dialogboxen als Rahmen verwendet. Am Listing kann man sehen, wie das Widget DropDatei dem auslösenden Widget dateiknopf als Eigenschaft des Typs XmNsubMenuID bei dessen Erzeugung mitgegeben wird. Die Betätigung dieses Knopfes wird also später das Klappmenü auf dem Bildschirm erscheinen lassen. Das Starten der Menüleiste wird bei X-Window durch den Aufruf zum Handhaben (Manage) des Widgets ausgelöst.


Trotzdem man aus dem obigen Listing den Eindruck gewinnen könnte, daß Motif für Menüs besondere Elemente zur Verfügung stellt, besteht das Menü aus sehr einfachen Widgets, wie sie auch anderen Stellen in einem Programm Verwendung finden. Sowohl die Menüleiste, als auch das Klappmenü sind einfache RowColumn-Widgets, die sich lediglich in ihrer Ausdehnungsrichtung unterscheiden. Daß die Menüpunkte einfache Druckknöpfe sind, erkennt man auch im Listing. Lediglich der Cascade-Knopf ist mit dem Blick auf das Menü entwickelt worden. Allerdings wird er auch an anderer Stelle verwendet, um Popups auszulösen.

Auch die Funktionen vermitteln einen etwas falschen Eindruck. Ein Widget-Set ist in erster Linie eine Sammlung von vordefinierten Objekten, weniger eine Library im herkömmlichen Sinne. Die Funktionen werden als Conveniencefunctions (Bequemlichkeitsfunktionen) bezeichnet. Sie erstellen die benötigten Standardwidgets, die auch mit der Toolkit-Funktion XtCreateWidget erzeugt werden könnten, und verändern einige für den Verwendungszweck benötigte Eigenschaften, wie in diesem Fall die Ausrichtung.


Unter den anderen Oberflächen wird zur Definition eines Menübaums eine andere Methode verwendet.

Dabei wird bei MS-Windows und Presentation Manager mittels einer Beschreibungssprache der Menübaum definiert und mit einem Ressourcencompiler übersetzt. Das Ergebnis ist eine Datei, die eine vom Programm verwertbare Datenstruktur enthält. Diese Datei wird zum ausführbaren Programm hinzugebunden. Um auf die Elemente der Struktur zugreifen zu können, definiert man Konstanten, die als Kennung für die einzelnen Objekte dienen. Da diese Konstanten sowohl für die Definition als auch im Programm gebraucht werden, legt man diese in eine Include-Datei. Betrachten wir zunächst die Datei mit den Konstanten:


/* PROGRAMM.H - Definition der Konstanten */
#define ID_MENU   100
#define MDATEI    110
#define MNEU      111
#define MLADEN    112
#define MSICHER   113
#define MARBEIT   120
#define MKOPIE    121
#define MSCHNEID  122
#define MEINFUEG  123

Die Zahlen für die Konstanten können vom Programmierer frei gewählt werden. Es dürfen allerdings keine Duplikate innerhalb des Menüs auftreten. Auch ein Duplikat mit einem Druckknopf, der von der gleichen Fensterfunktion bearbeitet wird, sollte vermieden werden. In der Definitionsdatei wird die Datei mit den Konstanten zuerst eingebunden. Die Datei mit der Menüdefinition sieht dann wie folgt aus:


#include "PROGRAMM.H"

MENU ID_MENU PRELOAD
BEGIN
   SUBMENU "Datei", MDATEI
   BEGIN
      MENUITEM "Neu", MNEU ...
      MENUITEM "Laden...", MLADEN ...
      MENUITEM "Speichern...", MSICHER ...
   END
   SUBMENU "Bearbeiten", MARBEIT
   BEGIN
      MENUITEM "Kopieren", MKOPIE
      MENUITEM "Ausschneiden", MSCHNEID
      MENUITEM "Einfuegen", MEINFUEG
   END
END

Das Menü erscheint im Hauptfenster, wenn bei der Fenstererzeugung in der Fensterstilvariablen die Konstante FCF_MENU hinzugefügt wird und im Ressourceparameter die Kennung des Menübaums angegeben wird, in unserem Beispiel wäre dies ID_MENU.

Bei der Erzeugung eines Popup-Menüs wird mittels der Funktion WinLoadMenu unter Angabe der Konstanten für die Menüleiste das Handle für das Menü ermittelt. Anschließend wird das Menü mit der Funktion WinPopupMenu gestartet. Dieser Funktion wird in den Parametern mitgeteilt, an welcher Stelle das Popup-Menü erscheinen soll. Dazu wird normalerweise die aktuelle Mausposition verwendet.


Auch beim Macintosh gibt es eine Klartext-Definition, aus der mittels eines Ressourcencompilers die für das Programm notwendige Datenstruktur erzeugt werden kann. Allerdings wird zur Erstellung von Menüs üblicherweise das Programm ResEdit verwendet, das die am Bildschirm erstellten Menüs in Binärcode umwandelt und zum Programm bindet.

Im Gegensatz dazu gibt es unter GEM keine Definitionssprache. Zu den Entwicklungspaketen gehört ein Resource Construction Set (RCS), mit dessen Hilfe Menübäume und Dialogboxen im grafischen Dialog vom Programmierer erstellt werden. Das RCS erzeugt direkt die für das Programm notwendige Datenstruktur. Dabei vergibt das RCS auch die Konstanten, die als Offsets zu jedem neuen Baum zu verstehen sind. Aus diesem Grund dürfen die Konstanten vom Programmierer nicht verändert werden und es können auch doppelte Werte auftauchen, solange sie nicht zum gleichen Baum gehören.

Das Menü wird gestartet, indem zunächst die vom RCS erzeugte Datei geladen wird. War dies erfolgreich, wird mit der Konstante des Menübaums dessen Speicheradresse bestimmt. Dann wird das Menü durch Aufruf der Funktion menu_bar erzeugt. Vor Ende des Programmes sollte man den durch die Ressourcen belegten Speicher wieder freigeben.


OBJECT *MenueLeiste;

   if (rsrc_load("PROGRAMM.RSC")) {
      rsrc_gaddr( 0, MENU, &MenueLeiste);
      menu_bar(MenueLeiste,1);
   }

   ...

   rsrc_free();

Reaktion auf die Menüauswahl

Die Auswahl eines Menüpunktes durch den Benutzer führt zu einem Ereignis, das in die Nachrichtenschlange eingehängt wird. Das Ereignis erreicht das Programm wie jedes andere auch. Bei OSF/Motif wird es durch eine Callbackfunktion gefangen, PM und MS-Windows empfangen die Nachricht WM_COMMAND in der Fensterfunktion, die das Fenster mit der Menüleiste betreut und GEM erhält die Nachricht bei Aufruf von evnt_multi.

Bei PM befindet sich im ersten Parameter der Nachricht die Konstante, die der Programmierer dem Menüpunkt zugeordnet hat. Es muß also innerhalb des WM_COMMAND-Falles eine weitere Fallunterscheidung für die Menüpunkte durchgeführt werden. Bei der Bearbeitung dieser Nachricht ist es wichtig, daß im Anschluß an die Verarbeitung des Programms die Standardfensterfunktion zum Zuge kommt, damit das Menü wieder ordnungsgemäß zurückgeklappt wird.

GEM und Macintosh verhalten sich hier wieder sehr ähnlich. GEM empfängt nach Aufruf von evnt_multi eine Nachricht vom Typ Message. Mit dem Empfang der Nachricht erhält man Parameter in einem Nachrichtenarray. Hierin findet man die Konstante des Menüleisteneintrages und die Konstante des Menüpunktes, der die Nachricht ausgelöst hat. Erstere benötigt man, um mit dem Aufruf von menu_tnormal den schwarz gewordenen Menüleisteneintrag wieder in den Normalzustand zu bringen. Der zweite Parameter gibt an, welcher Menüpunkt vom Benutzer gewählt wurde.

Neben der Steuerung der Menüs durch die Maus sollte man einen Mechanismus vorsehen, der auch die Steuerung durch Tastatur ermöglicht. Eine Bürokraft wird ihre Ergebnisse eher zwischendurch sichern, wenn sie dazu die Finger nicht von der Tastatur nehmen muß. Ist dies möglich, sinkt die Wahrscheinlichkeit, daß sie beim nächsten Stromausfall auf das Programm fluchen wird (wer sucht schon die Fehler zuerst bei sich selbst?).

Zur Auswahl von Menüpunkten per Tastatur gibt es zwei Verfahren. Bei OSF/Motif gibt es für diese die Namen mnemonics (Abkürzungen) und accelerators (Beschleuniger). Abkürzungen bestehen aus Buchstaben, die die Namen der Menüobjekte bezeichnen, meist deren Anfangsbuchstaben. So könnte der Punkt Sichern im Klappmenü Datei über D S erreicht werden. Zur Unterscheidung von normalen Eingaben wird der erste Buchstabe in Verbindung mit der Meta- oder Alt-Taste gedrückt. Beschleuniger sind Einzeltasten, die meist in Verbindung mit der Kontrolltaste direkt auf den Menüpunkt ohne den Umweg über das Klappmenü erreicht werden. In dem genannten Beispiel könnte auch Control-S das Speichern auslösen.

OSF/Motif bietet beide Möglichkeiten. Sie werden über die Eigenschaften (Ressourcen) der Widgets festgelegt und können in leicht änderbaren externen Dateien einstellt werden. Auch MS-Windows und Presentation Manager kennen beide Verfahren. Auch hier können sie in externen Dateien eingestellt werden. Lediglich bei den Beschleunigern muß das Programm die Accelerator-Tabelle aktivieren. Beim Macintosh können die Tastenkürzel bei der Definition des Menübaums im ResEdit angegeben werden und lösen dann bei Benutzung unter Verwendung der Apfeltaste die gleichen Ereignisse aus, als wäre der Menüpunkt mit der Maus angewählt worden. GEM unterstützt die Tastensteuerung von Haus aus nicht. Das Programm muß selbst bei Empfang eines Tastaturereignisses die gleichen Aktionen auszulösen wie bei entsprechenden Menüereignissen.

Manipulationen am Menü

Das Menü kann während des Programmes beeinflußt werden. Eine Variante, die jeder Anwender schon gesehen hat, ist die Möglichkeit, einen Menüpunkt zu deaktivieren. Er erscheint dann in grauer Schrift und löst kein Ereignis mehr aus. Ein typisches Beispiel sind die Blockoperationen, die sinnlos sind, wenn überhaupt kein Block markiert ist. Dem Benutzer sollte leicht erklärbar sein, warum er den Punkt nicht anwählen kann. Im Extremfall kann es sogar sinnvoller sein, den Punkt eingeschaltet zu lassen und mit Hilfe einer Dialogbox zu erläutern, warum dieser Punkt momentan keine Funktion hat. Besser ist eine Informationszeile, aus der hervorgeht, warum augenblicklich nur eingeschränkte Operationen möglich sind.

Außerdem gibt es noch die Möglichkeit, vor dem Menüpunkt einen Haken erscheinen und wieder verschwinden zu lassen. Dies eignet sich besonders für eine überschaubare Anzahl von Optionen. Der Benutzer muß so nicht erst die zugehörige Dialogbox aufrufen, um sich über deren Zustand zu informieren.

Bei PM und MS-Windows werden dem Menü Zustandsänderungen der Menüpunkte zugesandt. Zur Bestimmung des Handles der Menüleiste wird das Handle des Elternfenster benötigt. In diesem Fall ist allerdings nicht das Clienthandle, das die Fensterfunktion als Parameter erhält, relevant, sondern das Rahmenhandle, da das Menü ein Kind des Rahmens ist. Dem Menü wird die Nachricht vom Typ MM_SETITEMATTR zugesandt, die ihm signalisiert, daß ein Element des Menüs in seinen Attributen verändert werden soll. Im ersten Parameter findet sich die Konstante des betroffenen Menüpunktes und im zweiten Parameter das Attribut, das verändert werden soll. Da dies so abstrakt kaum verständlich ist, sei hier der Befehl zum Setzen und anschließend der zum Löschen eines Hakens dargestellt.


   MenueHandle = WinWindowFromID(RahmenHandle, FID_MENU);
   /* Setzen eines Hakens */
   WinSendMsg( MenueHandle, MM_SETITEMATTR,
            MPFROM2SHORT(MENUEPUNKTID, TRUE),
            MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED) );
   /* Loeschen des Hakens */
   WinSendMsg( MenueHandle, MM_SETITEMATTR,
            MPFROM2SHORT(MENUEPUNKTID, TRUE),
            MPFROM2SHORT(MIA_CHECKED, 0) );

In der ersten Zeile wird das Handle des Menüs bestimmt. Dies wird mit der Funktion WinWindowFromID erreicht. Diese benötigt als Parameter einmal das Handle des Elternfenster. Das Elternfenster der Menüleiste ist wie gesagt der Rahmen des Fensters, nicht der Inhalt! Das Rahmenfensterhandle wird bei Erzeugung des Fensters durch WinCreateStdWindow als Rückgabewert ermittelt. Die ID, die zur Ermittlung der Menüleiste verwendet wird, ist nicht die vom Programmierer vergebene Kennung, sondern die Systemkonstante FID_MENU, die einfach das Menü des Fensters bezeichnet. Da jedes Fenster höchstens eine Menüleiste haben kann, ist dies eindeutig.

Mit WinSendMsg wird dem Menü nun die Nachricht MM_SETITEMATTR übersandt. Das Makro MPFROM2SHORT dient dazu, zwei 16-Bit-Werte in die größere (z. Zt. 32-Bit lange) Parametervariable zu packen. Der erste Wert des ersten Parameter wird durch die vom Programmierer vergebene Konstante des Menüpunktes belegt. Der zweite Wert des ersten Parameter ist TRUE und weist PM an, alle Untermenüs der Menüleiste zu durchsuchen. Im zweiten Parameter befindet sich die zu verändernde Eigenschaft und die Maske, die angibt, ob die Eigenschaft gesetzt wird. Eine Doppeltnennung führt also zum Setzen; eine Null als zweiter Wert führt zum Löschen der Eigenschaft.

Der Einfachheit halber bieten PM und MS-Windows auch Funktionen zur Veränderung der Menüpunktattribute an. Hier gibt es für das Setzen bzw. Löschen von Haken, sowie für das Aktivieren bzw. Deaktivieren die Funktionen (Win)EnableMenuItem() und (Win)CheckMenuItem(). Als Parameter erhalten sie das Handle des Menüs, das wie oben beschrieben bestimmt wird, die Menüpunktkonstante und schließlich ein Flag, das bestimmt, ob die Eigenschaft gesetzt oder gelöscht werden soll. Ein Blick in die Headerdateien zeigt, daß sich hinter den Funktionen Makros verbergen, die den vorher beschriebenen Sendemechanismus realisieren.

GEM verwendet Funktionen zur Veränderung der Menüeinträge. Die Parameter haben gewisse Ähnlichkeiten zu den Makros von MS-Windows. Allerdings betrachtet GEM Menüs nicht als Fenster, sondern als ein Array von Datenstrukturen für Menübestandteile. Statt eines Handles ermittelt man mit Hilfe der Funktion rsrc_gaddr() die Adresse der Datenstruktur des Menübaums im Speicher. Alle Elemente des Menübaums sind als Array von Objektstrukturen realisiert. Für die Menümanipulation ist deren genaue Kenntnis aber nicht erforderlich, da die Standardfunktionen für die Manipulation der Menüeinträge als Parameter lediglich die Adresse des Menübaums und die vom RCS vergebene Konstante des Menüpunktes als Index benötigen.


Unter OSF/Motif werden für die beiden Mechanismen unterschiedliche Verfahren verwendet. Der Grund liegt darin, daß beide Mechanismen für andere Zwecke als die Menügestaltung ebenfalls Verwendung finden.

Um einen Menüpunkt zu deaktivieren, wird die Funktion XtSetSensitive verwendet. Als Parameter erhält sie die Widgetkennung des betroffenen Menüpunktes und eine boolesche Variable, die angibt, ob das Widget aktiv sein soll. An ihrem Präfix ist zu erkennen, daß sie zum X Toolkit gehört. Dementsprechend ist sie für alle Widgets aller Widget-Sets einsetzbar. Wurde ein Widget deaktiviert, reagiert es anschließend weder auf Maus- noch auf Tastenereignisse. Dabei wird das entsprechende Widget grau dargestellt.

Einen Haken an einem Menüpunkt kann man unter OSF/Motif nicht erzeugen. Statt dessen wird ein kleines Kästchen für eine angewählte Option verwendet. Man erreicht dies, indem man die Menüpunkte als Toggle-Knöpfe statt als normale Druckknöpfe anlegt. Durch Ändern der Eigenschaft XmNset kann man das Kästchen setzen oder entfernen.



Das Aktivieren und Deaktivieren von Menüpunkten erfordert Übersicht bei der Programmierung. Es ist erforderlich, ständig im Blick zu halten, in welchen Situationen Menüpunkte aktiviert bzw. deaktiviert werden sollen. Dies hängt meist davon ab, ob der Benutzer das Programm in einen Zustand gebracht hat, daß die Operation ausführbar ist. Man erleichtert sich das Leben meist erheblich, wenn man für jeden deaktivierbaren Menüpunkt eine boolesche Funktion schreibt, die ermittelt, ob alle Bedingungen erfüllt sind, um den Menüpunkt aktivierbar zu machen oder nicht. Nach jeder Aktion, die einen der Parameter für die Operation verändert, ruft man diese Funktion auf und aktiviert bzw. deaktiviert den Menüpunkt in Abhängigkeit vom Rückgabewert.

Neben dem Verändern der Menüpunkte in ihren Eigenschaften ist es auch möglich, den Menübaum in seiner Struktur durch Hinzufügen oder Herausnehmen von Menüpunkten zu verändern. Dadurch lassen sich benutzerkonfigurierbare Programme erzeugen. In den meisten Fällen dürfte eine solche Manipulation mehr der Verwirrung als dem Komfort dienen. Unter OSF/Motif sind aus diesem Grund nachträgliche Veränderungen der Menüstrukturen nicht vorgesehen2. Ich erinnere mich daran, daß ich einmal zwei Stunden damit verbracht habe, herauszufinden, warum bei Word für Windows der Menüpunkt zum Erstellen eines Inhaltsverzeichnis im Programm nicht auffindbar war, obwohl er im Handbuch beschrieben wurde. Für meinen persönlichen Geschmack gehört das Verändern der Menübaumstruktur zu den Beispielen dafür, daß man nicht alles tun sollte, was technisch machbar ist. Es gibt nur wenige Fälle, in denen das Verändern einer Menüstruktur sinnvoll ist, wie zum Beispiel bei integrierten Paketen, bei denen in Abhängigkeit vom Typ des Dokuments die Menüleiste ausgetauscht wird. In solchen Fällen sollte aber darauf geachtet werden, daß der Austausch für den Benutzer nachvollziehbar ist und trotz des Wechsels eine möglichst weitgehende Konsistenz zwischen den verschiedenen Menübäumen erreicht wird.


1 In der Literatur findet sich meist der Begriff Pull-Down-Menü. Bei GEM wird vom Drop-Down-Menü gesprochen, da die Liste bereits bei Berührung nach unten fällt. Ich verwende den Begriff Klappmenü, der kurz ist und sich in deutsche Sätze deutlich besser einfügt.

2 Eine Veränderung ist zwar technisch möglich, wird aber vom Motif Style Guide abgelehnt und wird darum auch nicht durch Convenience-Funktionen unterstützt


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