Grundlagen: Programmierung von CPX-Modulen in C - Teil 3

In diesem letzten Kursteil entreißen wir XControl noch die restlichen Geheimnisse der Event-Module. Diese Art von CPX Modulen wird bisher zwar seiten genutzt, reizt aber die Leistungsfähigkeit von XControl voll aus.

Wer gerade seinen Atari in Reichweite hat, sollte sich umgehend auf selbigen stürzen, um das Event-CPX Demo namens »UHR_CPX.CPX« im Archiv »CPX-Kurs« zu installieren. Nach dem Start fallen dem erfahrenen CPX-Freak einige Neuheiten auf:

  1. Es ändert sich der Fenstername von »Kontrollfeld« in TOS Magazin«
  2. Der Mauszeiger ändert sich, wenn er in den Bereich der Digitalziffern kommt
  3. Die Zeitanzeige läuft auch weiter, wenn das XControl-Fenster nicht mehr im Vordergrund liegt
  4. Das XControl-Fenster hat eine Menüzeile

Die Spielereien 1 bis 3 sind nur mit einem Event-CPX möglich. Der 4. Effekt ist auch in einem XForm-CPX zu realisieren. Ein besonderer Leckerbissen ist im Menü »Countdown« versteckt! Mutige Leser sollten die Selbstzerstörung aktivieren. Keine Panik, es macht nicht BUMM. Nach Ablauf des Countdowns wird XControl komplett beendet! Auch dieses Feature ist nur in Event-Modulen zugänglich. Genug geprotzt, ich hoffe, Sie sind nun von der Nützlichkeit der Event-Module überzeugt!

Was ist ein Event-Modul?

Der wichtigste Unterschied zu XForm-Modulen besteht darin, daß die Funktion XForm_do() nicht verwendet werden darf. Da wir aus diesem Grund nun alle Events selbst bearbeiten müssen, ergibt sich natürlich ein gesteigerter Arbeitsaufwand für den gestreßten Programmierer. So muß für jeden erdenklichen Event eine Funktion bereitstehen. XControl stellt nämlich keinen »event_multi()« Aufruf zur Verfügung sondern ruft, je nach Event, die betreffende Funktion direkt auf. Dadurch unterscheidet sich die Programmierung von Event-Modulen auch ein wenig von der üblichen GEM-Arbeit. Ein Event-CPX ist kein ablauffähiges Programm im üblichen Sinne, es ist vielmehr eine Sammlung von einzelnen Funktionen, die XControl je nach Bedarf (Event) startet.

Wie geht‘s?

Noch gut, denn »cpx_init()« ist erst aller Laster Anfang. Sie erinnern sich noch an die CPXINFO-Struktur, in der wir, am Ende von cpx_init(), XControl diverse Zeiger auf unsere Funktionen übergeben? Bisher war diese Struktur recht leer, da wir nur einen Zeiger auf »cpx_call()« übergeben mußten. Diese deprimierende Leere füllen wir umgehend. Für jeden gewünschten Event tragen wir einen Zeiger der entsprechende Funktion in die Struktur CPXINFO ein. Für Event-Module sind folgende Funktionen unbedingt nötig:

	WORD cdecl cpx_call (GRECT *rect);	Braucht ein jedes CPX-Modul
	void cdecl cpx_draw (GRECT *clip);	REDRAW-Event
	void cdecl cpx_wmove (GRECT *work);	XControl-Fenster wurde bewegt
	void cdecl cpx_close (WORD flag);	WM_CLOSED bzw. AC_CLOSE

Die Pflicht

Wie bei den XForm-Modulen, wird cpx_call() direkt nach cpx_init() gestartet. Diesmal stellt cpx_call() allerdings nicht den Hauptteil unseres Moduls dar. Hier geht es in erster Linie um die Initialisierung und um die Absprache mit XControl, welche Events wir zu nutzen gedenken. Dies geschieht über die Funktion. »Set_Event_Mask()«. Der Rückgabewert TRUE beendet cpx_call() und signalisiert XControl, daß ein Event-Modul am Werke ist.

XControl legt sich nun auf die Lauer und harrt der Events, die da kommen. Trudelt nun ein Event ein, überprüft XControl, ob unser Modul dies überhaupt bearbeiten möchte. Wenn ja, ruft es die betreffende Funktion auf. Einige Events muß jedes Modul unter stützen, sie sind daher Pflichtübungen. Für diese »lebensnotwendigen« Events sind die oben genannten Funktionen zuständig.

