Windows unter GEM, Teil 2: Interaktive Fenstertechnik unter GEM

In Teil 1 dieser Serie haben Sie erfahren, wie man die grundlegendsten Routinen, die GEM zur Fensterprogrammierung zur Verfügung stellt, handhabt und ein einfaches Fenster auf den Bildschirm bringt. Im folgenden werden Sie nun zu Experten in Sachen interaktiver Fensterprogrammierung. Denn eine ausgereifte GEM-Applikation sollte in der Lage sein auf Benutzerwünsche einzugehen und so in diesem Zusammenhang dem Benutzer überlassen, wo, wie groß, etc. er ein Fenster haben möchte.

Let’s go...

GEM-AES nimmt uns für unser Vorhaben eine Menge Arbeit ab. Wir brauchen uns um nichts weiter zu kümmern, als die gewünschten Parameter beim Öffnen eines Fensters richtig zu setzen und dann auf eine Meldung des GEM zu warten, die uns mitteilt, was der Benutzer mit unserem Fenster anstellen möchte, und gleich auch noch die entsprechenden Werte liefert. Soweit das Vorgehen. Bevor wir aber überhaupt etwas Neues tun, sollten wir uns noch ein wenig um die im letzten Teil vorgestellten Routinen kümmern. Schließlich hat es ja keinen Sinn, für jedes neue Fenster, das wir eröffnen möchten, so einen Rattenschwanz von Programmtext einzufügen. Viel eleganter ist es, eine Funktion zu entwerfen, der wir die gewünschten Werte mitteilen und die dann alles für uns erledigt. So reduzieren wir den Rattenschwanz auf einen einzigen Funktionsaufruf. (Vergleiche auch die beiden Funktionen gem_init()* und gem_exit() in Listing 2 der letzten Folge). Um das Ganze abzurunden stecken wir diese Routinen in eine eigene Bibliothek, die dann nur noch zum eigentlichen Programm dazugelinkt werden muß und so die Übersichtlichkeit verbessert. Das Ergebnis sehen Sie in Listing 1. Compilieren Sie diesen Source-Text separat und fügen Sie das Compilat in die Liste der hinzuzubindenden Bibliotheken ein, wenn sie den Linker starten.

Achtung News!

Nachdem wir mit der selbstgefertigten Routine open_window() ein Fenster auf den Bildschirm gebracht haben (und etwas auf dieses Fenster ausgegeben haben - dies bleibt einer der nächsten Folgen Vorbehalten), warten wir, d.h. unser Programm, darauf, daß der Benutzer etwas tut. Die entsprechende Routine, die uns diese Arbeit abnimmt und uns die vom Benutzer getätigten Aktionen meldet, ist evnt_mesag(). Hier die Definition:

event = evnt_mesag(buffer);

WORD event : ist immer 1, d.h. wir können diese Variable vergessen.

