Shadow-Buttons (GFA-Basic)

GEM-Buttons tauchen in fast allen Programmen auf und sind ein schnell zu erlernendes Bedienungselement, da sie wie ein Knopf an einem Gerät funktionieren. Doch - mal ehrlich - welcher richtige Knopf wird beim Hineindrücken schon schwarz bzw. invers?

So sieht die Dialog-Box zum Programm aus, die man mit einem Resource-Construction-Set erstellt. Sie enthält vier Radio-Buttons (UKW/MW/KW/LW), einen normalen Button (EIN) und einen Button zum Beenden des Dialogs (OK). Die vier Radio-Buttons befinden sich in einer übergeordneten Box, für die kein Name angegeben werden muß. Einer der Radio-Buttons wird ohne Schatten gezeichnet (hier UKW), er ist also vorgewählt, bei den anderen wird das Shadow-Flag gesetzt.

Id in richtiger Knopf wird eher in einer tieferen Position einrasten, um sich bei erneuter Betätigung wieder in die ursprüngliche Position zu bewegen. Dieses Verhalten soll nun auch auf dem Bildschirm simuliert werden. Da dieser aber nur zweidimensional ist, werden zur Darstellung der dritten Dimension die Mächte aus dem Schattenreich in Gestalt des fünften Status-Bits zu Hilfe gerufen.

Dieses Bit legt fest, ob der Schatten eines Objekts beim Zeichnen zur Darstellung kommt oder nicht. Durch den Schatten wird der Eindruck eines aus der Benutzeroberfläche herausragenden Knopfes erweckt, der durch einen Rahmen (outlined) noch verstärkt wird, welcher die Öffnung in der 'Frontplatte' des imaginären Geräts darstellt.

Konstruktion

Damit wären die Kriterien zur Kreation eines Shadow-Buttons mit einem Resource-Construction-Set bereits festgelegt. Man benutze folgendes Kochrezept: ziehe ein Objekt vom Typ G Boxtext in die Dialogbox und -setze die Flags für Outlined, Touchexit und Shadow. Soll der Button beim Zeichnen der Dialogbox bereits selektiert sein (entspricht Preselected bei einem normalen Objekt), setzt man den Schatten nicht. Der Rand wird auf 1 Pixel nach innen gesetzt, so daß der Schatten sich zwischen dem Objektrand und dem Objektrahmen befindet. Bei Radio-Buttons erfolgt natürlich noch das Setzen des Flags Radio-Button. Das war’s.

Die Verwaltung der zusätzlichen Routinen zur Verwendung von Shadow-Buttons wird einfach in einer Schleife erledigt, welche als einzigen Aufruf den FORM DO-Befehl enthält. Die Schleife wird verlassen, wenn das Objekt, durch das der Dialog beendet wurde, kein Shadow-Button war. In diesem Fall liefert die Funktion shadow_-hut(...) den Wert FALSE (=0) zurück.

Da durch dieses Prinzip generell die Möglichkeit besteht, das Aussehen und Verhalten von Formular-Objekten zu beeinflussen, gehe ich im folgenden konkreter auf die einzelnen Prozeduren/Funktionen ein. Noch weitergehende Gestaltungsmöglichkeiten würde die Verwendung von User defined objects bieten, doch leider (noch) nicht für GFA-BASIC-Programmierer. In der aktuellen Version ist es nämlich nicht möglich, die Adresse einer Prozedur/Funktion zu ermitteln, was aber für den Aufruf selbstdefinierter Objekte notwendig ist. Es bleibt zu hoffen, daß in der nächsten GFA-BASIC-Version ein entsprechender Befehl implementiert sein wird. Solange dieser Wunsch aber nicht in Erfüllung gegangen ist, müssen wir uns mit dem begnügen, was möglich ist. und wenden uns den einzelnen Prozeduren/Funktionen des Programms zu.

Identitätskontrolle

Am Anfang der Funktion shadow_but(...) muß zuerst einmal festgestellt werden, ob es sich bei dem zu untersuchenden Objekt überhaupt um einen Shadow-Button handelt. Dessen Identität stellen Sie mit der Funktion check_obj(...) ganz einfach durch Abfragen der entsprechenden Status- und Funktions-Flags fest.

Als nächstes muß das Programm zwischen Radio- und normalen Buttons unterscheiden können, was sich wiederum mit einer Abfrage des entsprechenden Funktions-Flags feststellen läßt. Bei einem normalen Button muß dieser lediglich (de)selektiert und neu gezeichnet werden. Bei einem Radio-Button muß dieser selektiert und der ‘alte’ Button deselektiert werden. Um diesen zu finden, müssen alle dazugehörigen Radio-Buttons abgefragt werden, die sich innerhalb der sie umgebenden Box befinden.

