Eines der wichtigsten Werkzeuge, die eine grafische Benutzeroberfläche wie GEM dem Benutzer zur Verfügung stellt, ist eine Fensterverwaltung. Sie ermöglicht dem Anwender, gleichzeitig mehrere Dinge auf einem einzigen Bildschirm zu tun - so zum Beispiel drei verschiedene Texte zur selben Zeit zu schreiben. Für diese Freiheit muß aber (wie üblich) der Programmierer zahlen. Er sieht sich gezwungen, sein Programm auf eine wesentlich komplexere Art mit der Umgebung kommunizieren zu lassen.
Der folgende Artikel soll dazu dienen, sowohl dem Einsteiger die grundlegenden Routinen vorzustellen, als auch fortgeschrittenen Profis wertvolle Tips und Tricks zu vermitteln. Das Endziel dabei ist ein komfortabler und flexibler Window-Manager, der das leidige Programmieren von Windows in eigenen Programmen vereinfachen soll.
Kenntnisse der Programmiersprache C sind für das Verständnis dieses Artikels sicher von Vorteil, doch auch Uneingeweihte sind in der Lage, die anfangs vorgestellten Routinen zu durchschauen und sie nach Konsultation des Handbuches in ihrer Lieblingsprogrammiersprache zu verwenden!
Bevor wir in unserem Programm auf dem Bildschirm ein wunderschönes Window erblicken, müssen wir einiges an Vorarbeit leisten. Zunächst wird mit wind_create ein Fenster vereinbart:
handle = wind_create(flags, x, y, w, h);
WORD handle: Identifikationsnummer des Fensters, über die wir im folgenden das Fenster ansprechen können. Sie wird vom AES vergeben, um mehrere Fenster auf dem Bildschirm unterscheiden zu können.
WORD flags: Komponenten des Fensters, die gezeichnet werden sollen. Sie umfassen: Titelbalken, Schließecke, Volle-Größe-Ecke, Verschiebebalken, Infozeile, Größen-Ecke, Pfeil nach oben, Pfeil nach unten, vertikaler Schieber, Pfeil nach links, Pfeil nach rechts, horizontaler Schieber (vgl. Listing 1 und Bild 1).
Achtung! Wenn ein Element nicht angegeben wird, kann das Programm auch keine entsprechende Message erhalten. Wenn also z.B. der Verschiebebalken nicht genannt wird, kann der Benutzer das Fenster nicht verschieben, und das Programm erhält nie eine Meldung vom Event-Manager (Genaueres vgl. weiter unten).
WORD x,y,w,h: X-/Y-Koordinaten und Breite/Höhe des Fensters in der maximalen Größe (meistens ganzer Desktop ohne Menüleiste, weiteres siehe unten).
Nachdem GEM nun weiß wie unser Fenster auszusehen hat, und welche Maximalgröße es besitzt, können wir es auch mit dem folgenden Befehl auf dem Bildschirm darstellen:
error = wind_open(w_handle,x,y,w,h);
WORD error: gleich 0, wenn ein Fehler aufgetreten ist
WORD w_handle: der von wind create zurückgegebene Wert
WORD x,y,w,h: geben die aktuelle auf dem Bildschirm dargestellte Größe des Fensters an
Achtung! Wurde NAME bei wind_create() angegeben, sollte vor der Darstellung mit wind_open() der Name des Fensters gesetzt werden, da sonst unvorhergesehene Wirkungen auftreten können.
Diese Arbeit kann uns wind_set() erledigen:
error = wind_set(w_handle,was,a,b,c,d);
error : bei Fehler gleich 0
w_handle : Fensteridentifikation
was : definiert, was geändert werden soll, vgl. Listing 1
a,b,c,d : enthalten je nach ‘was’ die jeweiligen Werte (vgl. Anhang)
Um den Fensternamen zu setzen, verwenden wir WF_NAME und geben in a und b die oberen 16 Bits bzw. die unteren 16 Bits der Adresse an, an der sich unser Namensstring befindet. (Wir erzeugen diese mittels des Makros ADDR aus Listing 1). Der Aufruf sieht folgendermaßen aus:
error = wind_set(w_handle,WF_NAME, ADDR(wind_name),0,0);
Endlich steht unser Fenster auf dem Bildschirm, doch leider noch nicht ganz perfekt, denn nur der Rahmen wurde gezeichnet, während die sogenannte Arbeitsfläche (Inneres des Fensters) nicht berührt wurde. Wir sollten diese also noch mittels eines v_bar()-Aufrufs weiss füllen. Um die Koordinaten der Arbeitsfläche zu ermitteln, verwenden wir wind_get():
error = wind_get(w_handle,was,a,b,c,d);
Diese Prozedur ist wind_set() ähnlich, verändert aber keine Windowdaten, sondern liefert uns die aktuellen Werte. Für die Koordinaten des Arbeitsbereiches setzen wir was auf WF_WORKXYWH und erhalten in a,b,c,d die gewünschten Werte:
error=wind_get(w_handle,WF_WORKXYWH,a,b,c,d);
Listing 2 zeigt dies alles noch einmal in Form eines kurzen Programms, das ein Fenster mit allem Drum und Dran eröffnet, auf einen Mausklick wartet und es darauf wieder schließt. Ein Window zu schließen, ist im Gegensatz dazu es zu öffnen, ganz leicht. Dazu ist der Aufruf von wind_close() notwendig:
error = wind_close(w_handle);
error : 0, falls Fehler
w_handle : Identifikation des zu schließenden Fensters
Jetzt ist unser Fenster vom Bildschirm verschwunden, existiert für GEM aber noch und kann jederzeit wieder mittels wind_open() geöffnet werden! Um es aus der GEM-Liste zu löschen, benötigen wir wind_delete():
error = wind_delete(w_handle);
Jetzt ist es endgültig begraben und seine Identifikationsnummer kann von GEM für ein anderes Fenster verwendet werden...
/* HEADERDATEI, DIE WICHTIGE KONSTANTEN FÜR DIE
WINDOW-ROUTINEN ENTHÄLT
(vgl. Gem-Bibliothek ihres C-Compilers !) */
/* Definitionen für wind_create() */
#define NAME 0x0001
#define CLOSER 0x0002
#define FULLER 0x0004
#define MOVER 0x0008
#define INFO 0x0010
#define SIZER 0x0020
#define UPARROW 0x0040
#define DNARROW 0x0080
#define VSLIDE 0x0100
#define LFARROW 0x0200
#define RTARROW 0x0400
#define HSLIDE 0x0800
/* Definitionen für wind_get() bzw. wind_set() */
#define WF_KIND 1
#define WF_NAME 2
#define WF_INFO 3
#define WF_WORKXYWH 4
#define WF_CURRXYWH 5
#define WF_PREVXYWH 6
#define WF_FULLXYWH 7
#define WF_HSLIDE 8
#define WF_VSLIDE 9
#define WF_TOP 10
#define WF_FIRSTXYWH 11
#define WF_NEXTXYWH 12
#define WF_RESVD 13
#define WF_NEWDESK 14
#define WF_HSLSIZE 15
#define WF_VSLSIZE 16
#define WF_SCREEN 17
/* Definitionen für wind_update() */
#define END_UPDATE 0
#define BEG_UPDATE 1
#define END_MCTRL 2
#define BEG_MCTRL 3
/* Definitionen für wind_scroll() */
#define W_UPPAGE 0
#define W_DNPAGE 1
#define W_ÜPLINE 2
#define W_DNLINE 3
#define W_LFPAGE 4
#define W_RTPAGE 5
#define W_LFLINE 6
#define W_RTLINE 7
/* Definitionen für wind_calc() */
#define WC_BORDER 0
#define WC_WORK 1
/* Definitionen für evnt_mesag() */
#define MU_KEYBD 0x0001
#define MU_BUTTON 0x0002
#define MU_M1 0x0004
#define MU_M2 0x0008
#define MU_MESAG 0x0010
#define MU_TIMER 0x0020
/* von evnt_mesag() gemeldete Aktionen */
#define MN_SELECTED 10
#define WM_REDRAW 20
#define WM_TOPPED 21
#define WM_CLOSED 22
#define WM_FULLED 23
#define WM_ARROWED 24
#define WM_HSLID 25
#define WM_VSLID 26
#define WM_SIZED 27
#define WM_MOVED 28
#define WM_NEWTOP 29
#define AC_OPEN 40
#define AC_CLOSE 41
/* Definitionen für graf_growbox() bzw. für graf_shrinkbox() */
#define FMD_START 0
#define FMD_GROW 1
#define FMD_SHRINK 2
#define FMD_FINISH 3
/* Strukturdefinition für Rechteckslisten */
typedef struct grect
{
short g_x;
short g_y;
short g_w;
short g_h;
} GRECT;
/* Ein Makro, das einen 32 Bit Pointer-Wert als
zwei 16 Bit Werte zurückgibt.
Wird in wind_set() gebraucht, um die Adresse
des Fensternamens zu übergeben */
#define ADDR(a) ((long)(a) >> 16), ((long)(a) & 0xFFFF)
/* Makros, die zwei Zahlen vergleichen und den
groesseren bzw. kleineren Wert zurueckgeben
*/
#define _max(a,b) ((a)>(b)?(a):(b))
#define _min(a,b) ( (a) <= (b) ? (a) : (b) )
Listing: Headerdatei
Wie Sie nach Starten des Beispielprogramms feststellen können, erscheint wie erwartet ein Fenster mit allen möglichen Komponenten auf dem Bildschirm. (Spielen Sie ruhig mit den Parametern von wind_create() und wind_open() und finden Sie heraus, wie sich die Elemente und die Größe des Fensters verändern!) Nachdem Sie die linke Taste der Maus gedrückt haben, läßt das Programm das Fenster verschwinden, und Sie befinden sich wieder im Desktop. Wenn Sie versuchen das Fenster zu verschieben oder seine Größe zu verändern, werden Sie feststellen, daß dies zwar auf den ersten Blick möglich ist, da auf dem Bildschirm ein Gummiband erscheint, das die neue Position bzw. die neue Größe anzeigt: sobald Sie aber die Maustaste loslassen, geschieht nichts. Kein Wunder! Denn unser Programm ist noch gar nicht so weit gereift, um auf diese Benutzerwünsche Rücksicht nehmen zu können, und ignoriert die Meldungen, die es vom GEM-AES (Teil des GEM, der u.a. die Windowoperationen zur Verfügung stellt und für das Gummiband von vorhin verantwortlich ist) erhält, ganz einfach. Wie dies genau aussieht und wie man es in eigenen Programmen implementiert, erfahren Sie in der nächsten Ausgabe!
/* Demoprogramm zur Fensterprogrammierung */
/* 1988 by Andreas Lötscher */
/* verwendeter C—Compiler Lattice C 3.04 */
/*******************************************/
#include <portab.h>
#include <listingl.h>
WORD handle, work_in[12], work_out[57];
/* allg. Variablen */
void
gem_init()
{
WORD gr_1, gr_2, gr_3, gr_4, i;
appl_init();
handle = graf_handle(&gr_1,&gr_2,&gr_3,&gr_4);
for( i=0; i<10; work_in[i++]=1)
;
work_in[10] = 2;
v_opnvwk(work_in,&handle,work_out);
}
void
gem_exit()
{
v_clsvwk(handle);
appl_exit();
exit(0);
}
void
main()
{
WORD w_handle, x, y, button, state, clip[4];
char name[80];
gem_init(); /* meldet die Applikation an */
w_handle = wind_create(NAME+CLOSER+FULLER+MOVER+INFO
+SIZER+UPARROW+DNARROW+VSLIDE
+LFARROW+RTARROW+HSLIDE,
20,40,500,300);
strcpy(name,"... zum Beispiel ein kleines Fenster");
wind_set (w_handle, WF_NAME, ADDR(name),0,0);
wind_open(w_handle,50,50,400,200);
wind_get(w_handle,WF_WORKXYWH,&clip[0],&clip[1],&clip[2],&clip[3]);
/* Da wind_get() Breite und Höhe liefert */
/* müssen wir die Werte für vs_clip() erst */
/* in absolute Koordinaten umwandeln : */
clip[2] += clip[0]-1;
clip[3] += clip[1]-1;
v_hide_c(handle); /* Maus verstecken */
vs_clip(handle,1,clip); /* Clipping setzen */
vsf_color(handle,0); /* Füllfarbe = Weiß */
v_bar(handle,clip); /* Fenster füllen */
v_show_c(handle,1); /* Maus wieder zeigen */
evnt button(1,1,1,&x,&y,&button,&state);
/* wartet auf Maustastendruck */
wind_close(w_handle);
wind_delete(w_handle);
gem_exit(); /* meldet die Applikation ab */
}
Listing: Demoprogramm zur Fensterprogrammierung
was = WF_WORKXYWH: Ermitteln der Arbeitsbereichskoordinaten. a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = WF_CURRXYWH: Ermitteln der Koordinaten des gesamten Fensters einschließlich des Randbereichs. a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = WF_PREVXYHW: unmittelbar vorhergehende Koordinaten des gesamten Fensters einschließlich des Randbereichs. a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = WF_FULLXYWH: Koordinaten des gesamten Fensters einschließlich des Randbereichs, in seiner maximalen Größe (Werte, die wind_create übergeben wurden). a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = WF_HSLIDE: Position des horizontalen Schiebers. a=Nummer zwischen 1 und 1000, wobei 1 die ganz linke und 1000 die ganz rechte Position des Schiebers angibt.
was = WF_VSLIDE: Position des vertikalen Schiebers. a=Nummer zwischen 1 und 1000, wobei 1 die ganz linke und 1000 die ganz rechte Position des Schiebers angibt.
was = WF_TOP: Bestimmen der Nummer des aktiven, obersten Fensters, a = w_handle des aktiven Fensters.
was = WF_FIRSTXYWFI: Koordinaten des ersten sichtbaren Rechtecks des Arbeitsbereiches aus der Rechteckliste des Fensters (vgl. auch spätere Erklärungen zum Redrawing von Fenstern). a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = NEXTXYWH: Koordinaten des nächsten sichtbaren Rechtecks des Arbeitsbereiches aus der Rechteckliste des Fensters (vgl. auch spätere Erklärungen zum Redrawing von Fenstern). a=X-Koordinate, b=Y-Koordinate, c=Breite, d=Höhe.
was = WF HSLSIZE: Ermitteln der Länge des horizontalen Schiebers im Verhältnis zur Länge des Scrollbalkens. a=Nummer, entweder-1 oder zwischen 1 und 1000. -1 ist die kleinste mögliche Größe (ein Quadrat). 1000 bedeutet, daß der Schieber gleich lang wie der Scrollbalken ist.
was = WF_VSLSIZE: Ermitteln der Länge des vertikalen Schie bers im Verhältnis zur Länge des Scrollbalkens. Parameter vergleiche WF_HSLSIZE.
was = WF_KIND: Ändern der Komponenten, die das Fenster besitzt, a enthält die neuen Komponenten (vgl. wind_create).
was = WF_NAME: Setzen der Adresse des Namenstrings des Fensters. a=Highword, b=Lowword der Stringadresse.
was = WF_INFO: Setzen der Adresse des Informationstrings des Fensters. a=Highword, b=Lowword der Stringadresse.
was = WF_CURRXYWH: Ändern der Koordinaten des gesamten Fensters einschließlich des Randbereichs, a = X-, b = Y-, c = W-, d = H-Koordinate.
was = WF_HSLIDE: Änderung der Position des horizontalen Schiebers, a = Nummer zwischen 1 und 1000, wobei 1 die ganz linke und 1000 die ganz rechte Position des Schiebers angibt.
was = WF_VSLIDE: Änderung der Position des vertikalen Schiebers. a = Nummer zwischen 1 und 1000, wobei 1 die oberste und 1000 die unterste Position des Schiebers angibt.
was = WF_TOP: Setzen des aktiven Fensters, a = w_handle des Fensters, das nach oben gebracht werden soll.
was = WF_NEWDESK: Änderung des Desktophintergrundes. a=Highword, b=Lowword der Adresse des Objektbaumes und c=Objekt, das als erstes gezeichnet werden soll.
was = WF_HSLSIZE: Änderung der Länge des horizontalen Schiebers im Verhältnis zur Länge des Scrollbalkens. a=Nummer, entweder-1 oder zwischen 1 und 1000. -1 ist die kleinste mögliche Größe (ein Quadrat). 1000 bedeutet, daß der Schieber gleich lang wie der Scrollbalken ist.
was = WF_VSLSIZE: Änderung der Länge des vertikalen Schiebers im Verhältnis zur Länge des Scrollbalkens. Parameter vergleiche WF_HSLSIZE.