Navigation
« 

Anonymous




Register
Login
« 
« 

Amiga Future

« 

Community

« 

Knowledge

« 

Last Magazine

The Amiga Future 167 was released on the March 5th.

The Amiga Future 167 was released on the March 5th.
The Amiga Future 167 was released on the March 5th.

The Amiga Future 167 was released on the March 5th.
More informations

« 

Service

« 

Search




Advanced search

Unanswered topics
Active topics
« 

Social Media

Twitter Amigafuture Facebook Amigafuture RSS-Feed [german] Amigafuture RSS-Feed [english] Instagram YouTube Patreon WhatsApp
« 

Advertisement

Amazon

Patreon

« 

Partnerlinks

Reaction-Programmierung Teil 1-3

Description: Amiga Aktuell

Categories: [DE] Workshops

Link to this article: Select all

[url=https://amigafuture.de/app.php/kb/viewarticle?a=1660&sid=20f00a8a6fa70906ebd9c4df9eece1f3]Artikeldatenbank - Reaction-Programmierung Teil 1-3[/url]

Einführung in die Reaction-Programmierung - T. 1 (von Martin R. Elsner)
Der Einstieg

Vor 10 Jahren war es für einen Programmierer schon eine besondere Leistung, wenn er ein Workbench-Programm mit vielen Buttons, Texteingabefeldern, Menüs usw. entwickelt hatte - jedes Element in seinem Fenster war Handarbeit und benötigte einige Zeit, bis es an der richtigen Stelle war und die richtige Größe hatte. Wenn dann so etwas wie eine Liste mit Scroll- Möglichkeit gefragt war, musste erst mal einiges an Vorarbeit geleistet werden. Die große Überraschung kam dann aber, wenn jemand auf die Idee kam, die Zeichensätze der Workbench zu ändern, andere Auflösungen oder Farben zu benutzen: da sah das mühsam zusammengestellte Programm eher wie ein schlechter Scherz aus, was man heute noch mit einigen alten Schätzchen aus dem Aminet ausprobieren kann.

Eine Alternative kam mit MUI, einem Programm, das standardisierte Objekte mit sich brachte, die dann von jedem Programm benutzt werden konnten. Leider war es nicht Bestandteil des OS und musste dazugekauft werden, und es scheint überdies nicht mehr weiterentwickelt zu werden.

In diese Lücke trat dann spätestens mit OS3.5 Reaction - wobei ich mit diesem Wort hier auch alles meine, was man als BOOPSI bezeichnet - "Basic Object Oriented Programming System for Intuition". Genau wie bei MUI handelt es sich also im Grunde um eine Menge von Oberflächenobjekten, die einmal zur Verfügung gestellt werden und dann in allen Programmen auftauchen können. Reaction bietet dann den Kitt, der alles zusammenhält und auch globale Einstellungen für alle Objekte (Hintergrund, Zeichensätze usw.) ermöglicht.

Wer also heute ein Programm für den Amiga (ab OS3.5) entwickeln will, kommt kaum an Reaction vorbei; deswegen will ich hier versuchen, die Reaction- Programmierung zu erläutern und Beispiele für eigene Programme zu geben. Wer mich kennt, wird wohl auch ClassAction und damit ein gutes Beispiel für Reaction kennen. Ich werde öfter mal an diesem Programm die Entwicklung einer eigenen Oberfläche erklären und auf gute und schlechte Erfahrungen hinweisen. Dabei halte ich mich größtenteils an die Programmiersprache C (persönlich programmiere ich nur C++), die Programmierung weicht in anderen Sprachen aber kaum ab. Grundkenntnisse in einer Programmiersprache setze ich allerdings voraus. Für die Programmierung benötigen wir in jedem Fall die entsprechenden Bibliotheksdateien, für C sind dies also sämtliche Header-Files (z.B. classes/window.h, clib/window_protos.h). In diesen Dateien sind die benötigten Konstanten, Typen und Funktionsprototypen enthalten.

Die größten Probleme bei der Programmierung am Amiga ist einerseits das Fehlen einer grafischen Entwicklungsumgebung wie z.B. Delphi, andererseits das Fehlen von Büchern zur aktuellen Programmierung. Deswegen kann ich hier auch keine Bücher empfehlen, sondern nur ein paar elektronische Wege zum Glück: Die einfachste Möglichkeit, an Informationen heranzukommen, liegt auf der Developer CD V2.1. Auf dieser CD befinden sich zum einen alle AutoDocs und Header-Dateien der Bibliotheksfunktionen, die wir benötigen, außerdem noch einige gute allgemeine Artikel, leider in Englisch (z.B. in den Ordnern Reference/Amiga_Mail...). Zum zweiten findet man dort den Storm-C(++)-Compiler in der (bzgl. der kommerziellen Nutzung) eingeschränkten Version 3, den ich sehr empfehlen kann. Das für unser Thema fast wichtigste ist aber ein kleines Programm im Storm-Verzeichnis mit dem Titel ReActor. Dabei handelt es sich um ein Tool, mit dem man sich ohne große Mühe eine Oberfläche zusammenbauen und direkt ansehen kann. Es erzeugt beim Speichern alle benötigten C-Dateien sowie eine Katalogdatei, mit der das Programm lokalisiert werden kann. Natürlich kommt man auch ohne dieses Programm aus, aber gerade zum Ausprobieren und für kleinere Programme ist es wunderbar. Ich werde im Folgenden sowohl die direkte Programmierung als auch die Bedienung von ReActor beschreiben.

Weitere Informationen gibt es im Aminet. Dort findet man auch neue BOOPSI- Klassen und Hinweise, wie man eigene Klassen programmiert. Den Anfänger werden diese Beispiele jedoch meist mehr verwirren als aufklären. Wenn es um konkrete Probleme geht, gibt es auch eine Mailingliste (Amiga C) unter http://yahoogroups.com, in die man sich eintragen und Fragen stellen kann. Leider hat mein AWeb die Anmeldung nicht hinbekommen, sodass ich hier nicht mehr darüber sagen kann. Es gibt aber auch viele Programmierer, die gerne weiterhelfen; ich beantworte gerne Fragen zur Reaction-Programmierung (obwohl diese Einführung eigentlich alle Probleme beseitigen sollte).

So, jetzt wird's Zeit mit Reaction anzufangen. Wie man vielleicht schon ahnt, hat BOOPSI sehr viel mit objektorientierter Programmierung (OOP) zu tun. Deswegen muss man jetzt nicht C++ benutzen, aber man sollte sich mit den Grundzügen der OOP vertraut machen:

Grob gesagt heißt objektorientierte Programmierung, dass die Programme nur noch aus einzelnen Objekten bestehen, die Eigenschaften und Methoden besitzen. Für BOOPSI heißt das z.B., dass es ein Objekt "Fenster" gibt, das z.B. die Eigenschaft "Titel" besitzt, und die Methoden "Öffnen" und "Schließen". Objekte gehören immer zu einer bestimmten Klasse, hier ist dies die "Fensterklasse". Da die Klasse schon programmiert ist und als Datei vorliegt (im Verzeichnis sys:classes), brauchen wir als Programmierer nur noch ein Objekt (oder auch "Instanz") anzulegen. Klassen können auch hierarchisch aufgebaut werden, z.B. gibt es die Klasse "Buttons", darunter - sozusagen als Unterklasse - die Klasse "Radiobutton", die Eigenschaften und Methoden von der übergeordneten Klasse übernimmt und zusätzliche zur Verfügung stellt (Gadgetklassen sind im Verzeichnis sys:classes/gadgets untergebracht).

Eins vorweg: Man sollte jetzt nicht direkt seinen Quelltexteditor starten und wild draufloshacken; so eine Reaction-Oberfläche sollte erst mal sorgfältig geplant werden. Wir werden uns im nächsten Teil mit Fenstern und dem grundsätzlichen Aufbau beschäftigen, jetzt schauen wir uns erst mal an, was in unserem Programm nötig ist, damit wir die OOP nutzen können.

Zunächst benötigt man wie gewohnt bestimmte Bibliotheken, die mit OpenLibrary und CloseLibrary geöffnet und geschlossen werden. Außer den Standardbibliotheken, die man fast immer benötigt (Intuition,Dos,...), kommen nun die Klassen hinzu, die man nutzen möchte. Benötigt man z.B. ein Fenster mit nur einem Button, so sieht das in C so aus:

struct Library
*IntuitionBase,*DosBase,*WindowBase,*LayoutBase,*ButtonBase;
...
IntuitionBase = OpenLibrary( "intuition.library", 39 );
DosBase = OpenLibrary( "dos.library", 39 );
WindowBase = OpenLibrary( "window.class", 44 );
LayoutBase = OpenLibrary( "gadgets/layout.gadget", 44 );
ButtonBase = OpenLibrary( "gadgets/button.gadget", 44 );

Die Klassen werden also genauso wie Bibliotheken geöffnet, wobei OpenLibrary auch in sys:classes sucht, Unterverzeichnisse müssen aber angegeben werden. Die Nummer der benötigten Version ist hier nicht wichtig, es sollte aber in jedem Fall sichergestellt sein, dass alle Bibliotheken geöffnet wurden:

if( (IntuitionBase == NULL) || (WindowBase == NULL) || (LayoutBase ==
NULL)) || (ButtonBase == NULL) ){
CloseLibrary( ButtonBase );
CloseLibrary( WindowBase );
CloseLibrary( LayoutBase );
CloseLibrary( DosBase );
CloseLibrary( IntuitionBase );
return; /* am besten noch Fehlermeldung und raus aus dem Programm */
}