Da XControl im Gegensatz zu XForm-Modulen vom Inhalt seines Fensters (unserem Dialog) keinen blassen Schimmer hat, behandelt »cpx_draw()« alle REDRAW-Events. Wie bei einem normalen GEM-Fenster, zeichnen wir über die Rechteckliste den Fensterinhalt neu. Dank der CPX-Funktionen »GetFirstRect()« und »GetNextRect()« bleibt uns aber wenigstens ein bißchen Komfort erhalten.

Sollte der Anwender auf die Idee kommen, das XControl-Fenster zu verschieben, kommt »cpx_wmove()« ins Spiel. Diese Funktion sollte alle von der Position des Fensters abhängigen Bereiche anpassen. Unserem Beispiel-CPX müssen wir also das Rechteck, in dem sich der Mauszeiger verändern soll, neu setzen, damit der Betrachter nicht zu sehr der Verwirrung anheim fällt, wenn der Mauszeiger in anderer Programme Fenster zu grinsen beginnt. Nun darf der Anwender oder das TOS auch unser hübsches Modul beenden. Für solch hinterlistiges Verlangen ist »cpx_close()« der Ansprechpartner von XControl. Beim Eintreffen einer »WM_CLOSED« bzw. »AC_CLOSE«-Nachricht wird diese Funktion aufgerufen und sollte entsprechend reagieren. Wie im ersten Teil unseres CPX-Marathons beschrieben, ist auch hier WM_CLOSED wie »OK« und AC_CLOSE wie »ABBRUCH« zu behandeln. Natürlich müssen wir auch eventuell angeforderte Speicherblöcke unbedingt freigeben und offene Dateien sowie VDI-Workstations schließen. In unserem Fall wird auch noch der Mauszeiger restauriert.

Mit diesen vier Pflichtfunktionen läßt sich natürlich noch kein Blumentopf gewinnen und deshalb folgt nun:

Die Kür

In der folgenden Tabelle stehen die restlichen Funktionen, die in unserem Demo-Modul für die Eventbehandlung vorhanden sind. Sie werden von XControl aufgerufen, wenn die entsprechenden Ereignisse eingetreten sind.

void cdecl cpx_timer (WORD *quit);
	Zuständig für TIMER-Events

void cdecl cpx_key (WORD kstate, WORD key, WORD *quit);
	Verwalter der KEYBOARD-Events
void cdecl cpx_button (MRETS *mrets, WORD nclicks, WORD *quit);
	Zuständig für BUTTON-Events
void cdecl cpx_m1 (MRETS *mrets, WORD *quit);
void cdecl cpx_m2 (MRETS *mrets, WORD *quit);
	Diese Funktionen werden bei MOUSE-Events aufgerufen
WORD cdecl cpx_hook (WORD event, WORD *msg, MRETS *mrets, WORD *key, WORD *nclicks);
	Diese Funktion ist ein Sonderfall! Sie wird bei jedem Event aufgerufen, 
	noch bevor XControl diesen Event bearbeitet hat.

Damit XControl diese Funktionen auch aufruft, müssen zuerst die betreffenden Events »scharf« gemacht werden. Dafür ist die CPX-Funktion Set_Evnt_Mask() zuständig. Ähnlich wie bei evnt_multi() übergeben wir die gewünschten Events in einem Bitfeld. Dazu kommen noch zwei MOBLK-Strukturen, in denen die Koordinaten sowie das Richtungsflag der Mausbereiche enthalten sind. Zuletzt folgt noch ein LONG-Wert mit den Millisekunden bis zum nächsten TIMER-Event.

Lasset die Events zu mir kommen

Damit sitzt unser Modul in den Startlöchern. XControl startet intern wieder einen evnt_multi()-Aufruf mit unseren Parametern und ruft beim Eintreffen der Events die betreffenden Funktionen auf. Natürlich dürfen wir die Event-Maske auch mitten im Geschehen ändern (siehe UHR_CPX). Allen Funktionen, außer »cpx_hook()«, übergeben wir einen Zeiger auf eine WORD-Variable namens »quit«. Diese Variable ist der »Ausschalter« eines Event-Moduls. Setzen wir dieses Flag auf TRUE, beendet XControl das Modul, sobald es wieder die Kontrolle erhält.

cpx_timer()