Um diese zu finden, nutzt man die Verkettung der Objektbaum-Struktur durch die Zeiger OB_NEXT, OB_HEAD und OB_TAIL aus. In unserem Fall wird nur der OB_NEXT-Zeiger benötigt, der, wie schon der Name sagt, auf das nächste Objekt in der gleichen Hierarchie zeigt. Man erhält also nacheinander die Objekt Nummern der zugehörigen Radio-Buttons. Ist man beim letzten angelangt, zeigt dieses auf das übergeordnete Objekt, was man daran erkennt, daß dessen Nummer kleiner ist als die der untergeordneten Objekte. Die zugehörige Funktion heißt parent_obj(...), da ein übergeordnetes Objekt in GEM als Parent-Objekt bezeichnet wird. Anders als in der Biologie hat jedoch ein GEM-Objekt nur ein Elter (oder wie heißt der Singular von Eltern?).

Selektion

Nach dieser aufreibenden Suche durch den Objektbaum kann man endlich den neuen Knopf selektieren: einfach das Status-Bit Nr. 5 löschen und das Objekt mit draw_sha~ dow_button(...) neu zeichnen. Beim Zeichnen berücksichtigt die Prozedur, daß das Objekt mit einem Rahmen umgeben ist. Dieser Rahmen und der Schatten des Objekts befinden sich aber außerhalb des Objekts, dessen Koordinaten mit OBJC OFFSET (...) und OB_W(...) und OB_H(...) ermittelt werden. Aus diesem Grund muß das umgebende Rechteck an jeder Kante um drei Pixel vergrößert werden (so weit setzt GEM den Rahmen vom Objekt ab). Andernfalls werden beim Neuzeichnen weder der Rahmen noch der Schatten mitgezeichnet - es hätte also beim Betätigen des Buttons keine sichtbare Veränderung stattgefunden.

Die eigentliche Verwaltung des neuen Button-Typs wäre damit erledigt. Ist der Dialog beendet, muß man natürlich abfragen, welcher Button selektiert wurde, um dann im weiteren Programmverlauf entsprechend verzweigen zu können. Die Routine obj_sel(...) macht genau das, wobei sie natürlich zwischen Shadow-Buttons und herkömmlichen Objekten zu unterscheiden vermag.

Experimentierfreudigen Programmierern seien einige Versuche mit unterschiedlichen Füllmustern in GEM-Buttons empfohlen, es lassen sich dann Beleuchtungseffekte simulieren, wie es bei Geräten mit in den Knöpfen eingebauten Kontroll-LEDs der Fall ist.

' ***********************************************
' * SHADOW_B.GFA - GEM-Buttons im neuen Outfit  *
' * Autor:   Andreas Hollmann                   *
' * Sprache: GFA-Basic                          *
' * (c) MAXON Computer GmbH 1991                *
' *********************************************** 
RESERVE 16000                    ! 16 kB sind genug
~RSRC_LOAD("a:\shadow_b.rsc")
~RSRC_GADDR(formular&,0,gp_formular%) 
rsrc_names                       ! Objekte werden getauft
~FN dialog(gp_formular%)         ! Dialog durchführen
' Abfrage, welcher Radiobutton gedrückt war:
IF FN obj_sel(gp_formular%,ukw&)
   PRINT "Wellenbereich UKW"
ELSE IF FN obj_sel(gp_formular%,mw&)
   PRINT "Wellenbereich MW"
ELSE IF FN obj_sel(gp_formular%,kw&)
   PRINT "Wellenbereich KW"
ELSE IF FN obj_sel(gp_formular%,lw&)
   PRINT "Wellenbereich LW"
ENDIF
~RSRC_FREE()
RESERVE
END
' ===================================================
FUNCTION dialog(p_tree%)
   ' kompletten Dialog durchführen und 
   ' Exit-Objekt zurückgeben.
   LOCAL x&,y&,w&,h&,obj&
   '
   ~FORM_CENTER(p_tree%,x&,y&,w&,h&) ! zentrieren
   ~FORM_DIAL(0,0,0,0,0,x&,y&,w&,h&) ! reservieren 
   ~OBJC_DRAW (p_tree% ,0,3,0,0,0,0) ! zeichnen
   DO
      obj&=FORM_DO(p_tree%,0)        ! Dialog
   LOOP UNTIL FN shadow_but(p_tree%,obj&)=FALSE 
   ~FORM_DIAL(3,0,0,0,0,x&,y&,w&,h&) ! freigeben 
   OB_STATE(p_tree%,obj&)=BCLR(OB_STATE(p_tree%,obj&),0)
   RETURN obj&                      ! Exit-Objekt zurückgeben
