AMIGA-Magazin · Ausgabe 2/00 · Programmierung: AmigaOS 3.5 (Teil 3)

Aktuelles Heft 2/00

Inspektor Gadget

In diesem Kursteil geht es ans Eingemachte: Es wird gezeigt, wie ein neues Reaction-Gadget oder Image erstellt werden kann und wie diese in das ReActor-Oberflächentool einzubinden sind.

von Michael Christoph

Nachdem wir in den letzten beiden Kursfolgen die Grundlagen für die Programmierung unter AmigaOS 3.5 gelegt haben, wollen wir in diesem Monat noch tiefer in die Praxis einsteigen und eigene Gadgets programmieren.

Wieso eigene Gadgets erstellen?

Kursübersicht
Grafische Benutzeroberflächen unter AmigaOS 3.5 entwerfen
Teil 1: Vorstellung der neuen ReAction-Gadgetklassen, Vorstellung von ReActor zum Erstellen der selbstlayoutenden Oberflächen.
Teil 2: Der Sourcecode zur Oberfläche wird erstellt und dabei die Resource. library vorgestellt. Abfrage und Setzen der Gadgetwerte.
Teil 3: Eigene Reaction-Gadgets/Images erstellen und deren Einbindung in das ReActor-Programm.
Zuerst stellt sich natürlich die Frage, wieso ein eigenes Gadget erstellt werden soll und nicht wie bisher einfach in das Fenster »gepinselt« werden kann? Konnten doch auf diese Weise bisher recht einfach Gadgets nachgebildet werden. Sieht man das Ganze aus Blickrichtung von Reaction mit seiner selbstlayoutenden Oberfläche, so kann gar kein Platz mehr »ausgespart« werden, in dem Programmaktionen stattfinden sollen. Es ist zwar theoretisch möglich, einen Platz auszusparen (Spacer, Framerect), dessen Größe und Lage variiert jedoch in Abhängigkeit der Fenstergröße und dessen verwendeten Zeichensatzes. Wird hingegen ein eigenes Gadget erstellt, paßt sich dieses automatisch in den Layoutprozess ein und wird immer in der optimalen Größe dargestellt bzw. kann man eine Größe für das Gadget vorgeben. Auch die Anzeige (Refresh) erledigt das Gadget automatisch, ohne daß programmseitig etwas zu programmieren oder aufzurufen wäre. Vor allem im Hinblick auf die Wiederverwendbarkeit ist es sehr empfehlenswert, das geplante Vorhaben als eigenes Gadget zu realisieren. So können Sie in zukünftigen Projekten direkt wieder auf das Gadget zurückgreifen.

Es besteht zwar auch die Möglichkeit, C++ den Klassen zu verwenden oder entsprechen Source-Code zu kopieren, aber in Punkto Weiterentwicklung/Anpassungen funktioniert es mit einem eigenen Gadget am besten. Zwar profitieren bei Klassen in C++ auch alte Programme von Neuerungen (wenn die Klasse zentral abgelegt wird), dann aber erst durch Neucompilieren. Ist das Programm öffentlich oder frei verfügbar, müssen andere Personen sich erst die neue Version besorgen. Das wird durch unterschiedliche Programmversionen ect. erschwert.Wird dagegen nur ein Gadget ausgetauscht, ist das normalerweise ohne weiteren Aufwand möglich. Im Hinblick auf das reine Kopieren von Source sind Änderungen kaum rückportierbar. Dadurch sind ältere Programme evtl. wegen dieses Fehlers nicht mehr oder nur noch eingeschränkt nutzbar.

Auf der anderen Seite haben die externen Gadgets den Vorteil, daß der Programmcode kleiner und überschaubarer ausfällt, was auch der Fehlerreduzierung zugute kommt. Kommt das Gadget in mehreren Programmen zum Einsatz, wird es auch mehrfach in unterschiedlichen Umgebungen getestet. Aufgedeckte Fehler werden zentral im Gadget beseitigt. und alle anderen Programme profitieren sofort von der Verbesserung.