WORD buffer[8] : ein Buffer, in dem evnt_mesag() die Benutzeraktionen meldet. Folgend eine Liste der möglichen Nachrichten, auf die unser Programm entprechend zu reagieren hat:

  1. MN_SELECTED (falls eine Menüzeile installiert ist). In diesem Fall enthält buffer[3] den Objektindex des Menütitels und buffer[4] den Objektindex des Menüpunktes, der angeklickt wurde.

  2. WM_REDRAW : GEM verlangt von uns, das Fenster bzw. Ausschnitte davon neu zu zeichnen. Wie das geht, werde ich in einer der nächsten Folgen zeigen. Vorläufig löschen wir einfach den Fensterinhalt durch einen v_bar()-Aufruf [Vergleiche Listing 2, do_redraw() ]. Buffer[3] enthält dabei die Identifikationsnummer des neu zu zeichnenden Fensters, buffer[4] bis buffer[7] die Koordinaten (x,y,Breite,Höhe) des Fensterbereichs, der neu gezeichnet werden muß.

  3. WM_TOPPED : buffer[3] enthält die Nummer des Fensters, das der Anwender angeklickt hat und damit nach oben bringen möchte. Unser Programm erledigt dies mittels wind_set() : wind_set (buffer[3],WF_TOP,buffer[3],0,0,0);

  4. WM_CLOSED : Der Benutzer hat die Schließecke des Fensters angeklickt, d.h. unser Programm soll das Fenster vom Bildschirm verschwinden lassen. Mit wind_close(buffer[3]); kein Problem!

  5. WM_FULLED : Der Benutzer hat die Volle-Größe-Ecke angeklickt, und unser Programm soll entweder das Fenster auf die volle Größe bringen (normalerweise der ganze Desktop, bzw. die in wind_create() angegebenen Werte), oder, wenn dies bereits der Fall ist, es auf die vorherige Größe verkleinern. GEM ermöglicht dies, da es die vorherigen, die momentanen und die maximalen Koordinaten eines Fensters speichert. Wir können mit wind_get() darauf zugreifen. In Listing 2 erledigt das eine eigene Funktion handle_full(). Sie ermittelt zuerst die vorherigen, momentanen und maximalen Koordinaten, vergleicht die momentane Größe mit der maximalen und vergrößert bzw. verkleinert dann das Fenster wie gewünscht.

  6. WM_ARROWED : Pfeile oder Scrollbalken wurden angeklickt. Buffer[3] enthält die Nummer des Fensters, und buffer[4] gibt an, was genau angeklickt wurde.

  7. WM_HSLID : Der Benutzer bewegte den horizontalen Schiebebalken. Buffer[3] enthält wie gewohnt die Nummer des Fensters und buffer[4] gibt die neue Position an.

  8. WM_VSLID : Der Benutzer bewegte den vertikalen Schiebebalken. Übriges siehe bei WM_HSLID.

-> Genaueres über die Meldungen 6 bis 8 in einer der nächsten Folgen, wenn von Dokumenten und Scrollbalken die Rede sein wird.

  1. WM_SIZED: Die Fenstergröße wurde verändert. Buffer[3] teilt uns die Fensternummer mit, buffer[4] bis buffer[7] die X- und Y-Koordinaten und die neue Breite und Höhe.

  2. WM_MOVED : Das Fenster wurde verschoben. Buffer[3] enthält wieder die Id des Fensters und buffer[4] bis buffer[7] die neuen Koordinaten.

  3. WM_NEWTOP: Das Fenster mit der Nummer in buffer[3] wurde aktiviert (nach oben gebracht).

Die folgenden Meldungen sind nur der Vollständigkeit halber aufgeführt. Wir brauchen sie momentan nicht.

  1. AC_OPEN : Buffer[4] enthält die Nummer des Accessorys, die durch den Aufruf von menu_register() diesem Programm mitgeteilt wurde. Diese Meldung erhält ein Accessory, wenn sein Menüpunkt im Desk-Menü angeklickt wurde.

  2. AC_CLOSE : Diese Meldung erhält ein Accessory, wenn a) das laufende Programm beendet wurde, b) der Bildschirm gelöscht werden wird oder c) Daten der Window-Library reinitialisiert werden. Im Klartext heißt diese Meldung: “Hau ab”, d.h. man soll alle Fenster löschen. Buffer[3] enthält die Id des entsprechenden Accessorys.

In Listing 2 sehen Sie, wie durch eine switch()-Anweisung die in buffer[0] enthaltene Meldung ausgewählt wird und die entsprechenden Aktionen getätigt werden. Dies wird solange durch eine do(...)while()-Schleife wiederholt, bis der Benutzer die Schließecke anklickt. Probieren Sie es aus und experimentieren Sie auch mit den Windowelementen, die in open_window() angegeben sind. Lassen Sie ein Element fort, so erhalten Sie auch keine entsprechende Meldung mehr. D.h. wenn Sie z.B. “MOVER” weglassen, kann der Benutzer das Fenster nicht mehr verschieben.