Einer der Hauptgründe ein Event-Modul zu wählen, ist sicher der TIMER-Event. Wer in seinem CPX-Modul diesen Event benötigt, muß - ob er nun will oder nicht - ein Event-Modul schreiben. In unserem Beispiel zeichnet »draw_time()« jede Sekunde die Uhrzeit neu und zählt - sofern gewünscht - den Countdown. ist der Countdown bei Null angekommen, wird die »Selbstzerstörung« eingeleitet. Da wir in diesem Falle ja nicht nur unser Modul beenden wollen, sondern auch gleich XControl, kommt hier nun Trick 17 ins Spiel: Wir schicken XControl eine WM_CLOSED-Nachricht und siehe da, es klappt! XControl beendet nicht nur unser Modul, sondern geht auch selbst schlafen. GEM-Kenner fragen sich jetzt vielleicht, woher die Fenster-Kennung kommt, die für die WM_CLOSED-Nachricht notwendig ist. Geduld, Geduld, dazu kommen wir bei der Besprechung von »cpx_hook()«.

cpx_key()

Damit der Anwender auch ein wenig mit der Tastatur spielen kann, reagieren wir in »cpx_key()« auf die KEYBOARD-Events. Der Parameter »kstate« ist ein Bitfeld, in dem der Status von Control, Alternate und den Shifttasten enthalten ist. Der Scan- und ASCIICode versteckt sich in »key«. Unser Modul reagiert auf folgende Tasten: »Help« zeigt die Info-Box, »Undo« beendet das Modul.

cpx_button()

Da uns Xform_do() fehlt, müssen wir die Maustasten selber auswerten. Der Parameter »mrets« ist ein Zeiger auf eine MRETS-Struktur, in der die aktuellen Koordinaten der Maus enthalten sind. In »nclicks« steht die Anzahl der getätigten Mausklicks. Anhand dieser Informationen durchsuchen wir mit »obj_find()« den Objekt-Baum nach »getroffenen« Objekten. Bei einem Treffer rufen wir die Funktion »do_menu()« auf, die sich auch um die Menüleiste kümmert.

cpx_m1()

Hier kommen die MOUSE-Events an. Der Parameter »mrets« ist ein Zeiger auf eine MRETS-Struktur, in der die aktuellen Koordinaten der Maus enthalten sind. Betritt die Maus den mittleren Teil unseres Fensters verwandelt sich der Mauszeiger in ein grinsendes Gesicht. Damit diese Mausverwandlung nicht in anderen GEM-Programmen stört, darf nur gegrinst werden, wenn unser Fenster aktiv ist. Leider hat Atari keine CPX-Funktion zur Verfügung gestellt, über die derartige Auskünfte einzuholen wären. Also greifen wir wieder zu Trick 17: Über die Fenster-Kennung des XControl-Fensters fragen wir das AES direkt nach dem obersten Fenster. ist es nicht das unsere, halten wir uns zurück, bis es wieder getoppt wird. Der geneigte Leser wird sich auch hier wieder fragen, woher die ominöse Kennung des XControl-Fensters stammt, da diese offiziell nicht bekannt ist. Noch ein wenig Geduld.

cpx_hook()

XControl ruft diese Funktion sofort nach jedem Event auf, noch bevor dieser ausgewertet wird. Hier kommen also alle Event-Informationen in noch unbearbeiteter Form durch. Die zugehörigen Parameter: In »event« steht das aufgetretene Ereignis, »msg« zeigt auf den Beginn des Nachrichtenbuffers und »mrets« ist ein Zeiger auf die bereits beschriebene MRETS-Struktur, inder die aktuellen Koordinaten der Maus enthalten sind. Den Scan- und ASCII-Code einer eventuell gedrückten Taste finden wir wieder »key«, die Anzahl der Mausklicks in »nclicks«.

Trick 17

Wir brauchen die Kennung des XControl-Fensters. Das AES schreibt diese Kennung bei jedem REDRAW-Event in den Mesag-Buffer. Wir brauchen also nur auf einen REDRAW-Event zu warten und schon haben wir die Kennung. Leider kommen die REDRAW-Events nur bei Bedarf, also müssen wir noch Bedarf schaffen. Wer sich schon den Quellcode unserer Demo zu Gemüte geführt hat, wird sich sicher über den form_dial(FMD_FINISH) Aufruf in cpx_call() gewundert haben. Laut Atari-Dokumentation wird nach einem solchen Aufruf an alle, die es angeht, eine REDRAW-Nachricht geschickt.

Damit es unser Modul auch sicher »angeht«, übergeben wir form_dial() die Koordinaten des XControl-Fensters und siehe da, der REDRAW-Event kommt in cpx_hook() an. Nun brauchen wir nur noch die Kennung in einer globalen Variablen ablegen und unser Problem ist gelöst. Bei dieser Gelegenheit tauschen wir auch den Fensternamen aus. Da der Fenstername auch nach der Beendigung unseres Moduls bestehen soll, ist darauf zu achten, daß er in den 64 Byte des statischen Rams steht, damit das AES auch nach dem Entfernen unseres Moduls aus dem Speicher, darauf zugreifen kann. Das Schönste an der ganzen Sache: Diese Vorgehensweise ist absolut sauber und daher legitim.