Class *CreateExampleGClass(void) 
{ Class *clas; 
  if(clas = MakeClass(NULL,GADGETCLASS,NULL,
    sizeof(struct ExampleGData),0)) 
  { clas->cl_Dispatcher.h_Entry = 
      (ULONG (*)()) ExampleGDispatcher; 
    clas->cl_Dispatcher.h_SubEntry = NULL; 
  } 
  return( clas );
}

Listing 1: Erste Schritte, um ein eigenes Gadget zu erzeugen.

Anhand der folgenden Beschreibung und dem Programmgerüst ist es nicht schwer, eigene Gadgets in die Realität umzusetzen. Und auch wenn das Gadget im Endeffekt nur in einem einzigen Programm zum Einsatz kommt, ist es doch mit einer definierten Schnittstelle versehen und nicht in den restlichen Programmcode »verstrickt«.

Grundlagengerüst

Die Gadget-Klassen basieren auf dem Library-Konzept und werden erst während
der Laufzeit nachgeladen, wenn sie angefordert werden. Damit fügen sie sich sehr gut in das speichersparende AmigaOS ein. Wie bei Libraries auch, werden sie über die Open/Close/Expange-Funktion bedient.

Open: wird jedesmal aufgerufen, wenn ein Programm dieses Gadget anfordert (OpenLibrary: Counter für die Benutzung wird hochgezählt).

Close: wird jedesmal aufgerufen, wenn ein Programm das Gadget wieder freigibt (CloseLibrary: Benutzercounter wird verringert).

Neue Elemente:
Das oberste Gadget im Fenster ist unser eigenes im Artikel beschriebenes ExampleG.gadget

.

Bibliothek nutzen:
Die PhoneField-Gruppe in diesem Beispiel greift auf bestehende Gadgets zurück.
Expange: Diese Funktion wird bei Speichermangel vom Betriebssystem aufgerufen. Ist der Benutzungscounter gleich null, so muß das Gadget alle reservierten Resourcen freigeben und sich aus dem Speicher entfernen.
Das Gadget muß sich sozusagen selber erzeugen. Dies geschieht, wenn das Gadget (bzw. die Library) in den Speicher geladen wird. Die eigentliche Aufgabe übernimmt die Intuition-Funktion MakeClass(), welcher auch die Basis-Klasse zu übergeben ist, von der abgeleitet werden soll (Beispiel s. »Listing 1«).

Auf den zugewiesenen Funktionszeiger (Dispatcher) wird weiter unten eingegangen. Soll es sich um eine öffentliche Klasse handeln, die von jedem Programm direkt über den Namen angesprochen werden kann, so ist dieser Name als erster Parameter von MakeClass() anzugeben (ohne die .gadget-Endung) und per AddClass (class) der Systemliste hinzuzufügen. Öffentliche Namen sollten allerdings nur System-Gadgets oder System-Erweiterunen erhalten, nicht aber programmeigene Gadgets. Entsprechend wird das Gadget »zerstört«, wenn die Library aus dem Speicher entfernt wird. Das geht mit einem einfachen FreeClass() (s. »Listing 2«).

Der Dreh- und Angelpunkt eines jeden Objekts ist sein Dispatcher (zu deutsch etwa Nachrichtenverarbeiter). Egal ob Daten gesetzt werden sollen (OM_SET), Daten erfragt werden sollen (OM_GET) oder ein Refresh ausgelöst wird (IM_DRAW/GM_RENDER): Immer wird diese Funktion angesprungen. Die eigentliche Aktion wird als numerische Methoden-ID übergeben. Den grundsätzliche Dispatcher-Aufbau sehen Sie in »Listing 3«.

Sehr wichtig dabei: Der Aufruf der Basisklasse im Defaultzweig. Alle Aufgaben, die wir nicht erledigen (wollen), werden von der Basisklasse übernommen ­ soweit wie möglich. Ist die Basisklasse ebenfalls von einer anderen Klasse abgeleitet, gibt auch diese die Nachricht eine Ebene weiter zurück, falls sie nicht verarbeitet werden konnte. Diese Schleife setzt sich fort bis zur Root-Klasse. Aber auch bei den selber behandelten Methoden ist es oft sinnvoll oder sogar notwendig, zuerst die Basisklasse aufzurufen.

Erstellen eines Gadgets

