<< Inhalt >> Workshops Inhalt

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 noch mal 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 Gadget-Entwicklern 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 relativen Pfade mehr) - der dann für die Datei eindeutig ist. Somit erkennt man direkt, dass sys:c/copy und c:copy für dieselbe 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 zweidimensionale 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 vergröß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>

<< >>