Workshops / Hintergrundberichte / Buchvorstellungen
---------------------------------------------------
1. Einführung in die Reaction-Programmierung (von Martin R. Elsner)
Teil 3: Images und Gadgets
Diesmal habe ich eine Beschreibung der wichtigsten Fensterelemente
zusammengestellt. Informationen über Images und Gadgets findet man genauso wie
für die sonstigen Klassen in den Autodocs, zur Not auch noch in den
Include-Files im Verzeichnis includes:images bzw. includes:gadgets.
Trotzdem möchte ich hier kurz die einzelnen Objekte, ihren Sinn, ihre Vor- und
Nachteile und Besonderheiten beschreiben. Denn nur wenn man alle Möglichkeiten
kennt, die die BOOPSI-Objekte bieten, findet man die optimalen Bedienelemente
für die eigene Oberfläche.
Zur Erinnerung: Im Gegensatz zum Gadget-System von Intuition gibt es keine
vordefinierte Menge von Objekten - jeder kann sein eigenes Image oder Gadget
schreiben. Hier möchte ich auch nochmal Stephan Rupprecht danken, der mir für
mein ClassAction den Quelltext für ein Icon-Gadget zur Verfügung gestellt hat,
das auf einfache Art einen Button um die Darstellung eines Glowicons
erweitert. Ich werde mich hier an der Liste der Standardobjekte orientieren,
wie man sie in ReActor vorfindet.
Die Liste ist relativ lang und sollte nicht von oben bis unten durchgelesen
werden, sondern man sollte die Gadgets z.B. mit ReActor direkt ausprobieren,
um dabei einen Überblick über die interessanten Objekte und ihre Funktionsweise
zu erhalten.
Zunächst zu den Images. In ReActor findet man diese in einem separaten
Untermenü "Image". Ich habe schon darauf hingewiesen, dass Images mit
LAYOUT_AddImage statt LAYOUT_AddChild hinzugefügt werden müssen. Sie müssen
anders behandelt werden als Gadgets, auch wenn sie zum Teil ähnlich aussehen.
Das bedeutet, dass auch Funktionen wie SetGadgetAttrs() hier nicht angewendet
werden können bzw. müssen (SetAttrs() reicht in den meisten Fällen aus, zur Not
kann man auch mit RefreshGList() das Layout neu zeichnen).
BEVEL
heißt eigentlich nur "Kante" und ist auch nur für solche Zwecke zu gebrauchen,
also z.B. um eine Trennlinie zwischen zwei Objekte zu setzen oder eine
Überschrift über ein Objekt (Text dann mit BEVEL_Label setzen).
In vielen Fällen wird man statt Bevels Layouts mit den gleichen Eigenschaften
benutzen, um Objekte zu gruppieren.
BITMAP und
PENMAP
ermöglichen es, ein eigenes Bild anzuzeigen. Man kann sowohl ein Bild für den
ausgewählten wie für den nicht ausgewählten Zustand angeben. ReActor lädt die
Bilddaten aus der Datei und bindet sie direkt in die zugehörige *.o-Datei ein,
während bei der Angabe im Quelltext die Daten vom Objekt aus der Datei geladen
werden, sodass dann ein Verschieben oder Löschen der Datei zu Problemen führt.
Da die Objekte Datatypes unterstützen, kann alles eingebunden werden, was über
die Datatypes als Grafik erkannt wird.
Einer Penmap kann im Quelltext kein Dateiname übergeben werden, sondern nur
Bilddaten im "Chunky-Format". Schade eigentlich, denn durch die Möglichkeit,
Bilder auch zu skalieren, besitzt die Penmap eine Eigenschaft, die man bei
einer Bitmap nur mit BitMapScale() aus der graphics.library nachbilden kann.
Mit Hilfe von BITMAP_Masking bzw. PENMAP_Transparent und PENMAP_MaskBlit lassen
sich Bilder auch transparent darstellen.
GLYPH und
DRAWLIST
wird man selten allein verwenden. Es handelt sich um Standardsymbole wie Pfeile
und die bekannten "Datei öffnen"-Bilder. Vorteilhaft ist, dass die Bilder
skaliert werden und sich der Größe des Images anpassen. Wer Standardgrafiken
benötigt, kann direkt einen Button erzeugen und dort die Eigenschaft
"AutoButton" nutzen. Die Drawlist bietet im Gegensatz zum Glyph die Möglichkeit,
eigene Symbole zu erzeugen. Dazu muss ein Array von Zeichenbefehlen übergeben
werden, die dann ausgewertet werden und ein entsprechend skaliertes Bild
ergeben. Für den Programmierer ist meist das Zeichen einer Grafik und das
Einbinden mittels Bitmap oder Penmap einfacher.
LABEL
ist eines der am häufigsten benötigten Elemente. Viele Gadgets bieten zwar Platz
zur Eingabe von Text oder zur Darstellung von Informationen, sie benötigen aber
einen Titel bzw. eine kurze Beschreibung. Ein Label eignet sich aber nicht nur
für die Anzeige eines Wortes vor einem Gadget (mit LABEL_Text angeben, nicht mit
GA_Text!), sondern kann auch mehrere Texte (inklusive Zeilenumbruch!)
und sogar Grafiken enthalten. Wenn allerdings auch Unterstriche im Text
dargestellt werden sollen, sollte LABEL_Underscore z.B. auf 0 oder auf ein
sonst unbenutztes Zeichen gesetzt werden. Eine Ausrichtung der Labels in einem
Layout kann übrigens mit LAYOUT_LabelColumn, LAYOUT_LabelWidth und
LAYOUT_AlignLabels realisiert werden.
Eine Alternative zur einfachen Textdarstellung ist die Erzeugung eines Buttons
(siehe dort).
So, nun zu den Gadgets. Die BOOPSI-Gadgets bieten eigentlich alles, was man als
Programmierer benötigt, auch wenn die Funktionalität manchmal nicht optimal ist.
Als Delphi-Programmierer vermisse ich vor allem die Möglichkeit, in einfacher
Weise auf verschiedene Ereignisse zu reagieren, z.B. Mausklick mit rechter
Maustaste über einem Objekt. Dass dies nicht implementiert ist, hängt allerdings
mit der Amiga-Architektur zusammen und kann den Gadgetentwicklern nicht
angelastet werden.
Möchte man über das Anklicken eines Gadgets informiert werden, muss GA_ID und
GA_RelVerify gesetzt werden (im Quelltext muss dafür gesorgt werden, dass GA_ID
einen im Fenster bzw. in der Page eindeutigen Wert erhält). Von den
Standard-Gadgetattributen (GA_...) sind vor allem GA_ReadOnly und GA_Disabled
von Interesse, weil damit die Bearbeitung verhindert (z.B. bei String-Gadgets)
bzw. das Gadget ausgeschaltet werden kann (z.B. bei Buttons).
BUTTON (NUMBER,TEXTBUTTON,TEXTLINE)
ist einfach ein Knopf, also im einfachsten Fall ein "Ok" oder "Cancel". Statt
einem Text kann ein Button auch einen Integer-Wert (was in Reactor als Number
bezeichnet wird), ein Image oder ein Glyph ("AutoButton") erhalten. Durch
BUTTON_PushButton kann der Button - ähnlich der CheckBox - zwischen ausgewählt
und unausgewählt umgeschaltet werden.
Übrigens kann man einen Button auch nur zum Anzeigen von Text benutzen - mit den
Eigenschaften GA_ReadOnly=TRUE, BUTTON_BevelStyle=BVS_NONE und
BUTTON_Transparent=TRUE (was in ReActor als Textline bezeichnet wird)
ist das Aussehen kaum anders als bei einem Label, mit einem wichtigen
Unterschied: sollte der Text während der Laufzeit geändert werden, ohne dass das
Layout neu berechnet wird oder werden soll, so wird ein Button den Text an der
richtigen Stelle abschneiden, während ein Label in jedem Fall den vollen Text
ausgibt und gegebenenfalls andere Objekte überschreibt.
CHECKBOX
ist ein an/aus- oder ja/nein-Schalter, wie man ihn häufig aus den Prefs-
Programmen kennt. Außer GA_Text kann man hier die Ausrichtung des Textes und die
Farben angeben. GA_Selected gibt dann den aktuellen Status an.
ACHTUNG: Eine Checkbox wird (jedenfalls in der mir vorliegenden Version) durch
SetGadgetAttrs() nicht aktualisiert!! Nach einer Änderung ist der Aufruf von
RefreshGList() nötig!
CHOOSER
ist die einfachste Möglichkeit, eine Liste von Auswahlmöglichkeiten anzubieten.
Es gibt zwei Arten von Choosern: möchte man einen Zustand darstellen, wie es
z.B. der aktuelle Zeichensatz in einem Textverarbeitungsprogramm ist, so erzeugt
man einen PopUp-Chooser (CHOOSER_PopUp=TRUE). Hier enthält das Gadget immer den
aktuell ausgewählten Wert. Möchte man dagegen dem Benutzer eine Liste von
Befehlen an die Hand geben, z.B. als Ersatz für mehrere Buttons oder ein Menü,
so setzt man CHOOSER_DropDown=TRUE und erhält ein Gadget, in dem immer nur der
CHOOSER_Titel angezeigt wird. Die Liste der Einträge wird mit CHOOSER_Labels
übergeben; für die Kontrolle dieser Liste stehen AllocChooserNode(),
FreeChooserNode(), GetChooserNodeAttrs() und SetChooserNodeAttrs() zur
Verfügung. Wie bei allen Listen sollte die Liste bei Änderungen erst mit
SetGadgetAttrs(gadget,intuiwin,NULL,CHOOSER_Labels,NULL,TAG_END)
abgehängt und danach mit
SetGadgetAttrs(gadget,intuiwin,NULL,CHOOSER_Labels,list,TAG_END)
wieder eingetragen werden (wer Probleme mit der Listenerzeugung hat, wird im
nächsten Teil mehr darüber erfahren).
Man sollte nicht vergessen, CHOOSER_MaxLabels auf einen Wert zu
setzen, der groß genug ist, und CHOOSER_AutoFit=TRUE zu setzen, damit der
Chooser breit genug wird.
CLICKTAB
ist eine gute Möglichkeit, viele Informationen in einem Fenster unterzubringen,
bei denen eine gleichzeitige Anzeige unmöglich ist oder eine Gruppierung
sinnvoll ist; bestes Beispiel ist der ClickTab im Drucker-Voreinsteller, mit dem
man zwischen Druckertyp, Seitengröße und Einstellungen hin- und herschalten
kann. Ich werde im nächsten Teil gesondert auf den ClickTab eingehen.
COLORWHEEL
ist eine schöne Möglichkeit zur Festlegung einer Farbe. Ein Farbrad wird
angezeigt, in dem ein Punkt die aktuelle Farbe kennzeichnet. Außer den
Eigenschaften zur Ermittlung der ausgewählten Farbwerte im HSB- oder RGB-Modell
und zur Darstellung des Rades ist es auch möglich, einen GradientSlider
anzugeben, der die Helligkeit der Farbe kontrolliert und direkt mit dem
ColorWheel zusammenarbeitet, ohne Mehraufwand für den Programmierer.
Bei der Verwendung sollte man - z.B. durch den Gradientslider - dafür sorgen,
dass die aktuelle Farbe irgendwo angezeigt wird, da das ColorWheel selbst nur
die Position im Rad anzeigt.
DATEBROWSER
ist ein einfacher Kalender; er zeigt die Tage eines Monats an, den man mit den
Eigenschaften DATEBROWSER_Year und DATEBROWSER_Month angeben kann. Leider werden
diese Werte (Jahr und Monat) nicht im Gadget angezeigt, können aber z.B. durch
ein zusätzliches Label oder einen Chooser per Hand gesetzt werden.
Bei Mehrfachselektion bekommt man in DATEBROWSER_SelectedDays ein Langwort, in
dem jedes Bit einem Tag des Monats entspricht (bis auf das 32. Bit ;)
und gesetzt ist, wenn der Tag ausgewählt ist - gut, dass es keinen Monat mit
33 Tagen gibt ...
Weiter bietet das Gadget die Möglichkeit, den Wochentag des ausgewählten Datums
und den ersten Wochentag des Monats zu ermitteln. Man sollte daran denken, dass
Wochen nach englischer Sitte mit dem Sonntag beginnen und daher 0 für Sonntag,
..., 6 für Samstag steht.
FUELGAUGE
ist eine schöne Verlaufsanzeige für alle Aktionen, die so lange dauern, dass
der Benutzer ungeduldig wird ;)
Mit FUELGAUGE_Min, FUELGAUGE_Max und FUELGAUGE_Level kann man den
Fortschrittsverlauf an die eigenen Bedürfnisse anpassen, man kann eine
automatische Prozentanzeige aktivieren und die Anzahl der Unterteilungsstriche
angeben.
GETFILE,
GETFONT und
GETSCREENNMODE
kann man in fast jedem Programm gebrauchen, sie erleichtern die Auswahl von
Dateien, Zeichensätzen oder Bildschirmmodi ungemein. Im Fenster selbst erscheint
dann nur noch ein Stringgadget mit dem aktuellen Text und ein Button mit einem
passenden Glyph. Die Gadgets haben ein paar Besonderheiten vorzuweisen: das
Öffnen des Requesters geschieht nicht automatisch, sondern muss nach dem
Erhalten eines GadgetUp-Events mit DoMethod(gadget,GFILE_REQUEST) (bzw.
GFONT_REQUEST oder GSM_REQUEST) selbst vorgenommen werden - dies kann man
dadurch aber auch an ganz anderen Stellen, ohne dass jemand auf den Button
geklickt hat. GetFile bietet auch die Möglichkeit, gleich mehrere Dateien
auszuwählen, die dann als Liste unter GETFILE_Filelist erhältlich ist, wobei man
das abschließende DoMethod(gadget,GFILE_FREELIST) zum Freigeben der Liste nicht
vergessen sollte.
GetFile ist auch das einzige der drei Gadgets, das eine direkte Eingabe ins
Stringgadget erlaubt - wovon ich aber abrate (also GETFILE_ReadOnly auf TRUE
setzen), da man in diesem Fall kein GadgetUp-Ereignis erhält, sondern
umständlich das Event umleiten muss (siehe dazu die Autodocs). Übrigens bekommt
man mit der sehr sinnvollen Eigenschaft GETFILE_FullFileExpand im Gegensatz zu
GETFILE_FullFile nicht nur den vollen Dateinamen, sondern auch den entsprechend
aufgelösten Namen (er enthält keine keine relativen Pfade mehr) - der dann für
die Datei eindeutig ist. Somit erkennt man direkt, dass sys:c/copy und c:copy
für die selbe Datei stehen!
GRADIENTSLIDER
hört sich an wie Slider, ist aber etwas anders aufgebaut: Man muss bei der
Erzeugung ein Array von Pens (also Farbindizes bezogen auf die aktuelle Palette,
siehe ObtainBestPen() in der graphics.library) angeben, das mit 0 abgeschlossen
ist; diese Farben bilden dann von links nach rechts oder von oben nach unten
einen Farbverlauf, vom System noch durch "Dithering" geglättet. Z.B. könnte man
grün, gelb und rot angeben, um einen Verlauf von grün nach rot darzustellen. In
GRAD_CurValue findet man dann den vom User eingestellten Farbwert.
IC und
MODEL
sind mal was ganz anderes, nämlich keine Oberflächenelemente, sondern
"Schaltzentralen"; IC bedeutet InterConnection, es handelt sich um eine
Verbindung zwischen BOOPSI-Objekten. Hier nur eine kurze Einführung:
Zugrunde liegt die Idee, dass manche Objekte zusammenhängende Funktionalitäten
besitzen, im einfachsten Fall soll z.B. ein Gadget immer dann Disabled sein,
wenn auch ein anderes Disabled ist. Allgemein sollen Eigenschaften, die in einem
Gadget geändert werden, direkt an andere übertragen werden.
Eine einfache Möglichkeit ist die Angabe von ICA_Target und ICA_Map für das
erste Gadget, wobei ICA_Target auf das Ziel, also das zweite Gadget gesetzt
wird, und ICA_Map bekommt ein TagItemArray mit den Eigenschaften beider
Gadgets, falls diese verschieden sind.
Eine zweite Möglichkeit ist die Angabe eines IC oder Models als ICA_Target.
Diese können zusätzlich ein IDCMPUPDATE-Ereignis erzeugen, das die Anwendung
dann auswerten kann, und ein Model kann sogar mehrere Gadgets aktualisieren,
die dann mit der Methode OM_ADDMEMBER hinzugefügt werden müssen.
Wer das nicht verstanden hat, kann es getrost wieder vergessen, denn den
gleichen Effekt kann man meist leichter in einer eigenen Ereignisbehandlung
erreichen.
LAYOUT und
PAGE
ist die schon beschriebene Gruppeneinteilung der Fensterobjekte. Hier gibt es
relativ viele Eigenschaften, mit denen man die Darstellung ändern kann, z.B.
die Abstände und Größenverteilungen oder die Ausrichtung der Labels.
Die in ReActor angegebenen Untertypen sind bis auf die Page (die aber auch in
layout.gadget enthalten ist) nur Layouts mit verschieden vordefinierten
Eigenschaften; nach dem Speichern und Laden werden sie auch als Layout
angezeigt.
Die Page wird nur für Clicktabs benötigt; jede Seite bzw. Karteikarte entspricht
dabei einem Layout der Page, die ansonsten wie ein normales Layout funktioniert.
LISTBROWSER
ist eines der komplexesten BOOPSI-Objekte. Im Grunde handelt es sich um die
Anzeige einer Liste irgendwelcher Objekte. Dabei kann es sich, wie bei einem
Filerequester, einfach um eine Menge Strings handeln, es kann sich aber auch
- wie in ClassAction - um eine zweimensionale Anzeige handeln, bei der jede
Zeile noch einmal verschiedene Grafiken oder Texte in der Form mehrerer
Spalten enthält. Da die Programmierung auch entsprechend komplex ist, werde
ich im nächsten Teil gesondert auf den Listbrowser eingehen.
PALETTE
ist die einfachste Art, eine Farbauswahl zu implementieren. Man kann die Anzahl
der darzustellenden Farben angeben und erhält eine Menge von Buttons in den
Farben des aktuellen Screens. In den Autodocs wird direkt darauf hingewiesen,
dass eine solche Auswahl bei mehr als 16 Farben nicht mehr optimal ist. Ich
rate in solchen Fällen ganz von einem solchen Gadget ab und schlage die direkte
Festlegung der Farbwerte vor, was bei den heute üblichen 16- oder 24-Bit-Modi
der sinnvollere Weg ist; falls man trotzdem ein Palette-Objekt benutzen möchte
oder muss, gibt es dort auch Möglichkeiten, die angezeigten Farben mit
PALETTE_ColourOffset und PALETTE_ColourTable einzuschränken, sodass man z.B. mit
ein paar Pfeilbuttons alle Farben anschauen und auswählen kann.
RADIOBUTTON
ist eigentlich eine Gruppe von CheckBoxes - mit der Einschränkung, dass immer
nur eine der Optionen ausgewählt sein darf. Ich habe noch nie Radiobuttons
benutzt, da man den gleichen Effekt auch mit einem Chooser realisieren kann,
der weniger Platz benötigt. Der einzige Vorteil des Radiobuttons ist, dass
man sämtliche Optionen auf einen Blick erkennt. Auch in der Programmierung ist
der Radiobutton dem Chooser sehr ähnlich: mit RADIOBUTTON_Labels übergibt man
wieder eine Liste der einzelnen Optionen, die mit den Funktionen
AllocRadioButtonNode(), FreeRadioButtonNode(), GetRadioButtonNodeAttrs() und
SetRadioButtonNodeAttrs() bearbeitet werden kann, RADIOBUTTON_Selected liefert
die Nummer des ausgewählten Eintrags.
SLIDER
ist eine Schieber, mit dem man z.B. Intensität, Lautstärke, Größe etc., in einem
Bereich SLIDER_Min bis SLIDER_Max einstellen kann. Außer der Ausrichtung und der
Skalierung des Bereichs können sogar eigene Grafiken angegeben werden.
SPACE
ist eigentlich nichts, aber ein sehr praktisches Nichts: Layouts haben die
Eigenschaft, den verfügbaren Raum zwischen den Gadgets gleichmäßig zu verteilen
(worauf man auch mit LAYOUT_ShrinkWrap und LAYOUT_EvenSize Einfluss nehmen
kann). In vielen Fällen sieht dies allerdings nicht besonders schön aus,
besonders wenn das Fenster vom Benutzer sehr stark vergößert wird. Dann sollte
man einfach ein Space in das Layout setzen und gegebenenfalls die
Größenverteilung mit CHILD_WeightedWidth/Height umstellen. Man kann ein
Space auch zur Gruppierung von Objekten nutzen, indem zwischen den Gruppen
jeweils ein Exemplar eingefügt wird, das dann sinnvoller Weise einen positiven
Wert in SPACE_MinWidth/Height erhält.
SPEEDBAR
ist eine schöne Möglichkeit, dem Benutzer ein grafikbasiertes Menü anzubieten.
Dabei handelt es sich um eine horizontale oder vertikale Anordnung von Images,
die als Liste organisiert ist. Dazu aber nächstes Mal mehr.
STRING und
INTEGER
sind die einfachste Möglichkeit, Buchstaben oder Zahlen durch den Benutzer
eingeben zu lassen. Beide lassen die Angabe einer mindestens sichtbaren und
einer höchstens zulässigen Länge zu, bei Integer kann auch eine untere und
obere Schranke angegeben werden. Das Integer-Objekt bietet optional zwei
nützliche Pfeile, mit denen die Zahl verändert werden kann. Ein String-Objekt
kann durch STRINGA_HookType dazu veranlasst werden, nur bestimmte Zeichen
zuzulassen oder sie anders darzustellen, z.B. als Passwort oder in
Großbuchstaben. Mit STRINGA_EditHook kann man sogar eine eigene Funktion
angeben, die dann statt der eingebauten Hooks die Eingabe überwacht. Außerdem
gibt es zwei Möglichkeiten, keine Änderung am Inhalt zuzulassen: mit
GA_ReadOnly, das auch bei anderen Gadgets funktioniert, kann das Gadget gar
nicht aktiviert werden, während mit STRINGA_FixFieldMode nur die direkte
Änderung abgelehnt wird, so dass man den Text noch kopieren kann.
TAPEDECK
ist eine gute Möglichkeit, einfach und schnell die nötigen Kontrollbuttons für
die Wiedergabe von Musik, Animationen oder ähnlichen Daten zu erzeugen.
Setzt man TDECK_Tape auf TRUE, so erhält man Zurückspulen, Wiedergabe,
Vorspulen, Stoppen und Pause, bei TDECK_Tape=FALSE entfallen Stop und Pause,
dafür ist ein Verlaufsbalken zu sehen, der mit TDECK_Frames und
TDECK_CurrentFrame gesteuert werden kann.
TDECK_Mode gibt den gerade ausgewählten Modus an, also BUT_REWIND, BUT_PLAY etc.
TEXTEDITOR
ist eines der nützlichsten Gadgets - man erhält mit wenigen Quelltext-Zeilen
einen kompletten Texteditor, der sogar Copy&Paste-Möglichkeiten bietet.
Außer den selbsterklärenden GA_TEXTEDITOR_Contents und GA_TEXTEDITOR_ReadOnly
bietet es keine zusätzlichen Eigenschaften.
Noch ein Hinweis zum Erzeugen der Objekte im Quelltext: u.a. für ColorWheels
existiert kein COLORWHEEL_GetClass(), wie man es erwarten könnte; in diesem Fall
öffnet man die Gadget-Library wie gewohnt mit OpenLibrary(), gibt aber beim
Erzeugen den Namen der Klasse an:
NewObject( NULL,"colorwheel.gadget",...,TAG_END )
So, das soll's mal wieder gewesen sein, nächstes Mal werde ich noch genauer auf
Clicktab, Speedbar und Listbrowser eingehen, wobei die Listenverwaltung nicht zu
kurz kommen soll. Außerdem werde ich das Geheimnis der schon mehrmals erwähnten
Hooks lüften!
Ansonsten wünsche ich viel Spaß beim Ausprobieren der vielen Objekte,
euer Martin R. Elsner <email@martin-elsner.de>
|