Je nachdem, welche Art von Gadget erstellt werden soll, ist zuerst zu überlegen, von welcher bestehenden Klasse das neue Gadget abgeleitet werden soll. Wird von der Root-Klasse abgeleitet, stehen nur die grundlegenden Funktionen bereit. Wird von höheren Gadgets abgeleitet, kann auf deren Funktionalität aufgesetzt werden und man kann sich viel Programmierung sparen. Wie gesagt, hängt es von der Basis ab, die vom neuen Gadget ab genutzt wird. Meist wird die GadgetClass zum Einsatz kommen, da diese alle Funktionen im Zusammenhang mit normalen Gadgets liefert (z.B. Normal/Selekt-Status usw.). In der Tabelle »Gadget-Klassen« finden Sie die vordefinierten Gadgets.

BOOL DisposeExampleGClass(Class *clas)
{
  if(clas) if(!FreeClass(clas)) return( FALSE );
  /* wenn Classe nicht freigegeben werden kann, */ 
  /* muß die Library im Speicher bleiben */ 
  return( TRUE );
}

Listing2: Das Entfernen eines Gadgets.

Während GADGETCLASS, IMAGECLASS und GROUPCLASS als normale Ableitungsklassen für die jeweiligen Arten dienen, ist BUTTONCLASS bereits ein höheres Gadget, daß zusätzlich GM_HANDLEINPUT verarbeitet und solange GM_UPDATE Nachrichten schickt, wie es gedrückt wird.

Lassen Sie sich vom Namen aber nicht täuschen! Es handelt sich lediglich um die interne Funktionalität, nicht um einen sichtbaren Button. Dazu müßte vom button.gadget abgeleitet werden. In der Regel ist GADGETCLASS die richtige Ausgangsbasis für die Gestaltung eigener Gadgets unter AmigaOS 3.5.

Auf bestehende Gadgets zurückgreifen

Die einfachste Möglichkeit besteht darin, auf bereits bestehende Gadgets zurückzugreifen und diese zu kombinieren oder zu erweitern. Nach außen ist dieses Gadget/diese Gruppe nur als einziges Gadget sichtbar. Auch alle Tagwerte werden für dieses Gadget gesetzt. Intern ist dann unsere Gadgetgruppe dafür verantwortlich, daß die Tags auch den richtigen »Empfänger« erreichen. Intern handelt es sich meist um eine Gruppe, da diese sowieso andere Objekte aufnehmen kann und uns somit die komplette Größenberechnung und Layouts abnimmt.

Achtung: Innerhalb der Reaction-Umgebung sollte vom layout.gadget als Gruppe abgeleitet werden, da dieses die Größenberechnung erledigt! Die Behandlung der Objektmethoden bezieht sich darauf, daß intern mehrere Objekte vorhanden sind:
OM_NEW: Es müssen alle Unterobjekte erzeugt werden OM_DISPOSE: Es müssen alle Unterobjekte freigegeben werden, sofern sie nicht Member (Mitglied) einer anderen Gruppe sind und dadurch automatisch durch diese aus dem System gelöscht werden. OM_SET: Daten setzen, eventuell an Unterobjekte weitermelden.
OM_GET: Daten zurückliefern, evtl. von Unterobjekten zu erfragen. Alle anderen Methoden können meist direkt an die Basisklasse ­ per DoSuperMethod() ­ weitergegeben werden.

Sinnvoll ist diese Vorgehensweise z.B. bei einem Telefonwählfeld. Für das Programm interessant ist lediglich die eingegebene Nummer. Ob diese nun direkt eingegeben wird (String-Gadget) oder per Tasten ist dem Programm egal. Auch ist es für das Programm einfacher, ein einzelnes Telefonwählfeld-Gadget zu erzeugen, als alle dreizehn Gadgets einzeln und die zugehörigen Layoutgruppen. Das genaue Vorgehen zeigt das Beispielprogramm »Phone-Field« (s. Bezugsquellen).