Und am Ende müssen die Bibliotheken genauso freigegeben werden. Wer Reactor benutzt, muss auch noch die resource.library öffnen. Übrigens ist LayoutBase kein Fehler, wir brauchen das Layoutgadget für jedes Fenster.

Haben wir alles geöffnet, kann's losgehen. Statt mit den BOOPSI-Objekten selbst arbeiten wir im Grunde immer mit Zeigern. D.h. unsere Objekte sind 4-Byte-Variablen, ich arbeite immer mit dem Typ Object*, was im Grunde ein Zeiger auf ein Langwort ist. Da die BOOPSI-Objekte ja nicht sprachabhängig sind, muss es eine Möglichkeit geben, Objekte anzulegen, Eigenschaften zu setzen und zu lesen sowie Methoden auszuführen. Dazu gibt es die Funktionen

Objekt = NewObject( Klasse,KlassenName,Eigenschaft1,Wert1,
Eigenschaft2,Wert2,...,TAG_END )

NewObject legt ein neues Objekt an. Die Klasse wird entweder durch einen Zeiger auf die Klasse oder durch den Namen der Klasse angegeben. Die meisten Klassen besitzen eine eigene Bibliotheksfunktion, die den Zeiger liefert. Zusätzlich zum Erzeugen kann man auch direkt Eigenschaften festlegen. Dies kann entweder als Liste geschehen, wie oben angegeben, oder mit der Funktion