Doch Vorsicht! Es gibt Programme wie »Let‘em Fly«, die es erlauben, REDRAW-Events zu blockieren. Leider stellt man fest, daß bei diesem Feature nicht nur die REDRAW-Meldungen von Dialogboxen, sondern alle REDRAW-Events, die von form_dial (FMD-FINISH) stammen, abgefangen werden. Dieses Verhalten ist natürlich höchst tückisch.

In der Atari-Dokumentation wird ausdrücklich darauf hingewiesen, daß auf form_dial (FMD_FINISH) eine REDRAW-Meldung folgt. Da etliche Programme sich darauf verlassen, sollten Sie das Blockieren der REDRAW-Meldungen abgeschaltet lassen, um Inkompatibilitäten zu vermeiden.

Nachdem wir nun alle CPX-spezifischen Teile abgehandelt haben, wenden wir uns noch ein wenig den anderen Funktionen von »UHR_CPX« zu. Diese sind auch in XForm-Modulen zugelassen.

do_cpx_alert()

Wer sich schon ein wenig mit XControl beschäftigt hat, dem wird aufgefallen sein, daß alle Alertboxen die von CPX-Modulen mit XGen_Alert() erzeugt wurden, im XControl-Fenster zentriert sind, egal wo sich dieses Fenster gerade befindet.

Damit auch unsere Dialogboxen dieses hübsche Verhalten zeigen, müssen die Objekt-Koordinaten vor jedem form_do()-Aufruf an das XControl-Fenster angepaßt werden.

Mit Hilfe von form_center() berechnen wir die wirklichen Ausmaße der Dialogbox und passen die Koordinaten an, so daß die Box zentriert im XControlFenster erscheint. Außerdem erledigt diese Funktion auch den anderen Verwaltungskram, der bei der Ausgabe von Dialogboxen anfällt. Natürlich muß die Dialogbox in das XControl-Fenster passen.

draw_obj()

Da die Uhrzeit auch dann angezeigt werden soll, wenn sich das XControl-Fenster im Hintergrund befindet, müssen die Objekte, aus denen die Digitalziffern bestehen, anhand der aktuellen Rechteckliste gezeichnet werden. Ansonsten würde der Inhalt anderer Fenster überschrieben. Das Abklappern der Rechteckliste gestaltet sich aber dank der CPX-Funktionen GetFirstRect() und GetNextRect() recht komfortabel.

draw_ziffer()

Die Digitalziffern bestehen aus sieben Objekten, denen wir nach Bedarf den Objektstatus »SELECTED« zuweisen.

Welche Segmente zu den jeweiligen Zahlen gehören, steht in Feld »ziff«. Damit wir nicht bei jeder neuen Zahl die komplette Ziffer zeichnen müssen, testet die Funktion die Unterschiede zwischen der alten und neuen Ziffer und zeichnet nur die sich ändernden Segmente.

draw_time()

Wie der Name schon sagt, zeigt diese Funktion die aktuelle Uhrzeit. Bei genauer Betrachtung der Uhrzeit fällt auf, daß sich die Zeit nur im Zwei-Sekunden-Takt ändert. Diese kleine Unschönheit liegt im GEM-DOS begründet. Um die Kompatibilität mit MS-DOS zu wahren, liefert Tgettime() die Zeit leider nur in diesem Rhythmus. Perfektionisten können natürlich die fehlende Sekunde berechnen.

do_dialog()

Wie schon erwähnt, versteckt sich in dieser Funktion unsere nachgemachte Menüleiste. Diese kleine Spielerei verwirklichen wir mit Hilfe der CPX-Funktion »Popup«.

Wer sich die Resource unseres Moduls einmal näher anschaut, entdeckt unterhalb der Menüleiste einige unsichtbare Objekte. Klicken Sie ein Objekt in der Menüleiste an, übergibt das Programm nicht wie üblich dessen Koordinaten an Popup(), sondern die Koordinaten des unsichtbaren Objekts darunter. Durch diesen Trick verdeckt das Popup-Menü nicht den angeklickten Button.

Nach der folgenden Tabelle ist unser CPX-Kurs am Ende angelangt. Natürlich halten wir Sie über weitere Entwicklungen in Sachen CPX auf dem Laufenden. Ich hoffe, es überschwemmt nun eine Flut von CPX Modulen den Markt. (ah)