Eine Erweiterungsmöglichkeit besteht in einer zusätzlichen Klasse, die Telefonnummern verwaltet und in einer Liste zur Auswahl anbietet. Bei Auswahl könnten per interner GM_UPDATE-Methode die Daten direkt an das PhoneField-Gadget weitergegeben werden.
Übersicht der Methoden:
Die folgenden Methoden bilden das Grundgerüst (Rootclass) und beginnen mit dem Prefix "OM_" (Object Methode; zu finden in intuition/classusr.h):
OM_NEW Es wird ein neues Objekt angelegt (läuft als erste Methode) und wird vom Anwendungsprogramm durch NewObject() ausgelöst. Mit den Werten aus der Tagliste sind die Objekt-Instanzdaten vorzuinitialisieren. Zurückzugeben ist die neue Klasse oder NULL im Fehlerfall.
OM_DISPOSE Objekt wird gelöscht (läuft als letzte Methode) und wird vom Anwendungsprogramm durch DisposeObject() ausgelöst.
OM_SET Es werden neue Daten in der Tagliste übergeben.Ein Returnwert von 1 besagt, daß ein Refresh des Gadgets notwendig ist (sonst 0). In wieweit ein automatischer Refresh erfolgt, bleibt dem Gadget überlassen.
OM_GET Es werden Daten des Objekts abgefragt. Wurden die Daten ermittelt, wird 1 zurückgeliefert, im Fehlerfall 0.
OM_ADDMEMBER Ein Objekt soll zur klasseneigenen Liste hinzugefügt werden (nur bei Gruppen-Objekten möglich)
OM_REMMEMBER Entfernt entsprechend ein Objekt aus der Liste.
OM_UPDATE Objekt soll sich aktualisieren (z.B. externe Datenänderung übernehmen). Zusätzlich wird das Gadget neu gezeichnet (Refresht). Diese Methode sollte nur gadgetintern benutzt werden!
OM_NOTIFY Damit können sich die Objekte untereinander über Veränderungen (z.B. Sliderposition) informieren (Gadget-Tags ICA_Target und ICA_Map).
Bei der Erstellung von Gadgets kommen noch weitere interessante Methoden hinzu. Diese beginnen zur leichteren Erkennbarkeit mit einer GM-Gadget-Methode ­ zu finden in intuition/gadgetclass.h>P>:
GM_HITTEST Das Gadget soll prüfen, ob die Maus darüber gedrückt wurde. Rückgabewert ist GMR_GADGETHIT für ja und 0 für nein.
GM_RENDER Gadget soll sich neu zeichnen. Anhand des Redrawmoduses (Redraw/Toggle/Update) wird die Zeichnungsart unterschieden.
GM_LAYOUT Ist eine Layout-Anforderung. Existiert seit V39 und wird vom Reaction-Layoutgadget verwendet, um den Gadgets ihre Position und Größe zuzuweisen bzw. um bei Datatype-Objekten ihre Größe berechnen zu lassen.
GM_GOACTIVATE Wird vor dem Aktivieren des Gadgets aufgerufen.
GM_GOINACTIVATE Wird nach dem Verlassen des Gadgets aufgerufen.
GM_HANDLEINPUT Benutzereingaben sind zu verarbeiten (z.B. Mausbewegungen oder Tastendrücke bei Eingabeobjekten).
Images, die von der Rootclass abgeleitet sind, besitzen ebenfalls zusätzliche Methoden, welche mit dem Prefix IM_ (Image Methode) gekennzeichnet sind (zu finden in intuition/imageclass.h):
IM_HITTEST Das Image soll prüfen, ob die Maus darüber gedrückt wurde. Returnwert TRUE für ja, FALSE für nein.
IM_DRAW Image neu zeichnen (wird z.B. durch DrawImageState() ausgelöst).
IM_ERASE Löscht den Ausgabebereich des Images (wird z.B. durch EraseImage() ausgelöst).
IM_MOVE Löschen des Images und zeichnen an der neuen Position.
IM_FRAMEBOX Berechnet die Rahmendimensionen.
IM_HITFRAME Wie IM_HITTEST, allerdings unter Einbeziehung des Rahmenbereichs.
IM_DRAWFRAME Zeichnen des Images innerhalb dem definierten Bereich.
IM_ERASEFRAME Löschen des Images (Gegenstück zu IM_DRAWIMAGE).

Eine einfache Zeichenfläche