Objekt = NewObjectA( Klasse,KlassenName,TagList );

wobei TagList der Zeiger auf eine Struktur ist, die die Eigenschaften und Werte hintereinander als Langworte (4 Byte) enthält und wieder durch das TAG_END (als Eigenschaft) abzuschließen ist (diese Struktur heißt TagItem, die Definition befindet sich in utilities/tagitem.h).

Dieser alternative Aufruf existiert auch für die anderen angegebenen Funktionen mit sogenannten Tags.

Natürlich kann das Ganze auch schiefgehen - z.B. wenn man mal den Klassennamen falsch geschrieben hat. Also: immer den Rückgabewert auf 0 prüfen! Außerdem sollte man immer achtgeben, dass man nicht falsche Eigenschaften setzt, also z.B. für einen Button eine Eigenschaft des Stringgadgets setzt - dieser Fehler kann nicht vom Compiler erkannt werden, sorgt aber mindestens für seltsame Effekt, wenn nicht sogar zum Absturz.

Ergebnis = SetAttrs( Objekt,Eigenschaft1,Wert1,...,TAG_END )

SetAttrs setzt die angegebenen Eigenschaften des Objekts auf die angegebenen Werte. Bei unseren Gadgets muss meist auch das Fenster informiert werden, wozu man dann die Funktion

Ergebnis = SetGadgetAttrs( Gadget,Fenster,Requester,Eigenschaft1,
Wert1,...,TAG_END )

benutzen sollte. "Fenster" ist ein Zeiger auf das Fenster, in dem sich das Gadget befindet, "Requester" ein Zeiger auf den Requester, falls das Gadget sich in einem solchen befindet.

