III. Beispiel: Umgang mit mehreren Fenstern
Ziel des Beispiels:
- ein Programm, welches auf Knopfdruck ein leeres Fenster öffnet
- ein Programm, welches auf Knopfdruck ein leeres Fenster öffnet und auf Knopfdruck wieder schließt
- ein Programm, bei dem ein Wert über ein neues Fenster eingegeben wird
Ein Programm, welches auf Knopfdruck ein leeres Fenster öffnet |
- Die Schritte 1 bis 5 vorhergehender Beispiele wiederholen sich an dieser Stelle:
- FLUID starten.
- main()-Methode anlegen.
- Fenster als Hauptfenster des Programmes anlegen.
- Button ins Hauptfenster setzen und Callback-Methode mit "Button_CB" bezeichnen.
- Funktion "Button_CB(Fl_Widget*,void*)" anlegen.
- Als nächstes bauen wir eine Funktion, die einfach ein Fenster ausgibt. Dazu legt man über New -> Code -> Function/Method
eine neue Funktion an.
Die Funktion, die ich "NewWindow" nenne hat vorerst keine Eingabewerte, wichtig ist aber der Rückgabewert: ein Pointer
auf ein Fl_Window:
Fl_Window*
- Jetzt wird innerhalb der gerade anglegten NewWindow()-Funktion über New -> Group -> Window ein neues Fenster eingefügt. Dabei
spielt es jetzt im Moment noch keine Rolle, was das Fenster eigentlich für Eigenschaften hat.
- Als nächste wird noch innerhalb der NewWindow()-Funktion eine manuelle Rückgabe eingefügt, dies ist nötig, damit die Funktion
einen Pointer auf das eben geschaffene Fenster zurückliefern kann.
Um das zu realisieren, wird über New -> Code -> Code unterhalb des eben angelegten Fensters (C-Hierachie!) ein neues
Codefragment eingefügt. Dessen Befehlszeile ist sehr einfach und kurz:
return w;
Zur Erklärung: FLUID legt im späteren Quelltext immer beim Einbau eines Fenster einen Pointer mit dem Namen w an, sowohl
in der main()-Methode als auch bei allen anderen Fenstern. Und da unsere Funktion noch keine Rückgabeanweisung hat, muss diese nun
manuell eingefügt werden, dabei muss man die strikte Quelltext-Generierung von FLUID "missbrauchen".
- Damit ist die NewWindow()-Funktion fertig und wir können uns der Button_CB zuwenden. Diese hat noch immer den Funktionskopf
static void Button_CB(Fl_Widget*,void*)
,wie im Beispiel II-1.
Die Callback-Funktion des Buttons benötigt (wieder) ein Codefragment, welches über New -> Code -> Code eingefügt wird.
Die Befehlszeile lautet diesmal:
NewWindow()->show();
Zur Erklärung: NewWindow() ist klar, das ist der Funktionsname der zuvor angelegten Funktion. An dieser Stelle liegt jetzt
also irgendwo ein Pointer w, der auf ein fertiges Fenster zeigt. Mit der Klassenfunktion show() wird dieses nun angezeigt. So
setzt sich diese Codezeile zusammen.
- Zum Schluss noch: Speichern, Kompilieren und Ausführen.
Hinweis: Es ist egal, ob in der Hierachie erst die NewWindow()- und danach die Button_CB(...)-Funktion oder umgedreht.
Ein Programm, welches auf Knopfdruck ein leeres Fenster öffnet und auf Knopfdruck wieder schließt |
- Ich greife an dieser Stelle auf das erste Beispiel dieses Abschnittes zurück, weil dieses Beispiel nur eine Erweiterung ist.
In dieser Erweiterung soll es darum gehen, dass das Fenster, welches sich auf Knopfdruck öffnet, einen "Schließen"-Button
enthält.
- Dem Fenster in der NewWindow()-Funktion gibt man als erstes einen Pointernamen (Eigenschaftsfeld des Fensters: C++/Name),
bei mir im Beispiel SubWindow.
- Außerdem fügt man im Fenster in der Funktion NewWindow() einen Button ein, dessen Label ich im Beispiel einfach "Schließen"
nenne.
- Jetzt kommt die Callback-Funktion des eben angelegten "Schließen"-Buttons. An dieser Stelle nutze ich eine Verkürzung, die
sich vorallem bei sehr kurzen Callback-Funktionen eignet.
Anstatt wie bisher den Namen der Callback-Funktion in das Feld Callback einzutragen, trägt man zwei ganz normale Codezeilen ein:
SubWindow->hide();
delete SubWindow;
Zur Erklärung: Die allgemein übliche Methode ein FLTK-Fenster zu schließen funktioniert über die hide()-Funktion. Die
Referenzierung erfolgt durch den Pointernamen SubWindow, den unser Fenster (NICHT HAUPTFENSTER) trägt.
Der zweite Befehl löscht darüber hinaus das Fenster und räumt es aus dem Speicher.
- Zum Schluss noch Speichern, Kompilieren und Ausführen.
Ein Programm, welches Berechnungen am Kreis ausführt |
- Ziel dieses etwas komplexeren Programmes ist es, den Umfang und den Flächeninhalt eines Kreises zu berechnen. Im Gegensatz
zu den bisherigen Programmen soll der Radius in einem separaten Fenster eingegeben werden.
Da das etwas komplexer nun wird, ist hier eine Auflistung der Elemente, die im Laufe des Programms erstellt werden
müssen:
- Klassendefinitionen (iostream, string, sstream)
- "using namespace std;" (zur Vereinfachung)
- die Variable Pi als globale Variable mit konstantem Wert
- die Variablen für Radius, Umfang und Fläche als globale Variablen
- MakeString (eine Funktion, die einen Double-Wert in einen Const-Char*-Wert umwandelt, was die Ausgabe erleichert)
- RadiusInput (eine Funktion, die später das Eingabefenster des Radius anzeigt)
- Eingabefeld
- Button zum Übernehmen des Wertes
- RadiusInput_CB (eine Funktion, die den Callback aus der RadioInput-Funktion verwaltet, sprich die Berechnungen
ausführt)
- main()-Methode
- Ausgabefeld Radius
- Ausgabefeld Umfang
- Ausgabefeld Flächeninhalt
- Button, der das Radius-EIngabefeld (Funktion RadioInput) aufruft
- Als erstes benötigt man natürlich wieder eine main()-Methode und ein Hauptfenster, Label im Beispiel ist "Hauptfenster".
- Danach legt man die drei Ausgabefelder für den Radius, Umfang und den Flächeninhalt über New -> Text -> Output an.
Dabei muss man jeden Ausgabefeld einen Pointernamen unter "Name" in der Registrierkarte "C++" des Eigenschaftenmenüs
geben.
- Ausgebefeld Radius: Radius_output
- Ausgabefeld Umfang: Umfang_output
- Ausgabefeld Fläche: Flaeche_output
- Als nächstes muss im Hauptfenster noch der Button angelegt werden, der im fertigen Programm dann schließlich das Eingabefenster
öffnet.
Das Label des Buttons habe ich "Radius eingeben" genannt. Um die Callback-Funktion des Buttons kümmern wir uns später,
da hier wieder eine Verkürzung angewandt wird. -
- Nachdem man nun alle Elemente des Hauptfensters hat, wird es Zeit sich um die nötigen Variablen zu kümmern.
Man benötigt vier globale Variablen (radius, umfang, flaeche, pi) vom Typ Double. Dabei kann die Variable pi mittels
const double pi = 3.1416;
als Konstante definiert werden.
Alle anderen drei Variablen werden lediglich angelegt, ohne ihnen einen Wert zuzuweisen.
Die Variablen werden über New -> Code -> Declaration angelegt (bereits von Klasseneinbindung bekannt). Man braucht
für jede Variable eine einzlne Deklaration und die müssen alle außerhalb der main()-Methode stehen um global zu sein.
- Wir gehen zurück zur main()-Methode: Mittels New -> Code -> Code legt man ein Codefragment an. In diesem Fragment
werden die drei kreisbeschreibenden Variablen radius, umfang und flaeche auf 0 (Null) gesetzt. Dieses Codefragment wird in der
Programmhierachie als erstes Element der main()-Methode positioniert (also noch über dem Fenster).
-
- An dieser Stelle fügen wir in das Programm eine zusätzliche Funktion namens MakeString ein, die uns später die Arbeit der
Ausgabe erleichtern wird. Die Funktion hilft dabei eine Zahl (Typ Double) als Typ const char* zurückzuliefern, dieser Typ
wird für die Ausgabefelder benötigt.
Wieder legen wir über New -> Code -> Function/Method eine neue Funktion mit dem Namen "MakeString" an. Es gibt einen
Funktionsparameter "wert" vom Typ Double. Der Rückgabetyp ist const char*, in FLUID heißen die Zeilen dann:
- Der Rest der Funktion ist ledglich ein größeres Codefragment, welches, wie gehabt, über New -> Code -> Code angelegt
wird.
In das Codefragmennt kommt dann dieses Stück Quelltext:
string outstring;
stringstream outstream;
outstream<<wert;
outstring = outstream.str();
return outstring.c_str();
Ich habe an dieser Stelle bewusst die std-Präfixe weggelassen, weil ich später über eine Code-Declaration die Zeile
"using namespace std;" eintrage. So erspare ich mir, überlegen zu müssen, wann ich dieses std bei welchem Befehl setzen
muss.
- Als nächstes kümmere ich mich um das Eingabefeld. Dazu lege ich (wieder) über New -> Code -> Function/Method eine
Funktion an, diesmal mit dem Namen "RadiusInput". Die Funktion hat keine Parameter und als Rückgabetyp ein FLTK-Fenster,
"Fl_Window*".
- Danach wird innerhalb der Funktion ein Fenster angelegt. Im Gegensatz zum Hauptfenster wird diesmal im Eigenschaftsfeld
unter C++ in der Zeile Name ein Pointername eingetragen, in meinem Beispiel "Radius_Window". Dieser Pointer wird später zum
Verstecken des Fensters und Löschen aus dem Speicher benötigt.
- Anschließend brauchen wir in dem Fenster ein Eingabefeld, welches über New -> Text -> Input angelegt wird. An dieser
Stelle greifen wir auf ein angenehmes Feature der FLTK-Bibliothek zurück. Es besteht die Möglichkeit, dass man den "Typ des
Eingabefeldes" auf Float/Double stellt, so dass nur gültige Zahlen akzeptiert werden. Dies erspart einem die Fehlerbehandlung,
sollte der eingegebene Wert nicht dem Typ Double entsprechen.
Dieses Feature stellt man im Eigenschaften-Menü des Eingabefeldes innerhalb der Registrierkarte C++ ein, indem man im Dropdown-
Menü neben Class "Float" einstellt.
Darüber hinaus muss noch ein Pointer auf das Feld gesetzt werden, dieser heißt im Beispiel "Radius_input".
- Das Fenster braucht natürlich noch einen Button, der das ganze schließt und die Berechnungen in Gang setzt. Dieser trägt
im Beispiel das Label "Übernehmen". Die Callback-Funktion heißt "RadiusInput_CB".
- Zum Abschluss der RadiusInput-Funktion muss in einem Code-Fragment noch die return-Anweisung eingegeben werden, dieses lautet:
return w;
- Es folgt die Callback-Funktion des Buttons "Übernehmen" aus dem Radius-Eingabefeld. In dieser Funktion müssen folgende Dinge
abgehandelt werden.
- Beschaffung des Radius, Berechnung des Umfanges und des Flächeninhaltes
- Ausgabe von Radius, Umfang und Flächeninhalt
- Schließen des Eingabefensters und Zerstörung des Pointers, der darauf zeigt.
Diese Funktion erhält den für Button-Callback-Funktionen typischen Funktionskopf:
- Da es sich durchgängig um Berechnungen und Befehle handelt, werden lediglich Code-Fragmente benötigt. Ich erstelle an dieser
Stelle mehrere, um bei späterer Bearbeitung die Übersicht zu behalten, man könnte aber alle Anweisungen auch in ein Fragment
schreiben.
Im ersten Fragment hole ich mir den Radius und führe anschließend die Berechnungen aus.
Zur Beschaffung muss der Wert, auf den der Pointer Radius_input->value() zeigt, erst in einem Stringstream geschrieben werden,
bevor er der Variable radius zugewiesen werden kann.
istringstream sRadius (Radius_input->value());
sRadius>>radius;
umfang = 2*pi*radius;
flaeche = pi*radius*radius;
Es folgt im zweiten Fragment die Ausgabeanweisungen an die Ausgabefelder im Hauptfenster. An dieser Stelle wird ersichtlich,
warum sich das Anlegen der MakeString-Funktion gelohnt hat, denn sonst müsste man die String-Konvertierung für jedes
Parameter einzeln behandeln.
Radius_output->value(MakeString(radius));
Umfang_output->value(MakeString(umfang));
Flaeche_output->value(MakeString(flaeche));
Im letzten Code-Fragment wird das Eingabefenster erst versteckt und anschließend der Pointer aus dem Speicher gelöscht.
Ersteres geht über die Funktion "hide()", die zur Klasse Fl_Window gehört. Das Löschen erfolgt über den Befehl "delete".
Radius_Window->hide();
delete Radius_Window;
- Es fehlt noch die Callback-Funktion des Buttons "Radius eingeben" im Hauptfenster. Jetzt kommt die bereits vorher angekündigte
Verkürzung. Die Callback-Funktion besteht einzig und allein aus dem Befehl
RadiusInput()->show();
Es würde die Übersicht
in FLUID mindern, wenn man dafür eigens eine Funktion anlegt, deswegen wird diese Befehlszeile direkt in das Callback-Feld
im Eigenschaftenmenü eingetragen.
- Es müssen schlussendlich noch die Klassendefinition eingefügt werden. Es werden vier Code-Declaration angelegt und an der Spitze
des Programmes postiert. Die folgenden Klassen/Befehle müssen in die Deklarationen eingtragen werden:
- #include <string>
- #include <sstream>
- #include <iostream>
- using namespace std;
- Damit ist unser Programm fertig.