Workshops / Hintergrundberichte / Buchvorstellungen
---------------------------------------------------
1. Einführung in die Reaction-Programmierung (von Martin R. Elsner)
Sechster und letzter Teil: Eigene Klassen
Die größten Probleme sollten jetzt schon gelöst sein, vielleicht ein
eigenes Programm fertiggestellt, das ausgiebig Reaction-Objekte benutzt.
Aber vielleicht ist der Eine oder Andere irgendwo auf eine Idee gestoßen,
die er mit den Standard-Objekten nicht lösen konnte. Aber auch dafür gibt
es eine Lösung: Man programmiert einfach eine eigene Klasse!
Zunächst ein kleiner Hinweis: Man sollte sich zunächst überlegen, ob die
Idee wirklich nicht mit den schon vorhandenen Objekten umgesetzt werden
könnte. Manchmal findet man in den Autodocs Klassen oder Tags, die man noch
nicht genau kennt und bisher unterschätzt hat. Z.B. kann ein Label nicht
nur eine einfache Beschriftung, sondern mehrere Zeilen Text und Grafiken
darstellen.
Außerdem ist es empfehlenswert, Standardklassen zu verwenden, da diese
meist gut getestet sind, weiterentwickelt werden und für einheitliche
Oberfläche und Funktionsweise der Programme sorgen.
Falls man trotzdem nicht fündig wird, kann auch im Aminet recherchiert
werden. Hier muss man aber auf die eben erwähnten Vorteile verzichten und
hat meist auch keinen Zugriff auf die Quelltexte. Dann bleibt keine Wahl:
Man muss selber ran!
Eine eigene Klasse wird immer von einer schon bestehenden abgeleitet. Dazu
bietet sich eine Klasse an, die möglichst viele der gewünschten
Eigenschaften schon mitbringt, sodass nur die Differenzen neu programmiert
werden müssen. Man entscheidet sich z.B. dafür, den Button als Basisklasse
zu nutzen und eine neue - private - Klasse zu entwickeln.
Eine umfassende Einführung in die Erzeugung eigener Klassen findet man im
Kurs von Michael Christoph (http://www.meicky-soft.de/amiga-
magazin/reaction.html oder direkt bei http://www.amiga-
magazin.de/magazin/a02-00/programmieren.html). Hier möchte ich deswegen nur
als Beispiel eine Klasse aufführen, die mir Stephan Rupprecht zur Verfügung
gestellt hat, und die ich für den Einsatz in ClassAction etwas modifiziert
habe: eine Icon-Klasse, basierend auf Gadgetclass (also der Basisklasse für
alle Gadgets), die ein Icon als Grafik verwendet und auf Mausklicks wie ein
Button reagiert.
Zum Vergleich: Ein Button kann natürlich mit GA_Image auch mit einer
beliebigen Grafik gefüttert werden, die man z.B. über ein Penmap- oder
Bitmap-Objekt aus einem Icon (bzw. der .info-Datei) erzeugt hat,
vorausgesetzt, ein entsprechender DataType existiert im System. Allerdings
ist der aktuelle Icon-Datatype nicht in der Lage, den Hintergrund der
Grafik transparent zu zeichnen, und nicht auf allen Systemen ist ein Icon-
Datatype installiert. Unser Icon-Button soll dagegen immer funktionieren
und das Icon wie auf der Workbench anzeigen. Der Einfachheit halber soll
die Klasse nicht extern, sondern im Quellcode des Programms selbst
enthalten sein.
Was benötigt man für eine eigene Klasse?
Nun, außer der Festlegung auf eine Basisklasse wird hauptsächlich eine
Ereignisbehandlungsfunktion benötigt, und zwar in Form eines Hooks. Diese
kann dann mit DoSuperMethod auf die geerbten Methoden zurückgreifen (falls
sie existieren), kann aber auch (und das ist ja der Sinn der Sache) neue
Funktionen einführen. Bei Gadgets kann dies hauptsächlich die Darstellung
oder die Reaktion auf Ereignisse betreffen.
Für unseren Icon-Button müssen zwei Dinge geregelt werden: das Zeichen des
Icons, für das wir DrawIconState verwenden, und die Reaktion auf einen
Mausklick. Dazu existieren die Methoden GM_RENDER und GM_HANDLEINPUT, die
wir in unserer Behandlungsroutine butclass_DISPATCH abfangen und in
butclass_RENDER und butclass_HANDLEINPUT bearbeiten. Wir müssen natürlich
das Icon selbst (d.h. einen Zeiger auf ein DiskObject) irgendwo speichern;
dazu verwenden wir einfach GA_UserData. Wenn wir aber auch einen BackFill-
Hook unterstützen und auf einen Doppelklick prüfen wollen, brauchen wir
zusätzliche Eigenschaften in unserem Objekt: Dazu definieren wir eine
Struktur InstanceData, die von Intuition angelegt und freigegeben wird,
aber von uns genutzt werden kann. Diese muss bei OM_NEW bzw.
OM_SET/OM_UPDATE gefüllt werden und kann dann in den sonstigen Routinen mit
Hilfe des Makros INST_DATA verwendet werden. Außerdem muss bei GM_DOMAIN
die Größe unseres Buttons ausgerechnet werden, damit die übergeordneten
Objekte den nötigen Platz reservieren können.
Zunächst die nötigen Includes, Abkürzungen und Bibliotheken:
#include <intuition/intuition.h>
#include <intuition/classusr.h>
#include <intuition/classes.h>
#include <intuition/gadgetclass.h>
#include <intuition/cghooks.h>
#include <workbench/icon.h>
#include <workbench/workbench.h>
#include <gadgets/layout.h>
#include <libraries/gadtools.h>
#include <clib/intuition_protos.h>
#include <clib/alib_protos.h>
#include <clib/icon_protos.h>
#include <clib/layers_protos.h>
#include <clib/utility_protos.h>
#include <clib/dos_protos.h>
#define REG(reg,arg) register __##reg arg
#define G(x) ((struct Gadget *)x)
extern struct Library
*IconBase,
*DOSBase,
*IntuitionBase,
*UtilityBase,
*LayersBase,
*GfxBase;
Nun unsere zusätzlichen Daten pro Objekt, und die benötigten Funktionen:
struct InstanceData{
ULONG secs; /* nur zur Prüfung ... */
ULONG micro; /* ... auf Doppelklick */
struct Hook *backfill; /* siehe butclass_RENDER */
Object *parent; /* siehe butclass_SET */
};
ULONG butclass_DISPATCH( REG( a0, Class *cl ), REG( a2, Object *o ), REG( a1, Msg msg ) );
ULONG butclass_DOMAIN( Class *cl, Object *o, struct gpDomain *gpd );
ULONG butclass_HANDLEINPUT( Class *cl, Object *o, struct gpInput *gpi );
ULONG butclass_RENDER( Class *cl, Object *o, struct gpRender *gpr );
ULONG butclass_SET( Class *cl, Object *o, struct opSet *ops );
Zwei Funktionen erlauben das Anlegen und Freigeben unserer Klasse:
IClass *MakeIconClass(){
IClass *cl;
if( cl = MakeClass( NULL,
"gadgetclass", /* die Basisklasse */
NULL,
sizeof( struct InstanceData ), /* Größe unserer Zusatzdaten */
0L ) )
cl->cl_Dispatcher.h_Entry = (HOOKFUNC) butclass_DISPATCH;
/* Einzige Initialisierung: Einsetzen unseres Dispatchers */
return cl;
}
BOOL RemoveIconClass( IClass *cl ){
if( cl ) return FreeClass(cl);
else return FALSE;
}
D.h. wir speichern den Zeiger (cl=MakeIconClass()). um ihn beim Erzeugen
von Gadgets zu verwenden (gadget=(Object*)NewObject( cl,NULL,...)), und
geben die Klasse zuletzt mit RemoveIconClass(cl) wieder frei.
Und jetzt der ganze Rest:
ULONG butclass_DISPATCH( REG( a0, Class *cl ), REG( a2, Object *o ), REG( a1, Msg msg ) ){
/* allgemeine Ereignisbehandlungroutine */
ULONG ret = 0L;
switch( msg->MethodID ){
case GM_HANDLEINPUT:
case GM_GOACTIVE: ret = butclass_HANDLEINPUT( cl, o, (struct gpInput *) msg ); break;
case OM_SET:
case OM_UPDATE: ret = butclass_SET( cl, o, (struct opSet *) msg ); break;
case GM_RENDER: ret = butclass_RENDER( cl, o, (struct gpRender *) msg ); break;
case GM_DOMAIN: ret = butclass_DOMAIN( cl, o, (struct gpDomain *) msg ); break;
case OM_NEW:{
if( ret = DoSuperMethodA( cl, o, msg ) ){
/* zusätzlich zum Erzeugen des Objekts müssen Eigenschaften gesetzt werden */
butclass_SET( cl, (Object *) ret, (struct opSet *) msg );
}
break;
}
default: ret = DoSuperMethodA( cl, o, msg );
/* in JEDEM Fall alle anderen Methoden an die Mutterklasse übergeben! */
}
return ret;
}
/****************************************************************************/
void butclass_redraw( Object *o, struct GadgetInfo *gi ){
/* siehe butclass_HANDLEINPUT */
struct RastPort *rp;
if( rp = ObtainGIRPort( gi ) ){
DoMethod( o, GM_RENDER, (ULONG) gi, (ULONG) rp, GREDRAW_REDRAW );
ReleaseGIRPort( rp );
}
}
/****************************************************************************/
ULONG butclass_HANDLEINPUT( Class *cl, Object *o, struct gpInput *gpi ){
/* Reaktion auf Eingaben (Mausklicks) des Benutzers */
InputEvent *ie = gpi->gpi_IEvent;
ULONG retval = GMR_MEACTIVE;
if( ie ){
switch( ie->ie_Class ){
case IECLASS_RAWMOUSE:{
switch( ie->ie_Code ){
case SELECTUP: retval = GMR_NOREUSE; break;
case SELECTDOWN:{
InstanceData *id = (InstanceData*)INST_DATA( cl, o );
#ifdef DOUBLECLICK
/* Falls das Icon eine GADGETUP-Botschaft nur nach einem */
/* Doppelklick liefern soll, müssen wir den Zeitpunkt des */
/* letzten Klicks prüfen: */
if( DoubleClick( id->secs, id->micro, ie->ie_TimeStamp.tv_secs,
ie->ie_TimeStamp.tv_micro ) ){
G(o)->Flags |= GFLG_SELECTED;
butclass_redraw( o, gpi->gpi_GInfo );
retval = GMR_NOREUSE | GMR_VERIFY;
id->secs = id->micro = 0UL;
}else{
id->secs = ie->ie_TimeStamp.tv_secs;
id->micro = ie->ie_TimeStamp.tv_micro;
G(o)->Flags ^= GFLG_SELECTED;
butclass_redraw( o, gpi->gpi_GInfo );
retval = GMR_NOREUSE;
}
#else
/* GADGETUP nach einfachem Klick */
G(o)->Flags |= GFLG_SELECTED;
butclass_redraw( o, gpi->gpi_GInfo );
retval = GMR_NOREUSE | GMR_VERIFY;
#endif
}
break;
case MENUDOWN: retval = GMR_REUSE; break;
}
break;
}
}
}
return retval;
}
/****************************************************************************/
BOOL IsIconFrameless( struct DiskObject *icon ){
/* Prüft, ob das Icon ohne Rahmen dargestellt werden muss */
/* siehe butclass_RENDER*/
ULONG frameless;
IconControl( icon,ICONCTRLA_GetFrameless, (ULONG) &frameless,TAG_DONE );
if( frameless == FALSE ){
ULONG globalfl;
if( IconControl( NULL,ICONCTRLA_GetGlobalFrameless, (ULONG) &globalfl,TAG_DONE ) )
frameless = globalfl;
}
return frameless;
}
/****************************************************************************/
ULONG butclass_RENDER( Class *cl, Object *o, struct gpRender *gpr ){
/* Zeichnen des Icons */
RastPort *rp = gpr->gpr_RPort;
DiskObject *icon = (struct DiskObject *) G(o)->UserData;
Hook *hook;
InstanceData *id = (InstanceData*)INST_DATA( cl, o );
Layer *layer = gpr->gpr_GInfo->gi_Layer;
WORD x = G(o)->LeftEdge, y = G(o)->TopEdge;
/* Unter unserem Icon muss der richtige Hintergrund dargestellt werden: */
hook = InstallLayerHook( layer, id->backfill );
DrawIconState(rp, icon, NULL, x,y,
(G(o)->Flags & GFLG_SELECTED) != 0L,
ICONDRAWA_DrawInfo, (ULONG) gpr->gpr_GInfo->gi_DrInfo,
ICONDRAWA_Frameless, IsIconFrameless( icon ),
TAG_DONE );
InstallLayerHook( layer, hook );
return 0L; }
/****************************************************************************/
ULONG butclass_SET( Class *cl, Object *o, struct opSet *ops ){
/* Setzen der Eigenschaften */
InstanceData *id = (InstanceData*)INST_DATA( cl, o ); TagItem *ti, *tlist = ops->ops_AttrList;
DiskObject *icon = NULL;
while( ti = NextTagItem( &tlist ) ){
switch( ti->ti_Tag ){
case GA_BackFill:
id->backfill = (Hook*) ti->ti_Data;
break;
case LAYOUT_Parent:
id->parent = (Object *) ti->ti_Data;
break;
case GA_UserData:
icon = (struct DiskObject *) ti->ti_Data;
break;
}
}
if( id->parent && icon ){
/* Berechnen der Icongröße und Mitteilung an das Parent-Objekt */
Rectangle rect;
UWORD w,h;
GetIconRectangle( NULL, icon, NULL, &rect,
ICONDRAWA_Borderless, FALSE, TAG_DONE );
/* Wir zählen also den Rand mit */
w = ( rect.MaxX - rect.MinX ) + 1;
h = ( rect.MaxY - rect.MinY ) + 1;
SetAttrs( id->parent,
LAYOUT_ModifyChild, (ULONG) o,
CHILD_MinWidth, w,
CHILD_MinHeight, h,
CHILD_MaxWidth, w,
CHILD_MaxHeight, h,
TAG_DONE );
/* Dies war die einzige Stelle, für die wir id->Parent brauchten. */
/* Da das Icon nachträglich geändert werden kann, müssen wir es */
/* im Objekt speichern. */
}
return (ops->MethodID != OM_NEW) ? DoSuperMethodA( cl, o, (Msg) ops ) : 1L;
/* bei OM_NEW wurde die SuperMethod schon in HANDLEINPUT aufgerufen! */
}
/****************************************************************************/
ULONG butclass_DOMAIN( Class *cl, Object *o, struct gpDomain *gpd ){
/* Berechnen der Gadget-Ausmaße */
InstanceData *id = (InstanceData*)INST_DATA( cl, o );
DiskObject *icon = (struct DiskObject *) G(o)->UserData;
if( icon ){
/* nur wenn das Icon schon geladen wurde, steht die Größe des Buttons fest */
struct Rectangle rect;
UWORD w,h;
GetIconRectangle( NULL, icon, NULL, &rect,
ICONDRAWA_Borderless, FALSE,
TAG_DONE );
w = rect.MaxX - rect.MinX + 1;
h = rect.MaxY - rect.MinY + 1;
switch( gpd->gpd_Which ){
case GDOMAIN_MINIMUM:
gpd->gpd_Domain.Width = w;
gpd->gpd_Domain.Height = h;
break;
case GDOMAIN_NOMINAL:
case GDOMAIN_MAXIMUM:
gpd->gpd_Domain.Width = w;
gpd->gpd_Domain.Height = h;
break;
}
return 1L;
}
return 0L;
}
Mit dieser Klasse können nun Objekte erzeugt und in Layouts eingefügt
werden, wobei mit GA_UserData ein Zeiger auf das Icon übergeben werden
muss, das man mit GetIconTags bei gegebenem Dateinamen laden kann.
Natürlich ist diese Klasse weder besonders trickreich noch völlig
ausgereift - man könnte noch weitere Eigenschaften hinzufügen, die die
Reaktion auf Doppelklick oder einfachen Klick steuern etc. Aber ich hoffe,
sie hat die grundlegende Funktionsweise eigener Klassen verständlich
gemacht.
So, das soll's erst mal gewesen sein - wer mehr über Reaction wissen will,
findet in den schon erwähnten Dokumenten und im Kurs von Michael Christoph
noch viel mehr Informationen.
Ansonsten stehe ich natürlich weiterhin mit meinem beschränkten Wissen zur
Verfügung ;)
euer
Martin R. Elsner <email@martin-elsner.de>
|