Die Eigenschaften und zulässigen Werte sind in den AutoDocs aufgeführt. Wir werden uns die wichtigsten noch anschauen.

Ergebnis = GetAttr( Eigenschaft,Objekt,Pufferadresse )

GetAttr speichert den Wert der Eigenschaft des Objekts im Puffer (der unbedingt 4 Bytes groß sein muss, auch wenn nur eine Boolesche Variable zurückgegeben wird!). Ist das Ergebnis 0, so besitzt das Objekt diese Eigenschaft gar nicht. Wer möchte, kann auch die reaction.library öffnen und die Funktion GetAttrs benutzen, die auch mehrere Eigenschaften auf einmal auslesen kann.

Ergebnis = DoMethod( Objekt,Methode,.. )

DoMethod führt die angegebene Methode des angegebenen Objekts aus und übernimmt methodenspezifische Parameter. Auch die Bedeutung des Rückgabewerts hängt von der Methode ab. Wir werden diese Funktion hauptsächlich für Fenster benutzen, ansonsten benötigen wir keine Methoden.

Es gibt zwar in der intuition.library noch DoGadgetMethod, aber diese Funktion wird nur äußerst selten nötig sein.

Übrigens gibt es statt eigener Methoden oft Bibliotheksfunktionen, die mit den Objekten arbeiten, z.B. "HideListBrowserNodeChildren" für Listbrowser- Objekte.

DisposeObject( Objekt )

Goldene Regel für Programmierer: was du ausleihst, musst du auch wieder zurückgeben! Sobald also das Objekt nicht mehr benötigt wird, sollte es freigegeben werden. Aber Vorsicht: erstens sollte man sicher sein, dass es auch angelegt wurde, zweitens muss man nicht alles freigeben. Manche Objekte "gehören" anderen Objekten. Werden dann diese Besitzer freigegeben, so auch die untergeordneten Objekte. Für uns heißt das z.B., dass unsere Gadgets zum Fenster gehören und nicht eigens gelöscht werden müssen. Man sollte sich in jedem Fall angewöhnen, Zeiger nach der Freigabe auf NULL (bzw. NIL) zu setzen, da die meisten Routinen mit einem NULL-Zeiger, aber nicht mit einem Zeiger auf ein schon freigegebenes Objekt umgehen können.

Bis auf DoMethod (aus der amiga.lib) befinden sich übrigens alle Funktionen in der intuition.library.

Das sieht zwar noch sehr theoretisch aus, aber wir schauen es uns mal im Beispiel an.

/* Prototypen einbinden : */
#include
#include
#include
#include
#include
#include
#include

/* Typen und Konstanten einbinden : */
#include
#include
#include
#include

/* Die Dateien können natürlich bei anderen Compilern anders */
/* heißen und an anderen Stellen stehen. Zur Not muss man die */
/* Verzeichnisse nach den benötigten Konstanten etc. durchsuchen.*/

struct Library *IntuitionBase,*DosBase,*WindowBase,*LayoutBase,*ButtonBase;