Kombination:
Eine Digital-Uhr, die mit led.image erstellt wurde.
Das nächste Beispiel geht einen Schritt weiter, indem es nicht mehr auf bestehende Objekte zurückgreift, sondern sich um alles selber kümmert. Es wird natürlich auch von einem Basis-Gadget abgeleitet, das wieder einen Großteil der Arbeit für uns erledigt.

Da das Gadget-Layout automatisch durch die Window-Klasse vorgenommen wird, besteht programmseitig keine Möglichkeit mehr, sich einen Bereich zu reservieren, indem z.B. direkt gezeichnet wird oder indem Ergebniswerte geprintet werden können. Läßt sich das Vorhaben nicht mehr mit einem der vorhandenen Standard-Gadgets lösen, so kann auf das DrawField-Gadget zurückgegriffen werden (zu finden in der gleichnamigen Schublade). Dieses nimmt lediglich Platz in Anspruch, entsprechend der Vorgaben in der Tagliste. Beim Refresh/ Zeichenanforderung wird eine Callback-Funktion im Programm angesprungen, die dann wieder die Fläche direkt füllen kann. Dadurch kann man darauf verzichten, ein komplett neues Gadget zu erzeugen. Beachten Sie hierzu auch den Abschnitt »Besondere Anforderung an Reaction-Gadgets«.

Ein komplettes Gadget

Der Schritt zu einem vollständigen Gadget liegt nur noch darin, die GM_RENDER-Methode selbständig auszuführen. Die anderen Aufgaben - Erstellen/Freigeben der Instanz, Setzen/Liefern von Werten - wurden bereits in den vorhergehenden Beispielen benutzt. Normalerweise vom Gadget zu unterstützen ist GM_HITTEST, um festzustellen, ob die Maus innerhalb dem Gadget geklickt wurde. Ist das der Fall, so muß GMR_GADGETHIT zurückgegeben werden, ansonsten 0. Die beiden Methoden GM_GOACTIVATE und GM_GOINACTIVATE wurden bereits oben beschrieben, zur Vollständigkeit fehlen nur noch die Return-Codes.


ULONG ExampleGDispatcher( register __a0 Class *clas,
register __a2 Object *obj, register __a1 Msg msg) { switch(msg->MethodID) { case OM_SET: return( set_methode(clas,obj, (struct opSet *)msg) ); case OM_GET: return( get_methode(clas,obj, (struct opGet *)msg) ); /* ... */ default: /* SEHR WICHTIG - Aufruf der Basisklasse */ return( DoSuperMethodA(clas,obj,msg) ); } }

Listing 3: Das Grundgerüst zu einem Dispatcher.

 ULONG domain_method(Class *clas,
Object *obj, struct gpDomain *gpd) { switch(gpd->gpd_Which) { case GDOMAIN_MINIMUM: case GDOMAIN_MAXIMUM: case GDOMAIN_NOMINAL: domain->gpd_Domain.Width = 100; domain->gpd_Domain.Height = 100; break; } return( TRUE ); }

Listing 4: Dispatcherauszug für ein statisches Gadget

GM_GOACTIVATE:
GMR_MEACTIVE: Gadget wird aktiviert.
GMR_NOREUSE: Gadget ist nicht aktivierbar.

GM_GOINACTIVATE: Es ist kein Rückgabewert definiert.

GM_HANDLEINPUT:
GMR_MEACTIVE: Um das Gadget weiter aktiv zu lassen und auf weitere Input-Messages zu warten.
GMR_NOREUSE: Um das Gadget zu verlassen/deaktivieren.
GMR_REUSE: Gadget deaktivieren und InputEvent weiterverarbeiten lassen.
GMR_NEXTACTIVE / GMR PREACTIVE: Um das nächste/vorherige Gadget zu aktivieren (TabCycle).

Bei den Methoden GM_GOACTIVATE und GM_HANDLEINPUT kann GMR_VERIFY mit der Return-Wert mit oder verknüpft werden (bei GMR_NOREUSE), um eine IDCMP_GADGETDOWN-Nachricht an das Programm zu erzeugen.

Besondere Anforderung an Reaction-Gadget