P.S. Anstelle von evnt_mesag(), die nur die von GEM-AES abgeschickten Meldungen übermittelt, können wir auch evnt_multi() gebrauchen. Diese GEM-Funktion vereinigt mehrere andere Funktionen in sich. So kann mit ihr auch eine Meldung empfangen werden, für die sonst evnt_button(), evnt_dclick(), evnt_keybd(), evnt_mesag(), evnt_mouse() und evnt_timer() zuständig sind. Das heißt z.B., daß das Program auf eine Menüselektion, eine Windowaktivität, einen Tastendruck, eine Mausbewegung und einen Timer-Event gleichzeitig warten kann. Da wir aber in unserem Programm nur die Events, die sich auf die Fenster beziehen brauchen, benutzen wir die einfacher zu handhabende Funktion evnt_mesag(). Für weitere Informationen über evnt_multi() sehen Sie bitte in Ihr GEM-Handbuch (z.B. Abschnitt D des Lattice-C-Handbuches: GEM VDI und AES Funktionen, Seite 560 ).

In der nächsten Folge werde ich zeigen, wie man Fenster “snappt”, d.h dafür sorgt, daß immer das ganze Fenster auf dem Bildschirm zu sehen ist, oder wie man Fensterkoordinaten an Wortgrenzen ausrichtet ...

    /* Bibliotheksfunktionen zur */
   /* Fensterprogrammierung     */
  /* Andreas Lötscher 1988    */
 /*****************************/

#include <a:\headers\portab.h>
#include <c:\listing1.h>

WORD    ap_id, handle, work_in[12], work_out[57]; 
char    wind_name[80] = "";