int main(){

Object *window,*layout,*button;

/* wie gesagt, man kann auch einen anderen Typ benutzen, */
/* z.B. ULONG window oder APTR window, es müssen nur 4 Byte sein */

struct Window *intuiwin; /* das brauchen wir noch */

/* So, den nächsten Teil kennen wir schon: */

IntuitionBase = OpenLibrary( "intuition.library", 39 );
DosBase = OpenLibrary( "dos.library", 39 );
WindowBase = OpenLibrary( "window.class", 44 );
LayoutBase = OpenLibrary( "gadgets/layout.gadget", 44 );
ButtonBase = OpenLibrary( "gadgets/button.gadget", 44 );
if( (!IntuitionBase) || (!WindowBase) || (!LayoutBase) || (!ButtonBase) ){
CloseLibrary( ButtonBase );
CloseLibrary( WindowBase );
CloseLibrary( LayoutBase );
CloseLibrary( DosBase );
CloseLibrary( IntuitionBase );
return( 20 );
}

/* Jetzt wird's interessant : */

layout = (Object*)NewObject( LAYOUT_GetClass(),NULL,
LAYOUT_Orientation,LAYOUT_VERTICAL,
LAYOUT_DeferLayout,TRUE,
LAYOUT_SpaceInner,TRUE,
LAYOUT_SpaceOuter,TRUE,
TAG_END );

/* da haben wir's: LAYOUT_GetClass() kommt aus layout.gadget */
/* und liefert uns den benötigten Zeiger, wir brauchen also */
/* keinen Klassennamen. Dann folgen einige Eigenschaften, mit */
/* denen wir uns noch genauer beschäftigen werden. Sie */
/* betreffen jedenfalls das Aussehen und die Funktionsweise des */
/* Layout. Übrigens gibt es auch Reaction-Makros, die diese */
/* Erzeugung etwas angenehmer machen (in */
/* reaction/reaction_macros.h). */

button = (Object*)NewObject( BUTTON_GetClass(),NULL,
GA_Text,"Mein erster Reaction-Button ...",
TAG_END );

/* Man gewöhnt sich dran, oder? */
/* Schön ist, dass man wirklich nur das angeben muss, was man */
/* benötigt; man muss nicht wie früher erst viele Parameter */
/* festlegen, sondern ist mit ein paar Zeilen fertig. Zudem */
/* sieht es auch noch übersichtlicher aus.*/

SetGadgetAttrs( (Gadget*)layout,NULL,NULL,LAYOUT_AddChild,button,TAG_END
);

/* Hier ordnen wir den Button unserem Layout unter. Dazu müssen */
/* wir nur die Eigenschaft LAYOUT_AddChild setzen. Übrigens */
/* hätten wir auch SetAttrs benutzen können, wenn wir sowieso */
/* weder Fenster noch Requester angeben. Aber wir sollten uns */
/* daran gewöhnen, mit SetGadgetAttrs zu arbeiten, denn wenn das */
/* Fenster offen ist, geht's nicht anders. */

window = (Object*)NewObject( WINDOW_GetClass(),NULL,
WINDOW_Layout,layout,
WINDOW_Position,WPOS_CENTERMOUSE,
WA_Activate,TRUE,
WA_Title,"Ein Fenster !!",
WA_DragBar,TRUE,
TAG_END );

/* Hier übergeben wir dem Fenster direkt sein "Kind", nämlich */
/* unser Layout. Dann folgen einige Eigenschaften, mit denen */
/* wir uns nächstes Mal genauer beschäftigen. Sie betreffen */
/* jedenfalls das Aussehen und die Funktionsweise des Fensters. */
/* Offen ist es aber noch nicht, also: */

DoMethod( window,WM_OPEN );

/* So einfach ist das. Wer sich mal die AutoDocs anschaut, wird */
/* feststellen dass man aber bei der Angabe eines Fensters immer */
/* einen Zeiger auf eine Window-Struktur benötigt und kein */
/* Objekt. Kein Problem, holen wir ihn uns: */

GetAttr( WINDOW_Window, window, &intuiwin );

/* Immer aufpassen, dass man einen ZEIGER auf die Variable */
/* übergibt, sonst kommt der Guru ... */

Delay( 500 ); /* wir warten erst mal ca. 10 Sekunden ... */

SetGadgetAttrs( (Gadget*)button,intuiwin,NULL,
GA_Text,"... ist immer noch da!",TAG_END );

/* Hier ist endlich unser intuiwin im Einsatz! Hat auch einen */
/* Sinn, denn das Fenster muss direkt aktualisiert werden. */
/* Übrigens ist es mir schon des öfteren passiert, dass ich */
/* TAG_END vergessen habe - gerade wenn ich zuvor GetAttr */
/* benutzt habe. Das macht sich dann meist durch seltsame */
/* Ergebnisse oder sogar einen Absturz bemerkbar. Sobald man */
/* mal das Programm debuggt und Enforcer oder ein ähnliches */
/* Tool startet, findet man meist den Fehler. */

Delay( 500 ); /* weil's so schön ist ... */

DoMethod( window,WM_CLOSE ); /* klar, oder ? */

DisposeObject( window );

/* So, das war's. Wie gesagt, das Layout und der Button werden vom */
/* Window freigegeben. Natürlich müssen jetzt noch die Libraries */
/* geschlossen werden. Und wenn das Programm noch weiter geht, */
/* sollte man nicht vergessen, alle Zeiger auf NULL zu setzen: */

window = NULL;
layout = NULL;
button = NULL;
intuiwin = NULL;

CloseLibrary( ButtonBase );
CloseLibrary( WindowBase );
CloseLibrary( LayoutBase );
CloseLibrary( DosBase );
CloseLibrary( IntuitionBase );
return( 0 );
}