Das erstellte Gadget ist bisher rein BOOPSI und kann auch so benutzt werden. Werden keine Funktionen aus höheren Libraries benutzt, so kann dieses Gadget bereits ab Kickstart 2.x (Library V37) benutzt werden! Damit es auch in der Reaction-Umgebung funktioniert, muß es noch die Methode GM_DOMAIN unterstützen, über welche die (minimalen, maximalen und nominalen) Größendaten des Gadgets abgefragt werden. Nur so paßt sich das Gadget in den automatischen Layoutprozess ein. In »Listing 4« finden Sie den Dispatcherauszug, der für alle drei Größenanfragen die selben Werte liefert, also keine Größenänderung des Gadgets zulässt.

Ein weiterer Sonderfall unter Reaction ist der Hintergrund. Ist beim normalen Intuition-Fenster in der Regel der Hintergrund immer Farbregister 0, so kann bei Reaction auch eine Hintergrundgrafik verwendet werden. Unterstützt das Gadget transparente Darstellung, ist vor dem Zeichen (bzw. Löschen) der Backfill-Hook zu installieren (REACTION_BackFill). Ein Beispiel sehen Sie in »Listing 5«.

Der letzte Punkt, der an die Reaction-Gadgets gestellt wird, ist eine Funktion zum Erfragen der Klassenbasis. Die Funktion muß den Einsprung-Offset -30 besitzen. Der Name ist zwar nur für den Benutzer von Interesse, dieser sollte aber in Angleichung an die anderen Funktionsnamen mit dem großgeschriebenen Classennamen + ¿_GetClass" lauten ­ also hier z.B. EXAMPLEG_GetClass().

Intern ist die Funktion absolut simpel, da lediglich der Classenzeiger (aus der Library-Base) zurückgeliefert werden muß:

 
Gadget-Klassen
Definiert in intuition/classusr.h sind unter anderem:
ROOTCLASS rootclass
GADGETCLASS gadgetclass
GROUPCLASS groupclass
IMAGECLASS imageclass
BUTTONCLASS buttonclass
Class *EXAMPLEG_GetClass(void)
{
return( ExampleGBase->exb_Class );
}

Auch wenn diese drei Punkte ausgeführt werden, ist das Gadget weiterhin voll BOOPSI-kompatibel und kann daher identisch in normalen Umgebungen sowie in Reaction-Umgebungen eingesetzt werden.

Nach der vielen Theorie liefern die Beispielcodes vermutlich mehr Informationen. Dazu sind sie wieder entsprechend mit Kommentaren versehen. Zu unterscheiden ist der eigentliche Gadget-Sourcecode (jeweils in den Unterschubladen gadgets zu finden) und der Sourcecode des Anwendungsprogramms (»Beispielanwendung.c«). Beide sind voneinander getrennt zu übersetzen. Dabei wird aus dem Gadget-Source eine Library mit der Endung .gadget erzeugt, die nicht direkt gestartet werden kann, sondern sich nur durch andere Programme benutzt läßt. In »Listing 6« finden Sie ein einfaches Benutzungsgerüst.

Die vollständigen Beispielsourcen sind in der Schublade »ExampleG« zu finden. Es handelt sich um eine Ableitung vom Button-Gadget, wobei selber nichts verarbeitet wird, sondern alles an die Basisklasse weitergereicht wird. Er eignet sich daher gut als Ausgangsbasis für eigene Gadgets und Versuche!

Erstellen eines Images

Images funktionieren genauso wie die Gadgets; mit dem kleinen Unterschied, daß im Dispatcher nur Methoden vom Typ OM_xxx und IM_xxx auftreten können. Images alleine sind nicht interaktiv, d.h. sie können nicht angeklickt oder verändert werden. Diese Aufgaben übernehmen die Gadgets, welche aber auf Images zurückgreifen können, um die Daten darzustellen. Bestes Anwendungsbeispiel ist der Button. Alleine stellt dieser nur den äußeren Rahmen und eventuell einen Textinhalt dar und reagiert auf das Anklicken. In Kombination mit einem Penmap- oder Bitmap-Image, kann der Button auch Grafiken als Schaltfläche benutzen.