Set_Evnt_Mask()

	void cdecl(*Set_Evnt_Mask)(WORD mask, MOBLK *m1, MOBLK *m2, long time);

mask : Ein Bitfeld, das die gewünschten Events enthält (wie bei event-muWo. Mögliche Events: MU_KEYBD, MU_BUTTON, MU_M 1, MU_M2, MU_MESAG, MU_TIMER

ml, m2 : Die sensitiven Mausrechtecke sowie die Richtung (rein oder raus)

time : Die Zeit für den Timerevent (in Millisekunden). In Event-CPX-Modulen werden über diese Funktion die gewünschten Events eingestellt.

cpx_draw()

	void cdecl cpx_draw(GRECT *clip);

clip : Zeiger auf eine GRECT-Struktur mit den Koordinaten des neu zu zeichnenden Bereichs. Wird bei jedem REDRAW-Event aufgerufen und muß in jedem Event- CPX vorhanden sein.

cpx_wmove()

	void cdecl cpx_wmove(GRECT *work);

work : Zeiger auf eine GRECT-Struktur mit den neuen Koordinaten des XControl-Fensters Wird aufgerufen, wenn das XControl-Fenster bewegt wurde und muß in jedem Event-CPX vorhanden sein.

cpx_close()

	void cdecl cpx_close(WORD flag);

flag : Wenn Flag FALSE ist, ist eine AC_CLOSE-, bei TRUE eine WM_CLOSED-Mitteilung eingetroffen. Wird bei WM_CLOSED und AC_CLOSE aufgerufen und muß in jedem Event-CPX vorhanden sein. WM_CLOSED ist als »OK« und AC_CLOSE als »Abbruch« zu behandeln.

cpx_timer()

	void cdecl cpx_timer(WORD *quit);

quit : Zeiger auf ein WORD, wird dieses Flag auf TRUE gesetzt beendetXControi das laufende Modul Diese Funktion wird von XControl bei jedem TIMER-Event aufgerufen.

cpx-key()

	void cdecl cpx_key(WORD kstate, WORD key, WORD *quit);

kste : Bitfeld mit dem Status der Sondertasten (Shift, Control, Altemate usw.)

key : Im Highbyte steht der Scancode der gedrückten Tasten, im Lowbyte der ASCII-Code (wenn vorhanden)

quit : Zeiger auf ein WORD, wird dieses Flag auf TRUE gesetzt beendet XControl das laufende Modul. Diese Funktion wird von XControl bei jedem KEYBOARD-Event aufgerufen.

cpx_button()

	void cdeci cpx_button(MRETS *mrets, WORD nclicks,WORD *quit);

mrets : Zeiger auf eine MRETS-Struktur mit den aktuellen Koordinaten der Maus

nclicks : Anzahl der Mausklicks

quit : Zeiger auf WORD; wird dieses Flag auf TRUE gesetzt, beendet XControl das laufende Modul. Diese Funktion wird von XControl bei jedem BUTTON-Evont aufgerufen.

cpx_m1() / cpx_m2()

	void cdecl cpx_m1 (MRETS *mrets, WORD *quit);
	void cdecl cpx_m2 (MRETS *mrets, WORD *quit);

mrets : Zeiger auf eine MRETS-Struktur mit den aktuellen Koordinaten der Maus

quit : Zeiger auf WORD, wird dieses Flag auf TRUE gesetzt, beendet XControl das laufende Modul. Diese Funktion wird von XControl bei jedem MOUSE-Event aufgerufen.

cpx_hook()

	WORD cdecl cpx_hook(WORD event, WORD *msg, MRETS *mrets, WORD *key, WORD *nclicks);

event : Der aufgetretene Event (siehe evnt_multi)

msg : Zeiger auf einen Nachrichtenbuffer (siehe evnt_mesag).

mrets : Zeiger auf eine MRETS-Struktur mit den aktuellen Koordinaten der Maus

key : Im Highbyte steht der Scancode der gedrückten Tasten, im Lowbyte der ASCII-Code (wenn vorhanden)

nclicks : Anzahl der Mausklicks

--> TRUE wenn die Bearbeitung des Events von XControl fortgesetzt werden soll, ansonsten FALSE.

ACHTUNG!! Wann FALSE zurückgegeben wird, kann es zu Problemen kommen (eventuell ein Atari-Bug?). Diese Funktion ist ein Sonderfall! Sie wird bei jedem Event aufgerufen, noch bevor XControl diesen Event bearbeitet hat.


Richard Kurz
Aus: TOS 07 / 1992, Seite 98

Links

Copyright-Bestimmungen: siehe Über diese Seite