An dieser Stelle möchte ich schon mal ein paar Tips loswerden, auch wenn ich erst in den nächsten Teilen auf die angesprochenen Themen eingehe:

Wer schon mit Reactor arbeitet, wird früher oder später beim Kompilieren die Fehlermeldung "Symbol _STR nicht definiert" erhalten. Der Grund ist einfach, dass man irgendwo vergessen hat, zu einem Text einen Konstantenbezeichner als "Locale ID" anzugeben. Diese Id's dienen zur Lokalisierung des Programms und sind unbedingt notwendig; am Anfang reicht es, irgendeinen Text einzugeben, z.B. MSG_TEXT1 usw.

Reactor stürzt manchmal ab, wenn man falsche Einstellungen vorgenommen hat oder Objekte entfernt hat, die an anderer Stelle noch eingetragen waren. (Z.B. sollte man erst den Verweis auf ein Label entfernen, dann das Label selbst.) Man sollte hier also ein bisschen mitdenken und nicht alles dem Programm überlassen ;)

Manches funktioniert nicht so, wie man erwarten könnte: z.B. wird normalerweise ein Gadget direkt nach SetGadgetAttrs() aktualisiert, bei einem Checkbox-Gadget allerdings muss RefreshGList() explizit aufgerufen werden. Man sollte nicht direkt verzweifeln, sondern die AutoDocs durchstöbern, meist findet man irgendwo einen Hinweis oder eine Funktion, die das Problem löst!

Jetzt sind wir schon am Ende des ersten Teils angekommen. Unser erstes Programm ist schon fertig und sieht doch hübsch aus! Jedenfalls kann sich niemand über endlose Quelltexte beschweren. Vor allem gab es nirgends irgendeine absolute Größe oder Position! Jetzt ist der Punkt gekommen, dieses Programm abzutippen und erste Erfahrungen zu sammeln. Wer möchte, kann sich ja schon mal die AutoDocs zu den Gadgets anschauen, oder im Internet stöbern. Eine schöne Beschreibung von BOOPSI und ein schon etwas komplexeres Beispielprogramm findet sich auf der Developer-CD unter Reference/Amiga_Mail_Vol2/IV-23/. Im nächsten Teil geht es dann hauptsächlich um Fenster (Erzeugung, Eigenschaften, Ereignisbehandlung) und ReActor.

Ansonsten wünsche ich schon mal viel Spaß mit Reaction,

euer

Martin R. Elsner

Einführung in die Reaction-Programmierung - T. 2 (von Martin R. Elsner)

Windows, Ereignisbehandlung, Layouts und ReActor

Bevor wir zum eigentlichen Thema kommen, hier noch ein paar Ergänzungen zum ersten Teil:

Ein kleiner "Fehler" hatte sich in das Beispielprogramm eingeschlichen:


...
GetAttr( WINDOW_Window, window, &intuiwin );
...


Das ist fast richtig, wird allerdings unter C++ zurückgewiesen, weil GetAttr als dritten Parameter einen ULONG* erwartet, und keinen Window*. Also einfach ändern in


GetAttr( WINDOW_Window, window, (ULONG*)&intuiwin );


Michael Christoph hat mich darauf hingewiesen, dass er im Netz auch einen (schönen und umfangreichen) Reaction-Kurs unter [1] veröffentlicht hat. Außerdem kam von ihm die begründete Beschwerde, dass man beim Öffnen der Libraries testen sollte, ob wirklich die benötigten Versionen vorhanden sind bzw. ob OS3.5 installiert ist; dazu reicht es zu prüfen, ob die resource.library geöffnet werden kann. Natürlich sollten immer alle Funktionsergebnisse kontrolliert und gegebenenfalls eine Fehlermeldung ausgegeben oder sogar das Programm beendet werden. Ich werde aber im Rahmen meiner Beispiele auf die Angabe dieser Überprüfungen verzichten, da ich sowieso nur einzelne Quelltextteile angebe.