Von der Programmierung her verhalten sich Gadget und Image identisch. Beide werden über den Dispatcher bedient, lediglich die Methoden sind abweichend. Und natürlich enden die (Library) Namen mit .image und sind in der Systemschublade classes/images zu finden. Anhand des led.image soll hier ein kurzer Überblick gegeben werden.

struct Hook *oldhook;
if(egd->egd_Hook)
  oldhook = InstallLayerHook(
    gpr->gpr_GInfo->gi_Layer,egd->egd_Hook);

  /* ... zeichnen ... */

if(egd->egd_Hook) 
  InstallLayerHook(
    gpr->gpr_GInfo->gi_Layer,oldhook);

Listing 5: Einen Hindergrund verwenden

Als nachteilig möchte ich bewerten, daß der Library-Startupcode
in Assembler vorliegt. Bei der Erstellung eigener Images empfehle ich den oben, bei den Gadgets gezeigten, C-Source zu verwenden. Er dürfte etwas umfangreicher als der reine Assemblercode sein, dafür aber portierbar.

Wie bei den Gadgets auch, werden beim Laden die benötigten Libraries geöffnet. In der Open-Funktion wird die Image-Klasse erzeugt und der Benutzungs-counter hochgezählt.

Ebenso wird in der Close-Funktion die Klasse freigegeben und der Counter reduziert. Das led.image unterstützt lediglich die vier Methoden:

OM_NEW: wenn ein neues Image erzeugt wird.
OM_SET / OM_UPDATE: zum Setzen/Aktualisieren der Werte,
OM_DRAW: zum Zeichnen des Images. Alle anderen Aufgaben werden direkt an die Basisklasse (imageclass) weitergereicht.

Anwendungsseitig kann das Image z.B. mit einem Button verknüpft oder alleine dargestellt werden. Es paßt sich dabei wieder automatisch dem Layout an, wenn es innerhalb einer Reaction-Umgebung benutzt wird (Methode IM_DOMAIN muß behandelt werden). Als dritte Möglichkeit kann die led.image-Klasse erzeugt werden (per NewObject()) und mit Hilfe DrawImage State() aus der intuition. library dargestellt werden. Dabei muß beim Erstellen eine Größe für das Image vorgegeben werden (IA_Width/ IA_Height).

Diese Möglichkeit wird auch im Beispielprogramm »test.c« (Verzeichnis led_ic) verwendet:

1. Image-Classe öffnen
2. Fenster öffnen
3. led.image erzeugen
4. Image anzeigen
5. warten, bis das Fenster geschlossen wird
6. Image freigeben
7. Image-Classe schließen Ergänzend habe ich ein weiteres Beispiel beigelegt (»Beispielanwendung.c«), daß das Led-Image zur Realisierung einer Digital-Uhr verwendet. Dabei kommt ein Größen-änderbares Fenster zum Einsatz.

Einbindung in ReActor

Die neu erstellen Gadgets/Images können Sie direkt vom Programm aus benutzen, indem die entsprechende Gadget/Image-Klasse angefordert wird. Soll aber die Oberfläche des Programms auch weiterhin komfortabel mit ReActor erstellt werden, so sind die neuen Gadgets/Images in dieses Tool zu integrieren. Das ist aber nicht schwer und schnell erledigt !

Im Verzeichnis von ReActor gibt es das Unterverzeichnis profiles und dort die weiteren Unterschubladen für gadgets und images (und weitere, hier aber nicht benötigte Schubladen). Wie zu vermuten ist, befinden sich in der Gadget-Schublade die Beschreibungsdateien für die Gadget (und Gruppenobjekte), in der anderen entsprechend für die Images. Hier muß eine neue Datei erstellt werden, in der die Möglichkeiten des neuen Gadgets/Images beschrieben wird. Die Datei ist sehr übersichtlich aufgebaut und liegt im Klartext vor (ASCII-Format). Sie beginnt in der ersten Zeile mit dem Namen des Gadgets. Darunter folgt der Name, der in der ReActor-Auswahlliste angezeigt wird. Als nächste Zeile ist der vollständige Pfad und Dateiname anzugeben, über das das Gadget geladen werden kann.