void gem_init() /* erledigt die leidigen Gem-Initialisierungen am Anfang */
{       WORD gr_1, gr_2, gr_3, gr_4, i ;

        ap_id = 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() /* Gegenstück zu gem_init() am Ende der Applikation */
{
    v_clsvwk(handle); 
    appl_exit(); 
    exit(0);
}

WORD open_window(was,x1,y1,w1,hl)
WORD    x1,y1,w1,h1,    /* Startkoordinaten   */
        was;            /* Liste der Elemente */
{
        WORD    w_handle,wi_dsk[4],clip[4];

        /* ermittelt die Grösse des Desktop: */ 
        wind_get(0,4,&wi_dsk[0],&wi_dsk[1],
                     &wi_dsk[2],&wi_dsk[3]);
        /* und setzt die Maximalgrösse des Fensters auf diese Werte: */ 
        w_handle=wind_create(was,wi_dsk[0],wi_dsk[1],wi_dsk[2],wi_dsk[3]);
        wind_set(w_handle,2,ADDR(wind_name),0,0);
        /* zeichnet öffnende Box: */ 
        graf_growbox(0,0,0,0,x1,y1,w1,h1); 
        wind_open(w_handle,x1,y1,w1,h1); 
        wind_get(w_handle,4,&clip[0],&clip[1],&clip[2],&clip[3]); 
        clip[2] += clip[0]-1; clip[3] +=clip[1]-1; 
        v_hide_c(handle); 
        vs_clip(handle,1,clip); 
        vsf_color(handle,0); 
        v_bar(handle,clip); 
        v_show_c(handle,1);
        /* schließlich geben wir dem rufenden 
           Programm die Identifikationsnummer 
           des Fensters zurück: */ 
        return(w_handle);
}
       /* Demoprogramm zur Fensterverwaltung    */ 
      /* 1988 by Andreas Loetscher             */
     /* verwendeter Compiler : Lattice C 3.04 */ 
    /*****************************************/

#include <a:\headers\portab.h>
#include <c:\listing1.h>

extern char wind_name[];   /* in listing1.c */
extern WORD ap_id, handle; /* definierte    */
                           /* Variablen     */


void
clear_window(w_hndl)
WORD  w_hndl;
{
    WORD clip[4];

    wind_get(w_hndl,WF_WORKXYWH,&clip[0],&clip[1],ficlip[2],&clip[3]); 
    clip[2] += clip[0]; clip[2]--; 
    clip[3] += clip[1]; clip[3]--; 
    v_hide_c(handle); 
    vsf_color(handle,0); 
    vs_clip(handle,1,clip); 
    v_bar(handle, clip); 
    v_show_c(handle);
}

WORD
rc_equal(p1, p2)
GRECT   *p1,*p2;
{
    if((p1->g_x != p2->g_x) ||
       (p1->g_y != p2->g_y) ||
       (p1->g_w != p2->g_w) ||
       (p1->g_h != p2->g_h)) return(FALSE); 
    return(TRUE);
}

void
handle_full (w_hndl)
WORD    w_hndl;
{
    GRECT prev;
    GRECT curr;*
    GRECT full;

    wind_get(w_hndl,WF_CURRXYWH,&curr.g_x,&curr.g_y,&curr.g_w,&curr.g_h); 
    wind_get(w_hndl,WF_PREVXYWH,&prev.g_x,&prev.g_y,&prev.g_w,&prev.g_h); 
    wind_get(w_hndl,WF_FULLXYWH,&full.g_x,&full.g_y,&full.g_w,&full.g_h); 
    if (rc_equal(&curr,&full))
    {   /* Window ist auf voller Groesse, jetzt auf alte Groesse setzen : */ 
        graf_shrinkbox(prev.g_x,prev.g_y,prev.g_w,prev.g_h,full.g_x,full.g_y,full.g_w,full.g_h); 
        wind_set(w_hndl,WF_CURRXYWH,prev.g_x,prev.g_y,prev.g_w,prev.g_h);
    }
    else
    { /* Window ist nicht auf voller Groesse, deshalb auf volle Groesse setzen : */
        graf_growbox(curr.g_x,curr.g_y,curr.g_w,curr.g_h, full.g_x,full.g_y,full.g_w,full.g_h); 
        wind_set(w_hndl,WF_CURRXYWH,full.g_x,full.g_y,full.g_w,full.g_h);
    }
}

void 
main()
{
    WORD w_handle, buffer[8]; 

    gem_init();

    strcpy(wind_name,"ein kleines Fenster"); 
    w_handle=open_window(NAME+CLOSER+FULLER+MOVER
             +INFO+SIZER+UPARROW+DNARROW+VSLIDE
             +LFARROW+RTARROW+HSLIDE,
             30,30,500,300);

    do
    {
        evnt_mesag(buffer); 
        switch(buffer[0])
        {
            case MN_SELECTED: break;
            case WM_REDRAW  : clear_window(buffer[3]); 
                              break;
            case WM_TOPPED  : wind_set(buffer[3],WF_TOP, buffer[3],0,0,0); 
                              break;
            case WM_CLOSED  : wind_close(buffer[3]); 
                              break;
            case WM_FULLED  : handle_full(buffer[3]);
                              break;
            case WM_ARROWED :
                form_alert(1,"[1][WM_ARROWED[ok]");
                break; 
            case WM_HSLID   :
                          wind_set(buffer[3],WF_HSLIDE,buffer[4],0,0,0);
                          break;
            case WM_VSLID   :
                          wind_set(buffer[3],WF_VSLIDE,buffer[4],0,0,0);
                          break;
            case WM_SIZED   :
            case WM_MOVED   :
                          wind_set(buffer[3],WF_CURRXYWH,buffer[4],buffer[5],buffer[6],buffer[7]);
                          break;
            case WM_NEWTOP  : clear_window(buffer[3]); 
                              break;
            default         : break;
        }
    }while(buffer[0] != WM_CLOSED);

    wind_delete(w_handle); 

    gem_exit();
}

Andreas Lötscher
Aus: ST-Computer 06 / 1989, Seite 127

Links

Copyright-Bestimmungen: siehe Über diese Seite