Wer trotzdem weiterlesen will ;) wird diesmal mehr über den Aufbau eines Fensters und die Abfrage der Ereignisse erfahren und ich gehe in einem Beispiel auf die Benutzung von ReActor ein.

Im letzten Teil haben wir schon ein einfaches Window erzeugt, geöffnet und geschlossen. Als Tags können beim Erzeugen alle von Intuition bekannten Eigenschaften angegeben werden, also Höhe, Breite, Position, Fenstertitel, die anzuzeigenden Windowgadgets und - besonders wichtig! - die IDCMP-Flags, also die Ereignisse, auf die das Fenster reagieren soll. Zusätzlich gibt es WINDOW_...-Tags, die spezielle Fähigkeiten der Window-Klasse bzw. von Reaction ausnützen, u.a. vereinfachte Ikonifizierung und Hintergrundgrafik.

Ich möchte hier nicht die Bedeutung aller einzelnen Tags aufführen, sondern einmal einen Auszug aus der Fensterdefinition von ClassAction angeben (sämtliche Fensterwerte sind in der globalen Struktur Main abgelegt, was die Übersicht deutlich erhöht):


...
WA_PubScreen, Main.Scr,
/* Bildschirm, auf dem geöffnet werden soll (Screen*) */
WA_Top, Main.WinY, WA_Left, Main.WinX,
WA_Width, Main.WinWidth, WA_Height, Main.WinHeight,
/* Position und Größe des Bildschirms */
WA_Zoom, Main.ZoomCoords,
/* Hier muss die Adresse eines Arrays aus 4 Worten stehen, */
/* d.h. short ZoomCoords[4]; */
/* in dem die Werte Left/Top/Width/Height der alternativen */
/* Größe gespeichert sind (->ZoomGadget) */
WA_RMBTrap, TRUE,
/* Rechte Maustaste öffnet kein Menü, sondern wird als normales */
/* Ereignis behandelt */
WINDOW_IconifyGadget, TRUE,
WINDOW_Icon, Main.Dobj,
WINDOW_AppPort, Main.AppPort,
WINDOW_AppWindow, (ULONG)TRUE,
/* Diese Tags machen das Fenster zum AppWindow, d.h. wir werden */
/* benachrichtigt, wenn der Benutzer auf das Iconify-Gadget */
/* drückt oder das AppIcon öffnet oder wenn Icons auf unser Fenster */
/* gezogen werden. Mehr dazu unten. */
WINDOW_SharedPort, Main.MsgPort,
/* Wenn man unbedingt einen eigenen MessagePort angeben möchte, */
/* kann man dies hier tun. */
/* Sonst wird ein neuer Port automatisch erzeugt. */
WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_GADGETUP|IDCMP_MOUSEBUTTONS|
IDCMP_RAWKEY|IDCMP_DISKINSERTED|IDCMP_DISKREMOVED,
/* Das sind alle Ereignisse, die mich interessieren. */
/* Die DISK-Ereignisse werden allerdings nicht von */
/* WM_HANDLEINPUT gemeldet, dafür gibt es einen anderen Weg: */
WINDOW_IDCMPHook, &Main.IDCMPHook,
WINDOW_IDCMPHookBits, IDCMP_DISKREMOVED|IDCMP_DISKINSERTED|
IDCMP_MOUSEBUTTONS|IDCMP_RAWKEY,
/* Eine Alternative zu einer eigenen Ereignisauswertung sind */
/* Hooks: man kann eine eigene Funktion definieren, die in allen */
/* angegebenen Fällen automatisch aufgerufen wird. */
WINDOW_BackFillName, Main.BackgroundPattern,
/* Eine feine Sache sind die Hintergründe: einfach einen Dateinamen */
/* angeben, und das Fenster wird bunt! */
...


Dies sind nicht alle Tags, weil manche schon in ReActor definiert werden - wie das geht, steht weiter unten. Jetzt schauen wir uns mal an, wie die Ereignisse in einem Programm verarbeitet werden können. Dazu verändern wir unser Beispielprogramm etwas:


...
BOOL end;
ULONG windowsignal,receivedsignal,result,code;
MsgPort *applport;
#define BUTTON_ID 1
...

button = (Object*)NewObject( BUTTON_GetClass(),NULL,
GA_Text,"Mein zweiter Reaction-Button ...",
GA_ID,BUTTON_ID, /*