Programmierung von Fenstern |
Es müssen die Funktionen erstellt werden, die auf die verschiedenen Ereignisse reagieren. Dabei ist beispielsweise eine Funktion erforderlich, die jederzeit den Fensterinhalt im aktuellen Zustand rekonstruieren kann. Diese Funktion wird benötigt, wenn das System das Programm zum Neuzeichnen des Fensters auffordert.
/* Die gewuenschten Rahmenelemente */ FensterStil = CLOSER | MOVER | SIZER . . . ; FensterHandle = wind_create(FensterStil,maxx,maxy,maxw,maxh); if (FensterHandle < 0) { /* kein Fenster mehr zur Verfuegung */ } else { if (wind_open(FensterHandle, posx,posy,posw,posh) == 0) { /* Fehler aufgetreten */ } . . . } . . . wind_close(FensterHandle); /* Schliessen */ wind_delete(FensterHandle); /* Abmelden */
Zu Anfang sieht man die Definition der Rahmenelemente. Im Beispiel soll das Fenster eine Schließbox, einen verschiebbaren Titelbalken und eine Vergrößerungsbox enthalten. Mit diesem Parameter wird die Funktion wind_create aufgerufen, die das Fensterhandle zurückliefert, sofern GEM noch Fenster zur Verfügung hat. Da GEM ursprünglich eine begrenzte Zahl von Fenstern hatte, ist die Abfrage, ob noch ein Fensterhandle zur Verfügung steht, von besonderer Bedeutung. Die Positionskoordinaten mit dem Präfix max sind die maximal zulässige Ausdehnung des Fensters. Im Normalfall verwendet man hier die Dimensionen des Desktops. Sie sagen etwas darüber aus, wie groß das Fenster im Laufe des Programms höchstens werden kann. Das Fenster ist nach diesem Aufruf noch nicht auf dem Bildschirm sichtbar.
Erst die Funktion wind_open erzeugt auf dem Bildschirm einen
sichtbaren Rahmen des Fensters. Der weiße Hintergrund des
Fensters muß durch den Programmierer erzeugt werden, indem er
anschließend ein weiß gefülltes Rechteck mit den Koordinaten des
Arbeitsbereiches zeichnet. Die Koordinaten mit dem Präfix pos
legen die tatsächliche Position des Fensters fest. Da eine
GEM-Applikation den ganzen Bildschirm für sich allein hat, werden hier
meist ebenfalls die Desktopdimensionen abzüglich eines kleinen
Anstandsrestes verwendet.
Der Aufruf von WinRegisterClass hat die Aufgabe, eine Klasse von
Fenstern zu definieren, die die gleiche Fensterfunktion verwenden. Das
bedeutet, daß beim Eintreffen von Ereignissen in einem dieser Fenster das
System die gleiche Funktion aufrufen wird. Die Fensterklasse wird durch
eine Zeichenkette definiert. Der Name ist frei
wählbar, dient aber auch bei der Kommunikation zwischen Prozessen als
Adresse. Dementsprechend ist es sinnvoll, die Applikationsfensterklasse
dem Zweck des Programms entsprechend zu benennen. Weitere Fensterklassen
des Programms bennent man üblicherweise so, daß man den Namen der
Applikationsfensterklasse voranstellt.
Da der Inhalt des Strings für die
korrekte Zuordnung eines Fensters zu seiner Fensterklasse von Bedeutung
ist und Tippfehler innerhalb des Strings vom Compiler nicht bemerkt
würden, wird hier die Stringkonstante per #define einer
symbolischen Konstante zugeordnet. Wird der Name der Konstanten falsch
eingegeben, meldet der Compiler, daß er den Namen nicht kennt. Sollte
man sich im Namen der Fensterklasse bei der Definition vertippt haben,
hat dies im Normalfall überhaupt keinen Einfluß auf den Ablauf des
Programms, solange nicht eine zweite Fensterklasse zufällig den gleichen
Namen hat.
Im nächsten Schritt wird das Fenster erzeugt. Damit ist es noch nicht auf
dem Bildschirm zu sehen. Es wird lediglich das Fensterhandle angefordert
und das Fenster beim Presentation Manager angemeldet. Durch die Angabe
der Fensterklasse weiß das System bereits jetzt, welche
Applikationsfunktion für die Ereignisse zuständig ist.
Bereits an dieser Stelle wird die Fensterfunktion das erste Mal aufgerufen.
Die Nachricht lautet WM_CREATE und bedeutet, daß das Fenster
soeben erzeugt wurde.
Betrachtet man noch einmal den Funktionsaufruf von
WinCreateStdWindow genauer, fällt die Existenz zweier Handles auf.
Das hängt damit zusammen, daß ein Standardfenster bei MS-Windows und
Presentation Manager eigentlich aus zwei Elementen besteht: dem Rahmen
und dem Fensterinhalt. Dabei ist der Rahmen das Elternfenster des Inhalts.
Der Fensterinhalt wird auch Clientbereich des Fensters genannt.
Das im Beispiel RahmenHandle
genannte Handle ermöglicht den Zugriff auf den Rahmen. Es wird benötigt,
wenn auf Rahmenbestandteile zugegriffen werden muß.
Dieses Handle wird zum Beispiel bei der Behandlung von Menüs relevant,
die als Bestandteil des Rahmens verstanden werden.
Das zweite Handle, im Beispiel ClientHandle genannt, bezeichnet den
Arbeitsbereich, also den Inhalt des Fensters. Dieses Handle erhält auch
die Fensterfunktion als ersten Parameter, da dieses Handle für alle
Objekte, die sich innerhalb des Fensters tummeln, zuständig ist und auch
benötigt wird, wenn innerhalb des Arbeitsfensters gezeichnet wird.
Die Funktion WinSetWindowPos stellt das Fenster schließlich dar.
Der Name dieser Funktion ist insofern etwas irreleitend, als daß es
keineswegs erforderlich ist, an dieser Stelle konkrete
Bildschirmpositionen vom Programm aus festzulegen. Vielmehr handelt es
sich um eine Funktion, die verschiedene Aufgaben ausführt, je nachdem,
wie der letzte Parameter besetzt ist. In der folgenden Tabelle sind
einige der möglichen Konstanten und ihre Bedeutung aufgeführt, die der
Funktion mitgegeben werden können.
Es kann durch Odern oder Addieren der Konstanten erreicht werden, daß auch
mehrere Aktionen gleichzeitig durchgeführt werden.
Fensterklasse und Fensterfunktion
Ganz ähnlich sieht das Anmelden eines Fensters bei MS-Windows und
Presentation Manager aus. Das Erzeugen und Eröffnen wird in zwei Schritten
durchgeführt und die Bestandteile des Rahmens werden in einer Variablen
bitweise zusammengesetzt. Allerdings wird vor die Erstellung eines
Fensters die Definition einer Fensterklasse gestellt.
Hier ein Beispiel für die Anmeldung eines Fensters:
#define FENSTERKLASSE "FensterKlasse"
WinRegisterClass (Handle,
FENSTERKLASSE,
(PFNWP) FensterFunktion,
CS_SIZEREDRAW,
0);
RahmenHandle = WinCreateStdWindow (
HWND_DESKTOP,
0L, &FensterStil,
FENSTERKLASSE,
"Fenstertext",
0, (HMODULE) 0L,
ID_MAIN, /* Kennung des Menues, sonst 0 */
&ClientHandle);
WinSetWindowPos(RahmenHandle,
HWND_TOP,
xlinksoben, ylinksoben,
xrechtsunten, yrechtsunten,
Option);
SWP_SIZE | Änderung der Fenstergröße |
SWP_MOVE | Verschieben eines Fensters |
SWP_HIDE | Das Fenster wird unsichtbar |
SWP_SHOW | Das Fenster wird sichtbar |
SWP_ACTIVATE | Aktiviere das Fenster |
Zum Schluß des Programms sollte man das Hauptfenster wieder abmelden. Dies wird mit der Funktion WinDestroyWindow(FensterHandle) erledigt. Auch diese Funktion führt zu einer Nachricht an die Fensterfunktion. Sie kann nun noch Datenbestände sichern. Das Entfernen des Fensters kann sie nicht mehr verhindern.
Beim Erzeugen eines Fensters wird als erster Parameter ein Fensterhandle angegeben. Dabei handelt es sich um das Handle des Elternfensters. Für das Hauptfenster einer Applikation ist dies der Desktop, welcher die Konstante HWD_DESKTOP als Fensterhandle hat. Um ganz genau zu sein, muß hier das Handle des Fensterinhaltes angegeben werden. Wird ein weiteres Fenster angelegt und diesem das Clienthandle des Applikationsfensters als erster Parameter übergeben, wird dieses innerhalb des Fensterinhalts dargestellt. Das heißt, daß es auch nicht über den Rand des Elternfensters hinaus dargestellt wird. Es können beliebig viele Generationen von Fenstern erzeugt werden, die jeweils von den Eltern begrenzt werden.
Betrachtet man das Fenster eines X-Programmes, so besteht dieses im Normalfall aus mehreren Widgets. Um die Zusammensetzung eines solchen Fensters zu verstehen, ist es zunächst notwendig, die Widgets in drei Kategorien einzuteilen.
Neben dem Hauptwidget wird ein solches Widget immer dann benötigt, wenn Elemente nicht innerhalb des Hauptwidgets angeordnet werden, sondern darübergelegt werden, wie beispielsweise Klappmenüs, Dialogboxen oder zusätzliche Applikationsfenster.
Auch unter X gibt es eine Funktion, die ein Widget erzeugt und davon getrennt eine andere Funktion, die das erzeugte Widget zur Darstellung bringt. Zur Veranschaulichung dienen Ausschnitte aus dem bereits gezeigten Programm. Die Anmeldung der Callbackfunktionen sind herausgenommen, da sie bereits betrachtet wurde. Auch die Initialisierung des Programmes ist an dieser Stelle nicht interessant und aus dem Listing entfernt worden.
... void initWidgetBaum(Widget top) { Widget wRahmen, wCount, wQuit; wRahmen = XtCreateManagedWidget("rahmen", xmRowColumnWidgetClass, top, NULL, 0); wMsg = XtCreateManagedWidget("message", xmLabelWidgetClass, wRahmen, NULL, 0); wCount = XtCreateManagedWidget("count", xmPushButtonWidgetClass, wRahmen, NULL, 0); wQuit = XtCreateManagedWidget("quit", xmPushButtonWidgetClass, wRahmen, NULL, 0); } main(Cardinal argc, char **argv) { Widget topShell; ... ... topShell = XtAppCreateShell(NULL, Applikation, applicationShellWidgetClass, display, NULL, 0); initWidgetBaum(topShell); XtRealizeWidget(topShell); ...
Zunächst wird die Widgetkennung der Applikation bestimmt. Diese ist von der Klasse ApplicationShell und heißt im Beispiel topShell. Die Shell ist wie gesagt unsichtbar, erhält aber bei Erscheinen auf dem Bildschirm vom Window-Manager einen Rahmen. Sie kann nur ein Widget aufnehmen. In der Funktion initWidgetBaum wird als erstes ein Management-Widget der Klasse RowColumn angelegt. Dieses ordnet seine Kinder einfach untereinander an. Bei Erzeugung dieses Widgets wird das Shell-Widget als Vater angegeben. Die folgenden Widgets, ein Label und zwei Knöpfe, sind wiederum Kinder dieses Management-Widgets. Man erkennt dies daran, daß ihnen die Widgetkennung des RowColumn-Widget als Parameter übergeben wird. Die Entstehung der Widgets geschieht in drei Schritten. Zunächst werden sie erzeugt (create). Damit ist der Speicher zur Verfügung gestellt und die Widgets sind initialisiert. Im nächsten Schritt werden sie vom Programm gehandhabt (manage). Dies ist der Vorgang, daß sie in die Widget-Hierarchie des Programmes eingegliedert werden und die Platzansprüche mit den anderen Widgets abgestimmt werden. Im letzten Schritt wird das Shell-Widget dargestellt (realize). Das hat zur Folge, daß auch alle gehandhabten Kinder zur Darstellung gebracht werden. Der Einfachheit halber wird mit der Funktion XtCreateManagedWidget das Erzeugen und Handhaben in einem Schritt ausgeführt. Nachdem der Baum erstmalig dargestellt ist, reicht es aus, ein Widget zu handhaben, um es darzustellen. Entsprechend taucht die Funktion XtRealizeWidget typischerweise nur einmal in einem Programm auf.
Beim Handhaben werden die Größenausdehnungen der Widgets festgelegt. Die sichtbaren Widgets reichen ihre Idealvorstellungen bezüglich ihrer Größe an das Eltern-Management-Widget weiter. Dieses stellt die Anforderungen aller seiner Kinder zusammen und reicht sie an ein gegebenenfalls darüberliegendes Managementwidget bis sie zum Rahmenwidget gelangen, welches die Anforderungen an den Window-Manager weiterleitet. Dieser stellt fest, welche Ausdehnung auf dem Schirm möglich ist und reicht diese wieder zurück an das Rahmenwidget, welches es an die Managementwidgets weitergibt. Diese bestimmen, welche sichtbaren Widgets welchen Raum erhalten und reichen sie ihrerseits weiter. Wie man sich vorstellen kann, ist dieser Vogang recht aufwendig. Aus diesem Grund empfielt es sich, Management-Widgets zunächst nur zu erzeugen und erst dann zu handhaben, wenn alle Kinder angelegt worden sind. Auf diese Weise erspart man die erneute Abstimmung nach der Erzeugung jedes Kindwidgets.
Sehr interessant ist bei diesem Konzept die völlige Abwesenheit von
Koordinaten als
Parameter beim Erstellen eines Fensters. Das hängt damit zusammen,
daß die Größe des benötigten Fensters im Normalfall
ohne den Eingriff des Programmierers bestimmt wird, sondern durch die
Ermittlung eines Kompromisses zwischen dem Platz, den der Inhalt eines
Widgets benötigt und dem Platz, den der Window-Manager
zur Verfügung stellen kann. Dieser Mechanismus bedingt, daß sich das
Programm auch nicht mit einem Vergrößerungsereignis befassen muß.
OSF/Motif unterstützt diese Art von Hauptfenstern durch ein eigenes
Management-Widget namens MainWindow, das genau diese
Bestandteile aufnehmen kann. Da allein das Einhängen der Menüleiste in
den Kopf des Fensters einfacher ist, als die Motage in einem eigenen
Form-Widget, findet man diesen Typ in fast jedem OSF/Motif-Programm.
Die Rollbalken sind optional und können weggeblendet werden. Dies ist
empfehlenswert bei Elementen, die in erster Linie mit Text arbeiten, da
sie über einen auf Text spezialisierten Rollbalken verfügen. Diese sind
sehr viel schneller als die Allzweckrollbalken des MainWindow, die
auf Pixelbasis operieren.
Für das Zusammensetzen des MainWindow gibt es eine eigene Funktion.
Die Paramter sind die Widgetkennungen der beteiligten Elemente. Der erste
Parameter ist die Kennung des MainWindow. Es folgen Menü, Kommandoelement,
die Rollbalken und schließlich der Arbeitsbereich. In dem Beispiel oben
werden die Rollbalken des MainWindow ausgeblendet, indem NULL
übergeben wird. Die zuletzt angegebene Liste besitzt eigene Rollbalken.
Aus diesem Grund muß das Elternwidget angegeben werden, da die Rollbalken
die Außengrenze des Elements festlegen.
Das besonders interessante an diesem Element ist, daß es quasi eine
Flankenunterstützung des Style Guide darstellt. Die Verwendung
des MainWindow ist Style-Guide-konform. Das heißt, daß es für
den Programmierer einfacher ist, sich an die Regeln zu halten, als sie zu
umgehen.
Das MainWindow-Widget
Für die Gestaltung des Hauptfensters werden immer wieder die gleichen
Elemente zusammengesetzt. Zuoberst befindet sich die Menüleiste, darunter
ein Arbeitsbereich, der meist mit Rollbalken ausgestattet ist. Ober- oder
unterhalb des Arbeitsbereich findet sich oft noch ein Bereich, in dem
Kommandos ausgegeben werden oder der Programmstatus angezeigt wird.
/* Das MainWindow wird zusammengesetzt */
XmMainWindowSetAreas(wRahmen,
wMenu /* Menue */,
wPara, /* Command */
NULL, /* horiz. Scrollbar */
NULL, /* vertik. Scrollbar */
XtParent(wList));
Die Verwaltung der Fensterereignisse
Nachdem das Fenster glücklich erstellt worden ist, muß sich das Programm
mit den Ereignissen befassen, die auf ein Fenster einwirken können.
Bei den für das Fenster ausgelösten Nachrichten ergeben sich für alle
Systeme ähnliche Ereignisse. Sie werden im Normalfall mit Konstanten
benannt. Hier eine Aufstellung der wichtigsten Nachrichten:
Betrachten wir die einzelnen Nachrichten im Detail.
Die meistbenötigte Funktion ist die zum Bearbeiten der Aufforderung zum
Neuzeichnen des Fensterinhalts.
Dies wird erforderlich, wenn das Fenster zwischenzeitlich verdeckt war oder
aus einer Position außerhalb des sichtbaren Bereichs wieder
zurückgeschoben wurde.
Im Normalfall wird das Programm in der Lage sein müssen, seinen
Fensterinhalt selbst jederzeit wiederherstellen zu können. Nähere Aspekte
werden an anderer Stelle
behandelt.
Die Nachricht über das Anklicken der Schließbox betrifft auch die Anwendung, da es bedeuten kann, daß der Benutzer den Fensterinhalt verwirft. Die Applikation muß in einem solchen Fall gegebenenfalls bisher ungesicherte Daten speichern oder nach Rückfrage verwerfen. Eine Abmeldung des Fensters ist in jedem Fall notwendig. Die Vergrößerungsbox des Fensters bringt beim ersten Anklicken das Fenster auf volle Bildschirmgröße und beim zweiten Mal auf die letzte Größe zurück. Bei PM und MS-Windows braucht die spezielle Nachricht für die Vergrößerungsbox nicht beachtet zu werden, da die Standardfensterfunktion die Größenveränderung automatisch durchführt. Danach wird eine weitere Nachricht gesendet, daß die Größe des Fensters sich geändert hat. Für das Programm reicht es normalerweise aus, auf diese Nachricht zu reagieren. Es erhält mit dieser Nachricht die neuen Koordinaten des Fensters. Bei GEM erfordert diese Nachricht die Speicherung der aktuellen Fensterposition und die manuelle Positionierung des Fensters auf die maximale Fenstergröße. Unter GEM muß auch festgestellt werden, ob das Fenster bereits auf maximale Fenstergröße eingestellt war, um es auf die Originalgröße reduzieren zu können. Mit dem Aufkommen von Großbildschirmen wird der ursprüngliche Gedanke, daß mit Hilfe eines einzigen Klicks die Applikation den gesamten Bildschirm erhält, nicht mehr so interessant. Das Vergrößern auf volle Größe ist in vielen Fällen sogar eher lästig. Aus diesem Grund geht der Trend auf dem Macintosh dahin, daß eine Applikation auf den Empfang einer Fullsize-Nachricht den maximalen Bildschirmausschnitt einnimmt, der für den Fensterinhalt sinnvoll ist.
Die Nachricht, daß ein hinter einem anderen Fenster liegendes Fenster angeklickt wurde und damit in den Vordergrund kommt, kann bei PM oder MS-Windows ignoriert werden, da das Umschalten zwischen den Fenstern vom System gehandhabt wird. Hier würde eventuell eine andere Fensterfunktion aktiviert, wenn die beiden Fenster nicht zur gleichen Fensterklasse gehören. Ansonsten ist das Umschalten nur durch ein anderes Fensterhandle in der Nachricht bemerkbar. GEM-Programme brauchen dagegen diese Nachricht, da sie selbst das Fenster in den Vordergrund bringen müssen. Dies ist noch recht einfach durch einen einfachen Funktionsaufruf zu bewerkstelligen. Bei Applikationen mit unterschiedlichen Fenstertypen kann dieses Ereignis eine Uminterpretation der eintreffenden Ereignisse bedeuten, da hier nicht für jedes Fenster eine eigene Fensterfunktion realisierbar ist. Allerdings kann in solchen Fällen für jede Fensterart eine eigene Ereignisschleife geschrieben werden, die bei Wechsel des Fensters aufgerufen wird. Der Macintosh kennt noch eine zusätzliche Nachricht, daß ein Fenster als oberstes Fenster verdrängt wurde. Dies ist erforderlich, da der Programmierer dort die Deaktivierung des Rollbalkens selbst ausführen muß und auch markierte Bereiche wieder auflösen sollte, da nach den Stilvorschriften immer nur im aktiven Fenster ein Block markiert sein soll.
Das Verschieben eines Fensters führt bei GEM zu einer Veränderung aller Fensterkoordinaten des Inhalts. Das Programm muß das Verschieben des Fensters selbst veranlassen. Wiederum sind PM und MS-Windows Programme von dieser Nachricht meist nicht betroffen. Das Verschieben führt die Standardfensterfunktion selbst durch und da die Koordinaten fensterbezogen sind, hat es keinen Einfluß auf die Applikation. Lediglich in dem Fall, daß ein Kindfenster teilweise aus dem Bereich des Elternfenster herausgeschoben wird, kann eine Erzeugung von Rollbalken im Elternfenster erforderlich machen.
GEM erhält die Fensterereignisse wie alle anderen Ereignisse nach dem Aufruf der Funktion evnt_multi als Rückgabewert. Als Parameter an evnt_multi wird unter anderem ein Array übergeben, das nach einem Fensterereignis nähere Informationen enthält. Dies sind einmal das Handle des betroffenen Fenster und die Bildschirmpositionen, die für die Bearbeitung des Ereignisses benötigt werden. Letztere können nur im Zusammenhang mit der Nachricht interpretiert werden. Beim Verschieben geben sie z.~B. die Zielposition an.
aktion = evnt_multi(handle, . . ., Bereich, . . . ); switch(aktion) { . . . case WMMOVED: Verschiebe(Bereich); . . . } . . . void Verschiebe(int *Bereich) { /* Bereich[0] bis [2] enthalten nachrichtenspezifische Details. */ wind_set(message[3], /* Handle des Fensters */ WF_CURRXYWH, /* Position aendern */ Bereich[4], /* neue x-Koordinate */ Bereich[5], /* neue y_Koordinate */ Bereich[6], /* Fensterbreite (unveraendert) */ Bereich[7]); /* Fensterhoehe (unveraendert) */ }
Der Mehraufwand für die Verschiebung des Fensters, die in anderen Oberflächen vom System übernommen wird, hält sich in Grenzen. Es sind die Offsetwerte für die Koordinaten zu korrigieren, falls sich das GEM-Programm diese in eigenen Variablen merkt, um sie nicht jedesmal vom System erfragen zu müssen[1] und es ist die Verschiebung des Fensters durchzuführen, was durch eine Systemfunktion leicht zu realisieren ist.
Bei PM und bei MS-Windows ist die Verwaltung der Fenster durch die Fensterfunktion geprägt. Liegt ein Ereignis für ein Fenster an, ruft das System die Funktion auf, die für diese Fensterklasse registriert wurde. Im folgenden sind die wichtigsten Nachrichten aufgeführt:
WM_CREATE | Das Fenster wurde erzeugt, ist aber noch nicht sichtbar |
WM_PAINT | Es ist notwendig, den Inhalt des Fensters neu aufzubauen |
WM_SIZE | Die Größe des Fensters ist verändert worden |
WM_CLOSE | Das Fenster ist geschlossen worden |
WM_COMMAND | Eine Taste oder ein Druckknopf im Fenster wurde gedrückt |
WM_CONTROL | Ein Kontrollelement des Fensters ist betätigt worden |
Die Nachricht WM_CREATE tritt genau einmal nach dem Erzeugen des Fensters auf. Es ist bis dahin noch nicht auf dem Bildschirm zu sehen. Wer also zum Beispiel bei Erscheinen des Fensters gern sein Konterfei erscheinen lassen möchte, wird an dieser Stelle auf Granit beißen. Diese Nachricht dient dazu, Vorbereitungen für die Fensterbearbeitung treffen, wie etwa das Anfordern von Speicher, um später Bildschirmkopien herstellen zu können oder das Erzeugen (nicht das Darstellen!) von Kindfenstern. Hier werden häufig auch die Standardtexte, die später im Fenster erscheinen sollen, in den Variablen vorbesetzt. Angezeigt werden sie allerdings erst beim Eintreffen der WM_PAINT-Nachricht.
Die WM_PAINT-Nachricht trifft erstmals bei der ersten Darstellung des Fensters ein. Ansonsten erscheint sie immer dann, wenn ein Ereignis eingetreten ist, was ein Neuzeichnen des Fensterinhalts erforderlich macht.
In einem PM-Programm kann man alle Ausgabefunktionen, die den Aufbau des Bildschirms bewirken, an dieser Stelle konzentrieren. Ergibt sich eine Änderung des Bildschirms an einer anderen Stelle des Programms, werden die Parameter für die Ausgabefunktionen verändert und anschließend eine WM_PAINT-Nachricht durch den Aufruf der Funktion WinInvalidateRect provoziert. Man übergibt ihr den neu zu zeichnenden Bildschirmbereich und braucht sich an dieser Stelle nicht weiter um das Zeichnen zu kümmern. Das System wird bei nächster Gelegenheit eine WM_PAINT-Nachricht auslösen und die Fensterfunktion wird den Bereich neu zeichnen.
Die WM_SIZE-Nachricht tritt auf, wenn das Fenster in seiner Größe verändert worden ist. Meist führt das dazu, daß auch Teile des Bildschirms neu gezeichnet werden müssen. Man korrigiert an dieser Stelle die Koordinaten und legt gegebenenfalls zusätzliche Informationen in Variablen ab, die später bei Neuzeichnen des Fensters verwendet werden. Gerade bei Grafikapplikationen stellt sich die Frage, ob die Vergrößerung des Fensters einen größeren Ausschnitt zeigt oder die Skalierung der Grafik verändert. In letztem Fall muß die gesamte Grafik neu aufgebaut werden. Aus diesem Grund gibt es zwei verschiedene Redraw-Strategien. Normalerweise wird bei Vergrößerung nur der hinzugekommene Bereich neugezeichnet. Wird der Fensterklasse bei ihrer Definition die Eigenschaft SIZEREDRAW mitgegeben, führt jede Veränderung der Größe des Fensters, also auch eine Verkleinerung, zu einer WM_PAINT-Nachricht für den gesamten Fensterinhalt.
WM_CLOSE ist die Nachricht, daß der Benutzer soeben das Fenster geschlossen hat. Hier ist die Möglichkeit, Speicherbereiche oder andere Ressourcen freizugeben, die dieses Fenster bis jetzt belegt hat. Handelt es sich um die Fensterfunktion des Hauptfensters, kann man davon ausgehen, daß der Benutzer das Programm beenden will. In diesem Fall sendet man die WM_QUIT-Nachricht, die dem System mitteilt, daß das Programm beendet werden soll. Diese Nachricht wird auch die Hauptereignisschleife in der Hauptfunktion beenden und damit die Abmeldung des Programmes einleiten. Bei einem normalen Fenster wird man das System durch Aufruf der Funktion WinDestroyWindow zum Entfernen des Fensters auffordern.
WM_COMMAND ist die Nachricht, daß ein Kommando eingetroffen ist. Um festzustellen, durch welche konkrete Aktion das Ereignis ausgelöst wurde, wird der erste Parameter zerlegt. Im ersten Wort steht das Kommando. Es folgt nun typischerweise eine weitere Fallunterscheidung.
WM_CONTROL wird ausgelöst, wenn eines der Kontrollelemente im Arbeitsbereich des Fensters betätigt wurde.
Die WM_CONTROL und WM_COMMAND Fälle werden im Zusammenhang mit den Dialogboxen näher betrachtet.
Ganz anders verhalten sich die Widgets unter X. Sie übernehmen vollständig die optischen Aspekte der Benutzeraktionen. Wird beispielsweise die Maus über einem Druckknopf gedrückt, wird dieser eingedrückt dargestellt. Wird die Maustaste anschließend über dem Druckknopf losgelassen, geht der Knopf wieder nach außen und der Knopf wurde angewählt. Die sichtbare Reaktion des Knopfes wird bereits bei der Konstruktion des Widgets realisiert, indem für die verschiedenen Ereignisse der Struktur des Widget jeweils ein Zeiger auf eine Funktion zugefügt wurde, die bei Eintreffen des Ereignisses aufgerufen wird. Zu jedem Widget gehören bereits Standardfunktionen, die insbesondere für die grafische Umsetzung der Ereignisse zuständig sind. Dadurch scheint ein Widgetbaum bereits selbstständig zu arbeiten, ohne daß die Applikation eine einzige Funktion beisteuert. Darüber hinaus ``kennt'' jedes Widget sein eigenes Aussehen und kann dieses durch entsprechende Standardfunktionen jederzeit rekonstruieren. Aus diesem Grund braucht eine Bearbeitung der Nachricht zum Neuzeichnen des Bildschirms nicht durch das Programm erfolgen, solange es sich der Standardwidgets bedient. Aus dem gleichen Grund ist auch das Verschieben eines Fensters kein Ereignis, das der Programmierer bearbeiten muß.
Auch die Veränderung der Fenstergröße wird durch die Widgets selbstständig geregelt. Diese Aufgabe übernehmen die verwendeten Management-Widgets, die nicht nur die anfängliche Ausrichtung ihrer Kinder steuern, sondern auch Änderungen der Außenmaße an ihre Kinder weiterleiten. Je nach Art des Widgets wird hier nach unterschiedlichen Strategien vorgegangen. Die einfachsten Managementwidgets sind solche, die ihre Kinder vorzugsweise senkrecht oder waagerecht anordnen. So werden die Auswahlknöpfe in einer Menüleiste vorzugsweise waagerecht angeordnet. Wenn der Platz nach rechts nicht reicht, wird eine weitere Zeile angelegt, in der die restlichen Knöpfe angeordnet werden.
Neben den einfachen Management-Widgets, die die ihnen zugewiesene Fläche gleichmäßig auf ihre Kinder verteilen, gibt es die Constraint-Widgets. Diese sind in der Lage für jedes Kind unterschiedliche Anordnungsstrategien zu verwenden, insbesondere im Falle einer Veränderung der Größe. Ein typischer Vertreter dieser Klasse ist das Formwidget. Das Besondere am Formwidget ist die Möglichkeit, daß die Kindwidgets in ihrer Position durch ihr Verhältnis zu anderen Kindwidgets oder zum Rahmen festgelegt werden können. Ferner kann eingestellt werden, welche Widgets eine Vergrößerung des Außenrahmens nachvollziehen und welche bei Vergrößerung des Gesamtfensters in ihrer Größe unverändert bleiben.
Auf diese Weise erstellt der X Programmierer einen Baum von Widgets, in dem er die Abhängigkeiten der Widgets voneinander festlegt und durch die Auswahl der Managementwidgets bestimmt, nach welcher Strategie sich eine Vergrößerung oder Verkleinerung des Gesamtfensters auf die einzelnen Widgets auswirkt. Nachdem dieser Baum einmal erstellt ist und die Abhängigkeiten festgelegt sind, reagiert das Fenster selbstständig auf alle typischen Fensterereignisse.
Lediglich in dem Fall, daß eine direkte Grafikprogrammierung benötigt wird, wie etwa bei CAD-Systemen, müssen die entsprechenden Mechanismen vom Anwendungsprogrammierer übernommen werden. Dazu gibt es unter OSF/Motif das DrawingArea-Widget, welches die freie Gestaltung des Inhalts zuläßt. In diesem Fall ist natürlich auch die Bearbeitung der Ereignisse, wie Neuzeichnen und Größenveränderung, vom Programm selbst zu bewerkstelligen. Zu diesem Zweck besitzt dieses Widget eigene Callbacklisten, für die die entsprechenden Routinen geschrieben werden müssen.
Positionen kann man durch die Verwendung von Verhältnissen ausdrücken. So könnte das Hauptfenster der Applikation zum Beispiel 80% der Höhe und 60% der Breite eines Bildschirms einnehmen. Dazu wird die Desktopgröße ermittelt und mit dem entsprechenden Faktor verrechnet. Eine solche Strategie ist bei Applikationen sinnvoll, die einen möglichst großen Anteil des Bildschirms benötigen wie etwa Textverarbeitungen oder Grafikprogramme. In anderen Fällen könnte die Verwendung der Zeichensatzgrößen sinnvoller sein. Hier rechnet man in Anzahl von Zeilen oder Buchstabenbreiten. Dazu ermittelt man die Fontgröße in Punkten. Lediglich bei Abstandsrändern macht es Sinn, in konstanten Punkten zu rechnen. Spätestens, wenn man die Ergebnisse auf einem Drucker, der typischerweise eine deutlich höhere Auflösung besitzt als der Bildschirm, ausgibt, wird sich die Verwendung der Systemwerte bezahlt machen, da man die gleichen Funktionen einfach auf den Drucker umleiten kann.
Unter PM gibt es die Möglichkeit, das Hauptfenster vom System positionieren zu lassen. Dies wird erreicht, indem dem Fensterstil die Konstante FCF_SHELLPOSITION hinzugefügt wird. Damit die vom PM vorgeschlagene Größe tatsächlich verwendet wird, darf beim Darstellen mit der Funktion WinSetWindowPos das Fenster nur aktiviert und sichtbar gemacht werden. Man kann dies gut kontrollieren, indem man die Parameter für die Fensterpositionen mit Nullen besetzt. Der Vorteil dieses Vorgehens gegenüber einer selbstgewählten Fensterposition liegt darin, daß beim mehrfachen Start des Programms nicht alle Hauptfenster auf der gleichen Stelle liegen. Der Benutzer kann dann kaum feststellen, daß er das Programm wirklich mehrfach gestartet hat.
Die Initialisierung des Fensters würde etwa wie folgt aussehen, wenn das System die Position des ersten Erscheinens festlegen soll:
FensterStil=FCF_SHELLPOSITION | FCF_SIZEBORDER | FCF_TITLEBAR; Option = SWP_SHOW | SWP_ACTIVATE; . . . RahmenHandle = WinCreateStdWindow (HWND_DESKTOP, 0L, &FensterStil, FENSTERKLASSE, "Fenstertext", 0, (HMODULE) 0L, ID_MAIN, &ClientHandle); WinSetWindowPos(RahmenHandle, HWND_TOP, 0, 0, 0, 0, Option);
Dem Benutzer sollte die Möglichkeit geschaffen werden, die Größe und Position des Fensters beliebig zu verändern. Dazu benötigt das Fenster einen Verschiebebalken und ein Vergrößerungselement.
Allerdings sollte man sich auch darüber klar sein, daß man das Versprechen, das man dem Benutzer mit dem Vergrößerungselement gibt, auch einhalten sollte. So sollte eine Listbox innerhalb eines Fensters auch wachsen, wenn der Benutzer das Fenster größer macht. Er wird dies vermutlich vergrößern, weil er mehr sehen will. Darauf sollte eine Anwendung aber nicht so reagieren, daß sie dem Benutzer mehr Hintergrund zur Verfügung stellt. Für die Fensterfunktion bedeutet dies, daß beim Ereignis WM_SIZE die Position der Liste anhand der neuen Fensterausdehnung mittels WinSetWindowPos neu gesetzt wird.
Zunächst wird die Anordung der Kinder von links oben zeilenweise nach
rechts unten festgelegt. Dies geschieht durch die Reihenfolge ihrer
Erzeugung. Dabei wird als Elternwidget immer das Formwidget angegeben.
Nun wird bei jedem Widget in der Eigenschaft fromHoriz
eingestellt, welches Widget der nächste Nachbar nach links und in
fromVert, welches Widget der nächste Nachbar nach oben ist.
In der Abbildung hat das Eingabefeld als linken Nachbarn
das Label und als oberen Nachbarn die Menueleiste. Die Liste hat keinen
linken Nachbarn. Als oberen Nachbarn könnte sowohl das Label als auch
das Eingabefeld eingetragen werden. Es wird nicht unbedingt ein direkt
angrenzendes Widget verwendet, sondern es ist das Widget relevant, welches
die gewünschte Ausdehnung nach unten hat. Der Abbruch-Knopf hat als
linken Nachbarn den Knopf Sichern, auch wenn dieser weit entfernt
ist.
Nachdem auf diese Weise die Anordnung festgelegt wurde, wird im nächsten
Schritt durch Einstellungen der Ränder das Vergrößerungsverhalten
bestimmt. Dazu haben die Widgets vom Formwidget je eine Eigenschaft für
jeden Rand erhalten, die sinnigerweise left, right,
top und bottom heißen.
Sie geben an, wie sich der Rand im Vergrößerungsfall zu welchem Nachbarn
verhält. Standardmäßig sind sie auf Rubber (Gummi) eingestellt. Beim
Vergrößern des Außenrahmens vergrößern sich so die Zwischenabstände
und
die Widgets selbst. Um dieses Verhalten zu verändern, kann eine oder
mehrere Seiten angekettet (Chain) werden. Dadurch wird der Abstand, der
bei der Erzeugung des Formulars vorlag, bei einer Größenänderung des
Rahmens nicht mehr verändert. Das heißt, daß der Raumgewinn beim
Vergrößern vollständig dem Widget zugute kommt. Wird ein
Widget auf der linken Seite nach links und auf der rechten Seite nach
rechts verkettet, kann man sich leicht vorstellen, daß das Widget bei
Vergrößerung des Rahmens auseinandergezogen wird. Soll dies nicht
erfolgen, wird sowohl die linke als auch die rechte Seite mit dem linken
Nachbarn verkettet. Damit bleibt die horiziontale Ausdehnung des Widgets
konstant. In der Abbildung sind diese Verkettungen
angedeutet.
Die Knöpfe und das Label werden ihre ursprüngliche Ausdehnung nicht
verändern. Dagegen ist die Liste nach allen vier Richtungen verankert, so
daß jede Vergrößerung des Raumes vollständig der Liste zugeführt
wird. Die
Menüleiste hat keinen Eintrag nach rechts, so daß die ursprüngliche
Gummiverbindung zum rechten Rand erhalten bleibt. Das bedeutet, daß eine
Verbreiterung zwischen der Menüleiste und seinem rechten Rand aufgeteilt
wird. Beim Eingabefeld ist hier eine Verkettung nach rechts eingesetzt
worden, um eine Verbreiterung des Rahmens vollständig dem Eingabefeld
zugute kommen zu lassen. Der Nachteil dieses Verfahrens ist, daß auch eine
Verkleinerung direkt auf das Eingabefeld einwirkt, so daß das Eingabefeld
kleiner werden kann als das Labelfeld. Ist dieses Verhalten unerwünscht,
kann die Verkettung des rechten Randes auch nach links erfolgen, um ein
immer gleich großes Eingabefeld zu erhalten.
Die folgende Funktion erzeugt das in der Abbildung skizzierte
Fenster.
Wie im Listing zu sehen, wird das Setzen der Eigenschaften durch Füllen
eines Feldes vom Typ Arg erreicht. Dazu steht die Funktion
XtSetArg zur Verfügung. Der zweite Parameter der Funktion
beschreibt die zu
verändernde Eigenschaft und der dritte den neuen Wert. Das Array wird dem
Widget bei seiner Erzeugung mitgegeben.
Die Tatsache, daß die Abstände bei Erzeugung des Formwidgets eingefroren
werden, kann zu dem Problem führen, daß sich die Ausgangspositionen der
Ränder zu weit links befinden, da sie ja von links nach rechts aufgebaut
werden. Dies
betrifft zum Beispiel den Abbruchknopf, der entgegen der Grafik nicht am
rechten Rand ``kleben'' wird, sondern sich dicht an seinem linken Nachbarn
anordnen wird und bei Erzeugung des Rahmens den Abstand zum rechten Rand
ermittelt und einfriert. Bereits die geringste Verkleinerung des Rahmens
wird nun dazu führen, daß sich der Abbruchknopf unter seinen linken
Nachbarn schiebt. Ein ähnliches Problem tritt bei der Ausdehnung der
Liste auf. Da diese bei Erzeugung des Widgets noch keine Einträge hat,
wird sie nur die minimale Größe anfordern und damit einen sehr großen
rechten Rand einfrieren.
Um dies zu verhindern, müssen beim Athena Widget Set
auf die Koordinaten
der Widgets zugegriffen werden. Dabei werden die Ausgangspositionen bzw.
die Breiten der betroffenen Widgets vom Programmierer anhand der
Ausdehnung der anderen Widgets vorbesetzt, so daß sie bei ihrer
Erzeugung den gewünschten Raum einnehmen. Die einfachere Lösung besteht
darin, in den Ressourcedateien Dimensionen vorzubelegen, die die
gewünschten Positionierungen bewirken.
Unter OSF/Motif besitzt das Formwidget aus diesem Grund
zusätzliche Eigenschaften, die es den Widgets erlauben, die Entfernung
zum rechten Rand festzulegen. Zusätzlich bietet es die Möglichkeit,
Kindwidgets an festgelegten Positionen zu arretieren. Dazu kann eine
virtuelle Skalierung vorgenommen werden. Dies ist beispielsweise
hilfreich, wenn man ein Drittel für das Label und zwei Drittel des Raumes
für das Eingabefeld vergeben möchte. Dazu wird die Skalierung in der
Eigenschaft XmNfractionBase auf drei eingestellt. Das Label erhält
die Startposition 0 und die Endposition 1. Das Eingabefeld erhält die
Startposition 1 und die Endposition 3. Bei einer Veränderung der Größe
wird die Skalierung immer auf die konkret zur Verfügung stehende
Ausdehnung angepaßt, so daß die Größenverhältnisse immer gleich
bleiben.
Bei PM und MS-Windows werden die Kontrollelemente in der Fensterfunktion
angelegt. Kontrollelemente sind aus der Sicht dieser Systeme Sonderfälle
von Fenstern. Bei Eintreffen der WM_CREATE-Nachricht werden sie
erzeugt, aber noch nicht dargestellt. Da bei Eintreffen der Nachricht
das Fenster noch gar nicht auf dem Bildschirm vorliegt, gibt es erstens
gar keine Möglichkeit, Ausgaben durchzuführen und es liegen zweitens noch
keine Fensterkoordinaten vor, an denen man sich orientieren könnte.
Die Darstellung und Positionierung der Kontrollemente wird bei Eintreffen
der Nachricht WM_SIZE ausgeführt. In den Parametern der Nachricht
findet sich die Größe des Fensterinhalts. Anhand derer kann die
Aufteilung des Fensters für die Kontrollelemente vorgenommen werden.
Der Anwendungsprogrammierer braucht also für Fenster
mit gleicher Aufgabenstellung nur eine Fensterfunktion zu schreiben.
Fenster mit unterschiedlicher Aufgabenstellung sollten unterschiedliche
Fensterfunktionen verwenden. Dazu müssen mehrere Fensterklassen definiert
werden. Für eine Applikation, die mit Fenstern für Grafik und Text
arbeitet, würde man also jeweils eine Fensterklasse definieren. Bei der
Erzeugung eines Fensters würde die jeweils benötigte Klasse als Parameter
angegeben.
Es können mehrere Fenster die gleiche Funktion benutzen.
Das führt dazu, daß diese Fenster auf die Ereignisse gleich reagieren,
sofern der Programmierer nicht anhand des Handles eine
Fallunterscheidung vornimmt. Da letzteres die übersicht nicht gerade
erhöht, wird man für unterschiedliche Arten von Fenstern auch
unterschiedliche Fensterfunktionen schreiben und damit auch verschiedene
Fensterklassen einrichten. Gleiche Fensterklassen werden verwendet, wenn
man zum Beispiel in mehreren Fenstern gleichartige Dokumente verwaltet.
In der Abbildung ist dies am Beispiel der beiden Fensterklassen
angedeutet. Die eine Fensterklasse bearbeitet Texte und besitzt dazu eine
speziell auf Textverarbeitung zugeschnittene Fensterfunktion. Die andere
Fensterklasse enthält Fenster für Grafiken mit einer anderen
Fensterfunktion. Beispielsweise werden die Fensterfunktionen
einen Mausklick unterschiedlich interpretieren. Während er in der
Textklasse zum Neupositionieren des Cursors führen wird, könnte im
Grafikfenster damit ein Punkt gesetzt werden.
Äußerlich ist der Unterschied der Fensterklassen nicht unbedingt
sichtbar. Alle Fenster sind Nachkommen des Rahmenfensters. In
der Abbildung
ist dies veranschaulicht.
Diese Anwendung würde mit mindestens vier Fensterklassen arbeiten. Eine
Klasse für das Applikationsfenster, je eine Klasse für Text und Grafik
und schließlich eine Fensterklasse für das Steuerungsfenster.
Die Fensterfunktionen bilden Module, die für die zugehörigen Fenster
alle Reaktionen auf äußere Ereignisse übernehmen. Es gibt aber Fälle,
in
denen Fenster auf andere Fenster einwirken müssen. Beispielsweise könnte
das Drücken eines Druckknopfes im Steuerungsfenster Auswirkungen auf ein
Grafikfenster haben. Zu diesem Zweck ist es möglich, applikationseigene
Nachrichten zu definieren, die die Fensterfunktion des Steuerungsfensters
an die Adresse eines anderen Fensters versendet. Dazu muß in der
Fallunterscheidung der Fensterfunktion ein zusätzlicher Fall für die
benutzereigene Nachricht eingetragen werden. Wie bei den Systemnachrichten
können auch Benutzernachrichten Parameter übergeben werden. Auf diese
Weise ergibt sich fast von selbst der Ansatz für ein objektorientiertes
Design der Anwendung. Es ist möglich, völlig abgeschlossene Objekte zu
bilden, die miteinander über festgelegte Nachrichten kommunizieren.
Da eine Fensterfunktion alle Nachrichten des zugehörigen Fensters
empfängt und im Idealfall allein die Ausgaben in das Fenster
kontrolliert, kann durch sie der Charakter eines Fensters fast beliebig
verändert werden. Man könnte eine Fensterfunktion definieren, die
lediglich einen Text mittig darstellt und eine Benutzernachricht zur
Änderung dieses Textes akzeptiert. Das Ergebnis wäre mit einigen
Abstrichen einem Label-Widget vergleichbar. Durch Hinzufügung des Empfangs
von Mausklicks und einigen Grafikfunktionen wäre daraus auch ein
Druckknopf zu gestalten. Tatsächlich sind sämtliche Kontrollelemente
von MS-Windows und Presentation Manager so angelegt und damit
nichts anderes als Fenster einer vom System vorgegebenen Fensterklasse.
Entsprechend gibt es für sie jeweils eine feste Fensterfunktion, die ihre
gewünschte Funktionalität realisiert. Aus diesem Grund sind auch alle
Kontrollelemente durch das Zusenden von Nachrichten ansprechbar.
Dies erklärt auch, warum der Rahmen eines Standardfensters bei der
Erzeugung ein ganz gewöhnliches Fensterhandle erhält. Der Rahmen ist
nichts anderes als ein spezielles, vom System vordefiniertes Fenster.
In manchen Situationen würde man gern die vom System vordefinierten
Kontrollelemente für eigene Zwecke anpassen. Dazu wäre eventuell nur eine
Kleinigkeit an der Fensterfunktion zu manipulieren. Da aber kein Zugriff
auf den Quellcode möglich ist, gibt es hierzu die Möglichkeit der
Unterklassenbildung.
Soll beispielsweise das Verhalten eines Druckknopfes bei Loslassen der
Maustaste verändert werden, erzeugt man hierzu zunächst einen Druckknopf.
Anschließend ruft man die Funktion WinSubclassWindow auf.
Als Rückgabewert erhält man die Adresse der systemeigenen
Fensterfunktion. Als neuen Parameter übergibt man das Handle des zu
manipulierenden Objekts und die Adresse der eigenen Fensterfunktion.
Letztere fängt nur die Nachrichten ab, die manipuliert
werden sollen und ruft ansonsten die Fensterfunktion des Systems.
Um die folgenden Aussagen nicht zu abstrakt werden zu lassen,
wollen wir von einem Fenster auf einen Text ausgehen,
an dem sich ein vertikaler Rollbalken befindet, wie es in der Abbildung
zu sehen ist.
Zur Berechnung der Rollbalken sind folgende Parameter notwendig:
void initWidgetBaum(Widget Shell)
{
Widget wAussen, wMenueLeiste, wList, wEingabe, wLabel,
wSichern, wAbbruch;
Arg arg[10];
Cardinal n;
n=0;
XtSetArg(arg[n], XtNresizable, True); n++;
wAussen = XtCreateManagedWidget(äussen", formWidgetClass,
Shell, arg , n);
n=0;
XtSetArg(arg[n], XtNleft, XtChainLeft); n++;
XtSetArg(arg[n], XtNtop, XtChainTop); n++;
XtSetArg(arg[n], XtNbottom, XtChainTop); n++;
XtSetArg(arg[n], XtNorientation, XtEhorizontal); n++;
wMenueLeiste = XtCreateManagedWidget("menueleiste", boxWidgetClass,
wAussen, arg , n);
initMenue(wMenueLeiste); /* baut den Rest des Menues */
n=0;
XtSetArg(arg[n], XtNfromVert , wMenueLeiste); n++;
XtSetArg(arg[n], XtNright, XtChainLeft); n++;
XtSetArg(arg[n], XtNleft, XtChainLeft); n++;
XtSetArg(arg[n], XtNtop, XtChainTop); n++;
XtSetArg(arg[n], XtNbottom, XtChainTop); n++;
wLabel = XtCreateManagedWidget("eingabelabel", labelWidgetClass,
wAussen, arg, n);
n=0;
XtSetArg(arg[n], XtNfromVert , wMenueLeiste); n++;
XtSetArg(arg[n], XtNfromHoriz , wLabel); n++;
XtSetArg(arg[n], XtNright, XtChainRight); n++;
XtSetArg(arg[n], XtNleft, XtChainLeft); n++;
XtSetArg(arg[n], XtNtop, XtChainTop); n++;
XtSetArg(arg[n], XtNbottom, XtChainTop); n++;
XtSetArg(arg[n], XtNeditType , XawtextEdit); n++;
wEingabe = XtCreateManagedWidget("eingabefeld",
asciiTextWidgetClass, wAussen, arg, n);
n=0;
XtSetArg(arg[n], XtNfromVert , wLabel); n++;
XtSetArg(arg[n], XtNright, XtChainRight); n++;
XtSetArg(arg[n], XtNleft, XtChainLeft); n++;
XtSetArg(arg[n], XtNtop, XtChainTop); n++;
XtSetArg(arg[n], XtNbottom, XtChainBottom); n++;
wListe = XtCreateManagedWidget("liste", listWidgetClass,
wAussen, arg, n);
n=0;
XtSetArg(arg[n], XtNfromVert , wView); n++;
XtSetArg(arg[n], XtNright, XtChainLeft); n++;
XtSetArg(arg[n], XtNleft, XtChainLeft); n++;
XtSetArg(arg[n], XtNtop, XtChainBottom); n++;
XtSetArg(arg[n], XtNbottom, XtChainBottom); n++;
wSichern = XtCreateManagedWidget(ßichern", commandWidgetClass,
wAussen, arg, n);
n=0;
XtSetArg(arg[n], XtNfromVert , wView); n++;
XtSetArg(arg[n], XtNfromHoriz , wSichern); n++;
XtSetArg(arg[n], XtNright, XtChainRight); n++;
XtSetArg(arg[n], XtNleft, XtChainRight); n++;
XtSetArg(arg[n], XtNtop, XtChainBottom); n++;
XtSetArg(arg[n], XtNbottom, XtChainBottom); n++;
XtCreateManagedWidget(äbbruch", commandWidgetClass,
wAussen, arg, n);
}
Fensterklassen
Bei MS-Windows und dem Presentation Manager muß jedes Fenster bei seiner
Erzeugung einer Fensterklasse zugeordnet werden. Wie bereits beschrieben,
ordnet die Klasse dem Fenster eine Fensterfunktion zu, die vom System bei
Eintreffen von Ereignissen aufgerufen wird. Daraus ergibt sich, daß alle
Fenster einer Klasse auf die eintreffenden Nachrichten mit einer gleichen
Funktionalität reagieren.
OrigFensterFkt = WinSubclassWindow(KnopfHandle, MeineFensterFkt);
Die Verwaltung von Rollbalken
Rollbalken werden verwendet, wenn Fenster wie reale Fenster benutzt
werden, bei denen der Betrachter durch einen kleinen Ausschnitt auf ein
größeres Objekt schaut. Um einen anderen Teil des Objektes zu sehen,
wird der Betrachter in der realen Welt seine Position verändern.
Unter grafischen Oberflächen wird dagegen das Objekt hinter dem
Fenster mittels Rollbalken solange verschoben,
bis man den gewünschten Teil sehen kann. Dabei vermittelt der Rollbalken
dem Benutzer zwei Informationen: erstens die Position des Fensters in
Bezug zum Objekt und zweitens das Größenverhältnis zwischen Fenster
und Objekt. Zu diesem Zweck ist es möglich, den Schieber im Rollbalken
zu verschieben und in seiner Größe zu verändern.
Textlänge | Die Länge des Textes in Zeilen |
Textausschnitt | Der sichtbare Ausschnitt des Textes in Zeilen |
Textposition | Die Zeilennummer der ersten sichtbaren Zeile |
Rollbalkenlänge | Die Länge des Rollbalkens |
Schiebergröße | Die Größe des Schiebers im Rollbalken |
Schieberposition | Die Position des Schiebers im Rollbalken |
Für die Längen gelten die folgenden Verhältnisse
Schiebergröße / Rollbalkenlänge} = Textausschnitt / Textlänge
Für die Position gilt:
Schieberposition / Rollbalkenlänge = Textposition / Textlänge
Um dem Benutzer anzuzeigen, an welcher Stelle des Textes er sich befindet, müssen aus den Textparametern die Rollbalkenparameter errechnet werden.
Schiebergröße = (Textausschnitt / Textlänge) * Rollbalkenlänge
Da normalerweise mit ganzzahligen Werten gerechnet wird, würde das Ergebnis meist 0 ergeben, da der Textausschnitt bei größeren Texten immer kleiner als die Textlänge ist und eine ganzzahlige Division erfolgt. Aus diesem Grunde muß die Multiplikation vorgezogen werden. Es ergibt sich:
Schiebergröße = (Textausschnitt * Rollbalkenlänge) / Textlänge
Schieberposition = (Textposition * Rollbalkenlänge) / Textlänge
Wenn der Benutzer den Rollbalken in Bewegung setzt, muß umgekehrt aus den Rollbalkenparametern die neue Textposition errechnet werden.
Textposition = (Schieberposition * Textlänge) / Rollbalkenlänge
Bei der Festlegung der Textlänge ist zu überlegen, ob man die Anzahl der im Fenster sichtbaren Zeilen von der Textlänge abzieht. Andernfalls ist der letzte anzuwählende Ausschnitt fast leer, da die oberste Zeile der letzten Textzeile entspricht. Will man dies nicht, muß genau geprüft werden, ob die letzte Zeile noch erscheint. Es ist meist eine Rundung des Restes der Division notwendig.
Soviel zu den grundlegenden Formeln. In der Praxis werden drei Funktionen benötigt. Eine Funktion reagiert auf das Ereignis des Verschiebens. Die Größe des Schiebers muß neu berechnet und gesetzt werden, wenn sich die Textlänge oder der Ausschnitt verändert. Also muß auch das Vergrößern des Fensters eine Schieberkorrektur auslösen. Die dritte Funktion betrifft die Position des Schiebers. Diese wird neu gesetzt, wenn sich die Textposition ändert, also bei Verwendung der Cursortasten, wenn sich die Textlänge ändert oder wenn vorher die Schiebergröße aufgrund von Textveränderungen oder Größenänderungen des Fensters verändert wurde. Bei diesen Berechnungen sind die Fensterdimensionen notwendig. Zur Ermittlung des Textausschnitts muß schließlich berechnet werden, wieviele Zeilen das Fenster aufnehmen kann.
Neben dem Verschieben kann der Rollbalken zum seitenweisen Blättern verwendet werden. Dies wird erreicht, indem der Bereich im Rollbalken unter- oder oberhalb des Schiebers angeklickt wird. Des weiteren befinden sich im Normalfall unten und oben am Schieber kleine Pfeile, die zum zeilenweisen Verschieben verwendet werden. In den meisten Fällen wird ein Programm dem Benutzer die Positionierung im Text auch durch Cursortasten ermöglichen. Der Programmierer muß darauf achten, daß sich der Rollbalken in diesem Falle gleich verhält wie bei der Verwendung der Rollbalkenlemente.
Verhält sich der Rollbalken sehr sprunghaft, kann dies auf das Problem zurückgehen, daß ein Overflow aufgetreten ist. Bei der Berechnung der Schiebergröße wird beispielsweise die Textlänge zunächst mit der Rollbalkenlänge multipliziert. Sei die Rollbalkenlänge beispielsweise 350, so ist bereits bei einer Textlänge von 100 Zeilen mit einem Overflow einer vorzeichenbehafteten 16-Bit-Variablen zu rechnen. Es ist hier für das Zwischenergebnis eine 32-Bit Operation zu erzwingen. Die Ergebnisse der Berechnungen passen dann wieder bequem in eine 16-Bit Variable.
Die Rollbalkenlänge hängt nicht, wie man erwarten könnte, von der tatsächlichen Größe auf dem Bildschirm ab. Bei GEM ist sie konstant 1000, bei den anderen Systemen kann sie vom Programmierer weitgehend frei eingestellt werden. Damit wird erst einmal erreicht, daß die Schieberposition unabhängig von der Fenstergeometrie bestimmt werden kann. Für die Bestimmung der Schiebergröße wird allerdings die Ausschnittgröße benötigt, so daß man nicht umhin kommt, doch die Fensterdimensionen zu ermitteln. Dieser Problematik haben sich Macintosh und MS-Windows dadurch entzogen, daß die Schiebergröße bei ihnen nicht veränderbar ist.
Unter PM wird das Rollbalkenelement wie alle anderen Kontrollelemente als Fenster behandelt, dem man die entsprechenden Einstellungen zusendet. Die Nachrichten lauten:
SBM_SETSCROLLBAR | Setze die virtuelle Länge des Rollbalkens |
SBM_SETPOS | Setze die Position des Schiebers |
SBM_SETTHUMBSIZE | Setze die Größe des Schiebers |
SBM_QUERYPOS | Erfrage die Position des Schiebers |
SBM_QUERYRANGE | Erfrage die virtuelle Länge des Rollbalkens |
Beim Empfangen der Nachrichten vom Rollbalken empfängt das Elternfenster in seiner Fensterfunktion eine WM_HSCROLL-Nachricht für den horizontalen und eine WM_VSCROLL-Nachricht für den vertikalen Rollbalken. Dabei wird im zweiten Parameter angegeben, welche Aktionen auf dem Rollbalken ausgeführt wurden.
MRESULT EXPENTRY FensterFunktion (HWND FensterHandle, ULONG Nachricht, MPARAM mp1, MPARAM mp2) { HWND RBHandle; /* Rollbalken-Handle */ USHORT pos; switch (Nachricht) { ... case WM_HSCROLL: /* horizontaler Rollbalken */ /* Bestimme das Handle des Rollbalkens */ RBHandle = WinWindowFromID(FensterHandle, DID_HORZSCROLL); switch(SHORT2FROMMP(mp2) { /* was genau passierte? */ case SB_SLIDERPOSITION: /* Verschiebung beendet */ pos = SHORT1FROMMP(mp2); /* Applikationseigene Auswertung der Position */ WinSendMsg(RBHandle,SBM_SETPOS,MPFROMSHORT(pos),NULL); break; case SB_SLIDERTRACK: /* Verschiebung im Gange */ /* Analoge Verschiebung des Fensterinhalts */ break; case SB_LINELEFT: /* eine Position links */ case SB_LINERIGHT: /* eine Position rechts */ case SB_PAGELEFT: /* eine Seite links */ case SB_PAGERIGHT: /* eine Seite rechts */ case SB_ENDSCROLL: /* Maustaste losgelassen */ } break; case WM_VSCROLL: /* vertikaler Rollbalken */ ... break; case WM_PAINT: ... } return WinDefWindowProc (FensterHandle, Nachricht, mp1, mp2); }
In dem Skelett ist die Bearbeitung von Rollbalkenereignissen angedeutet. Nach dem Empfang der Nachricht WM_HSCROLL wird zunächst das Handle des Rollbalkens ermittelt, um ihm später die Nachricht zum Positionieren zusenden zu können. Anschließend wird untersucht, welche Aktion der Benutzer ausgeführt hat. Die Aktion SB_SLIDERPOS sagt aus, daß der Schieber im Rollbalken an eine bestimmte Stelle geschoben wurde. Aus der ersten Hälfte des zweiten Parameters wird ermittelt, an welcher Stelle der Schieber losgelassen wurde. Im Normalfall wird eine Verschiebung des Bildschirminhalts durchgeführt. Gegebenfalls wird dabei die Position des Schiebers korrigiert. Anschließend wird die Position des Schiebers gesetzt. Das Setzen des Schiebers muß vom Programm aus explizit durchgeführt werden, da sonst der Schieber an seine Ausgangsstellung zurückgleitet. Das gilt auch für GEM und Macintosh.
Die Aktionen vom Typ LINE und PAGE betreffen das Blättern mit Hilfe des Pfeils bzw. des Leerraums über oder unter dem Schieber. Gerade in solch einem Fall kann das Problem auftauchen, daß der Benutzer die Maustaste über dem Pfeilsymbol des Rollbalkens gedrückt hält. Dadurch werden meist schneller Nachrichten ausgelöst, als die Applikation scrollen kann. Das führt dazu, daß der Bildschirm nach dem Loslassen der Maustaste noch einige Zeit weiterscrollt. Um dies zu verhindern, kann man die Anzahl der Aktionen akkumulieren bis keine Nachrichten mehr eintreffen. Zu diesem Zweck erhält man beim Loslassen der Maustaste die Nachricht SB_ENDSCROLL.
Die Aktion SB_SLIDERTRACK wird während des Verschiebens bei jeder Bewegung des Schiebers ausgelöst. Dadurch ist es möglich, den Fensterinhalt analog zum Verschieben scrollen zu lassen. Man erhält diese Nachricht also normalerweise mehrfach, bevor die Aktion SB_SLIDERPOS eintrifft. Ob er den Bildschirm mitscrollen läßt, kann der Programmierer selbst entscheiden. Unter MS-Windows werden zur Steuerung des Rollbalken üblicherweise zwei Funktionen verwendet.
SetScrollRange(Handle, SB_HORZ, min, max, TRUE); SetScrollPosition(Handle, SB_HORZ, pos, TRUE);
SetScrollRange legt die virtuelle Länge des Rollbalkens fest und der Aufruf von SetScrollPosition positioniert den inneren Schieber. Eine Größeneinstellung des Schiebers ist wie gesagt nicht vorgesehen. Wird durch die Einstellung der Bereichsgrenzen min und max das Intervall kleiner oder gleich 1, wird der Rollbalken automatisch unsichtbar und vergrößert damit den Fensterinhalt. Ebenso kann auch das Erfragen des Bereichs bzw. der Position über Funktionien durchgeführt werden.
Eine besonders elegante Lösung bieten die Widgetsets unter X an. Dort gibt es ein Widget, daß mit zwei Rollbalken versehen ist und daß ein weiteres Widget aufnehmen kann. Das heißt, daß der Rollbalken als Elternwidget bei der Erzeugung angegeben wird. In diesem Fall übernimmt das System selbstständig die Kontrolle über den Schieber. Da das unterliegende Widget versuchen wird, seinen Inhalt vollständig darzustellen, reicht es seine Gesamtgröße nach oben. Damit kennt das Rollbalkenwidget alle benötigten Parameter: die maximale Widgetgröße und die eigene Größe als Ausschnittsbereich. Es ermittelt daraus selbstständig Größe und Position des Schiebers. Auch die Veränderung des Schiebers braucht der Programmierer nicht mehr zu überwachen. Das Rollbalkenwidget wird den gewünschten Ausschnitt des Kindwidgets automatisch anzeigen.
Daneben bieten die Widgetsets auch eigenständige Rollbalken an, die direkt programmiert und für eigene Zwecke verwendet werden können. Auch in den oben beschriebenen Konstrukten, in denen ein Widget als Kind von Rollbalken verwendet wird, kann das Programm die Steuerung selbst übernehmen, wenn die Originaleigenschaften unerwünscht sind.
Inhaltsverzeichnis | (C) Copyright 1993-1999 Arnold Willemer |