ENDFUNC
FUNCTION shadow_but(p_tree%, obj&)
   ' prüfen, ob das Exit-Objekt ein Shadow-Button 
   ' oder ein anderes Objekt war.
   LOCAL parentobj&,selobj&
   '
   IF FN check_obj(p_tree%,obj&)    !=Shadow-Button
      IF BTST(OB_FLAGS(p_tree%,obj&),4) !Radio-Flag 
         ' Elternobjekt bestimmen: 
         parentobj&=FN parent_obj(p_tree%,obj&)
         ' jetzt muss das z.Zt. selektierte d.h. das 
         ' Objekt OHNE Schatten gesucht werden:
         FOR selobj&=OB_HEAD(p_tree%,parentobj&) TO OB_TAIL(p_tree%,parentobj&)
            ' Kinder-Objekte abfragen 
            EXIT IF FN obj_sel(p_tree%,selobj&)
         NEXT selobj&
         IF selobj&<>obj&
            ' neuen Knopf selektieren und zeichnen: 
            OB_STATE(p_tree%,selobj&)=BCHG(OB_STATE(p_tree%,selobj&),5)
            draw_shadow_button(p_tree%,selobj&)
            ' alten Knopf deselektieren und zeichnen: 
            OB_STATE(p_tree%,obj&)=BCHG(OB_STATE(p_tree%,obj&),5)
            draw_shadow_button(p_tree%,obj&)
         ENDIF
      ELSE  ! kein Radio-, sondern normaler Button 
         ' Knopf (de)selektieren und zeichnen: 
         OB_STATE(p_tree%,obj&)=BCHG(OB_STATE(p_tree%,obj&),5)
         draw_shadow_button(p_tree%,obj&)
      ENDIF
      RETURN TRUE       ! = das war ein Shadow-Button
   ELSE
      RETURN FALSE      ! = das war kein Shadow-Button 
   ENDIF 
ENDFUNC
FUNCTION eheck_obj(p_tree%,obj&)
   ' Feststellen, ob es sich um ein 
   ' Shadow-Button handelt.
   IF OB_TYPE(p_tree%,obj&)=22            ! G_Boxtext
      IF BTST(OB_STATE(p_tree%,obj&),4)   ! Outlined
         IF BTST(OB_FLAGS(p_tree%,obj&),6)
            ' Touchexit
            IF BTST(OB_FLAGS(p_tree%,obj&),0)=FALSE 
               ' 'Selectable' ist nicht gesetzt,
               RETURN TRUE    ! Objekt = Shadow-Button 
            ENDIF 
         ENDIF 
      ENDIF 
   ENDIF
   RETURN FALSE ! Objekt kein Shadow-Button 
ENDFUNC
FUNCTION parent_obj(p_tree%,obj&)
   ' Elternobjekt suchen.
   LOCAL parentobj&
   '
   parentobj&=obj&
   DO                ! durch den Baum wühlen ...
      ' nächstes/übergeordnetes Objekt holen: 
      parentobj&=OB_NEXT(p_tree%,parentobj&)
      ' Nr.des Elternobjekts ist immer kleiner 
   LOOP UNTIL parentobj&<obj&
   RETURN parentobj& ! Elternobjekt zurückgeben 
ENDFUNC
PROCEDURE draw_shadow_button(p_tree%,obj&)
   ' Shadow-Button neu zeichnen.
   LOCAL x&,y&,w&,h&
   '
   ~OBJC_OFFSET(p_tree%,obj&,x&,y&)    ! x,y holen
   w&=OB_W(p_tree%,obj&)               ! w holen
   h&=OB_H(p_tree%,obj&)               ! h holen
   ' wegen 'Outlined' sind an jeder Seite 
   ' 3 Pixel zu addieren:
   SUB x&,3 
   SUB y&,3 
   ADD w&,6 
   ADD h&,6
   ~OBJC_DRAW(p_tree%,0,10,x&,y&,w&,h&)
RETURN
FUNCTION obj_sel(p_tree%,obj&)
   ' Prüfen, ob ein Objekt selektiert ist.
   ' Es muss natürlich zwischen normalem Objekt 
   ' und einem Shadow-Button unterschieden werden. 
   IF FN check_obj(p_tree%,obj&) ! Shadow-Button
      IF BTST(OB_STATE(p_tree%,obj&),5)
         ' Schatten ist eingeschaltet
         RETURN FALSE            ! = nicht selektiert
      ELSE
         ' Schatten ist ausgeschaltet 
         RETURN TRUE             ! = selektiert
      ENDIF
   ELSE                          ! beliebiges anderes Objekt
      ' Wert des 'selected'-Bits zurückgeben 
      RETURN BTST(OB_STATE(p_tree%,obj&),0)
   ENDIF
ENDFUNC
' -----------------
PROCEDURE rsrc_names
   ' Objektnummern Variablen zuordnen.
   ' (=konvertierte *.H-Datei vom RCS)
   ' * resource set indicies for SHADOW_B */
   LET formular&=0   !* form/dialog */
   LET ukw&=2        !* BOXTEXT in tree FORMULAR */
   LET mw&=3         !* BOXTEXT in tree FORMULAR */
   LET kw&=4         !* BOXTEXT in tree FORMULAR */
   LET lw&=5         !* BOXTEXT in tree FORMULAR */
   LET ein&=6        !* BOXTEXT in tree FORMULAR */
   LET ok&=7         !* BUTTON in tree FORMULAR */
RETURN

Andreas Hollmann
Aus: ST-Computer 03 / 1991, Seite 84

Links

Copyright-Bestimmungen: siehe Über diese Seite