Per INCLUDE können dann noch andere Beschreibungsdateien eingebunden werden. So spart man sich Tiparbeit, wenn das neue Gadget Tags anderer Gadgets mitbenutzt (bei Ableitungen oder z.B. die allgemeinen Gadget-Tags). Darunter sind dann die einzelnen Tags anzugeben, die das Gadget kennt. Abgeschlossen wird diese Liste (und die Datei) mit dem Schlüsselwort END. Es folgt eine kurze Beschreibung, wie die Tags anzugeben sind. Ausführliche Informationen finden Sie in der Anleitung zu ReActor.

Gegliedert ist die Zeile von links nach rechts, beginnend mit der Tagart BOOLEAN (Ja/Nein), STRING (Text), INTEGER (Zahlenwert), IMAGE (Grafik),

PEN (Farbstift), SELECT (Auswahlliste), FLAGS (Mehrfachauswahl), PRECENT (prozentualer Wert 0 bis 100 Prozent); es existieren noch viele weitere Arten.

Danach folgt zuerst der Zahlenwert des Tags, dann seine Einsatzmöglichkeiten:

struct ExampleGBase *ExampleGBase; 
if((ExampleGBase = (struct ExampleGBase *)
 OpenLibrary("gadgets/ExampleG.gadget",44)))
{ printf("ExampleG LibBase: 0x%lx, Gadget-Class : 0x%lx\n"
  ,ExampleGBase,EXAMPLEG_GetClass());
  /* ... Gadget kann benutzt werden ... */
  /* erzeugen mit: gb_ExampleGadget = NewObject(
                   EXAMPLEG_GetClass(),NULL,<tags>); */
  /* einfügen in ein Fenster:
        AddGList(gb_Window,gb_ExampleGadget,-1,1,NULL); */
  /* entfernen aus dem Fenster mit:
             RemoveGList(gb_Window,gb_ExampleGadget,1); */
  /* löschen mit: DisposeObject(gb_ExampleGadget); */
  CloseLibrary((struct Library *)ExampleGBase);
} else printf("ERROR: can`t open ExampleG.gadget V44.\n"); 

Listing 6: Ein einfaches Beispiel-Gerüst zur Gadget- Programmierung

I = Init
G = Get
S = Set
A = wird automatisch in die Taglist gesetzt
F = kann nicht aus der Tagliste gelöscht werden und sein symbolischer (Define) Name.

Danach folgen nur noch Tagartspezifische Zusatzangaben, z.B. die Einträge für die Auswahlliste. Am einfachsten ist es, eine bestehende Beschreibungsdatei zu kopieren und einfach abgeändert unter dem neuen Namen zu speichern.

Wenn Sie nun ReActor starten, sind Ihre neuen Gadgets/Images automatisch in der Auswahlliste und können wie gewohnt in die Oberfläche integriert werden. Auch in der Vorschau stehen sie sofort zur Verfügung. Wenn Sie ein öffentliches Gadget/Image erstellen, vergessen Sie nicht, auch die entsprechende Beschreibungsdatei mitzuliefern.

Es ist zu hoffen, daß diesem Grundgerüst viele weitere (programmspezifische) Gadgets folgen (ähnlich MUI). Je mehr unterschiedliche Gadgets existieren, umso höher ist die Wahrscheinlichkeit, daß beim Erstellen eines neuen Programms bereits alle Elemente zur Verfügung stehen und so die Erstellung neuer Programme in kürzerer Zeit erfolgen kann.

Bei Fragen zur Programmierung können Sie sich gerne per E-Mail an den Autor des Artikels wenden: michael@meicky-soft.de
Alle Programmarchive sind wieder auf der CD zum Heft zu finden sowie auf der Homepage des Autors: http://www.meicky-soft.de/amiga-magazin/reaction.html

Damit sind wir am Ende unseres Kurses.

lb


 Hauptseite © 1999 All Rights Reserved. Alle Rechte vorbehalten Franzis' Verlag GmbH
Veröffentlichung und Vervielfältigung nur mit schriftlicher Genehmigung des Verlags

Kommentare, Fragen, Korrekturen und Kritik bitte an Webmaster AMIGA schicken.
Zuletzt aktualisiert am 03. März 2000, Michael Christoph.