Donnerstag, 10. Mai 2018

Weitere GUI- Elemente und C++- Streams

Weitere GUI- Elemente und C++- Streams

Wie im letzten Post schon angedeutet, geht es heute um weitere GUI- Elemente, die ich als Ausgabemedium für Standard- Streams verwenden möchte. Dabei sind der Phantasie keine Grenzen gesetzt,sie können Edit- Felder,  Label, die Statuszeile und die Überschrift des Formulars verwenden, aber auch so komplexe Oberflächenelemente wie ein VCL- ListView- Element für Windows. Dabei können die Standard- Streams cout, clog und cerr verwendet werden, oder sie definieren einen eigenen Stream den sie dann mit den normalen Stream- Operatoren verwenden können.


Um das vorzubereiten, nutze ich jetzt einmal die Objektorientierung und hier eine neue abstrakte Klasse für meine Umsetzung. Gerade hier liegt der entscheidene konzeptionelle Vorteil von C++. Im Gegensatz zu Java und damit letztlich auch C# war und ist C++ nie als reine Sprache definiert worden. Während der 90iger wurde sich aus dem dortigen Lager oft über den hypriden Charakter von C++ ausgelassen. Heute werden mehr recht als schlecht Eigenschaften der Metaprogrammierung und der funktionalen Entwicklung in die einst reinen Sprachen ergänzt. C++ war und ist als Multiparadigmen- Sprache entworfen wurden, unterstützt damit nicht nur eine Reihe von verschiedenen Programmierparadigmen, sondern ermöglicht ein nahtloses Zusammenspiel. So haben wir in den bisherigen Post nicht nur globale Variablen verwendet, wir haben die Vorteile von templates und deren Spezialisierung verwendet. Nun eine abstrakte Klasse, und die Implementierung der Methode, in der die Ausgabe erfolgt zentral mit Hilfe dieser abstrakten Methode.

Beginnen wir mit der Vorbereitung und passen die bisherige Lösung aus dem Posting Verwendung von Standardstreams zur Ausgabe in Memofeldern an. Als erstes definiere ich mir eine neue Basisklasse für meine bestehende Klasse "MyMemoStreamBuf". Diese neue Klasse bekommt die bisherige Basisklasse std::streambuf. Ich verschiebe den als Puffer verwendeten ostringstream "os" in diese Klasse, die Sichtbarkeit definiere ich einfachheitshalber nur als geschützt (protected).. Der Konstruktor und Destruktor können leer bleiben.

Nun definiere ich eine pure virtuelle Methode Write(). Mit dieser kann ich die Implementierung der Methode Write() aus der Klasse "MyMemoStreamBuf" in diese Klasse verschieben, der Zugriff auf das eigentliche Datenelement "value" ersetze ich dafür durch den Aufruf der Methode Write(). Gerade der Zugriff auf dieses Datenelement wird sich von Steuerelement zu Steuerelement unterscheiden.

class MyStreamBufBase : public std::streambuf {
   protected:
     std::ostringstream os;
   public:
     MyStreamBufBase(void) { }
     virtual ~MyStreamBufBase(void) { }

     virtual void Write(void) = 0;

     int overflow(int c) {
       if(c == '\n') {
         Write();
         os.str("");
         }
       else {
         os.put(c);
         }
       return c;
       }

   };

Damit habe ich eine Basisklasse für die anderen Umlegungen und der Puffer steht allen zur Verfügung. Die Methode Write() übernimmt damit den eigentlichen Zugriff auf das Datenelement. Hier müssen wir natürlich unsere bisherige Klasse "MyMemoStreamBuff" auch anpassen. Um diese später instanzieren zu können, müssen wir die Methode Write() implementieren.

class MyMemoStreamBuf : public MyStreamBufBase {
   private:
     TMemo*             value;
   public:
     MyMemoStreamBuf(TMemo* para, bool boClean = true) : MyStreamBufBase() {
       value = para;
       if(boClean) value->Lines->Clear();
       }

     virtual ~MyMemoStreamBuf(void) { value = 0; }

     virtual void Write(void) {
       if(os.str().length() > 0) {
         value->Lines->Add(__ToVCL(os.str()));
         }
       else {
         value->Lines->Add(L"");
         }
       }
   };

Nun können wir weitere Klassen definieren. Dabei gibt es, wie beim Memofeld sehr einfache Varianten, wie zum Beispiel normale Eingabefelder, da sich ihr die beiden Implementierung wieder gleich verhalten. So sieht diese wie folgt aus.

class MyEditStreamBuf : public MyStreamBufBase {
   private:
     TEdit*             value;
   public:
     MyEditStreamBuf(TEdit* para, bool boClean = true) : MyStreamBufBase() {
       value = para;
       if(boClean) value->Text = L"";
       }

     virtual ~MyEditStreamBuf(void) { value = 0; }

     virtual void Write(void) {
       if(os.str().length() > 0) {
         value->Text = __ToVCL(os.str());
         }
       else {
         value->Text = L"";
         }
       }
   };


In der Implementierung nutze ich hier die Hilfsmethode __ToVCL() aus dem Post Konvertierung der Delphi Stringtypen. Entsprechend der Steuerung der Headerdateien ist eine weitere Unterscheidung hier nicht notwendig.

Anders ist es mit den Labeln. Hier gibt es einen bedauerlichen Unterschied. Während das Datenfeld mit dem anzuzeigenden Text in der VCL "Caption" heisst, ist es in FMX die Eigenschaft "Text". Aber auch hier hilft uns die bedingte Übersetzung, die wir ja in den bisherigen Beispielen auch schon genutzt haben.

class MyLabelStreamBuf : public MyStreamBufBase {
   private:
     TLabel* value;
   public:
     MyLabelStreamBuf(TLabel* para, bool boClean = true) : MyStreamBufBase() {
       value = para;
       if(boClean) {
         #ifdef BUILD_WITH_VCL
            value->Caption = L"";
         #else
            value->Text = L"";
         #endif
         }
       }

     virtual ~MyLabelStreamBuf(void) { value = 0; }

     void Write(void) {
       #ifdef BUILD_WITH_VCL
         value->Caption = os.str().c_str();
       #else
         value->Text = os.str().c_str();
       #endif
       return;
       }
   };

Nun gibt es auch Steuerelemente, für die eine Implementierung für ein bestimmtes Framework keinen Sinn macht. Während eine Statuszeile in der VCL komplex ist, und mit der Eigenschaft "SimplePanel" und "SimpleText" über direkte Beschriftung erfolgt, ist dieses Element in FMX nur ein Container, der wieder andere Elemente aufnimmt. So könnte man in diese ein Label ziehen und mit einem Align von "alClient" zu einem Simplepanel machen. Deshalb möchte ich nur für die VCL eine Implementierung der Statuszeile vornehmen. Auch hier wieder, wie gewohnt, die bedingte Übersetzung.

#ifdef BUILD_WITH_VCL
class MyStatusStreamBuf : public MyStreamBufBase {
   private:
     TStatusBar* value;
   public:
     MyStatusStreamBuf(TStatusBar* para, bool boClean = true) : MyStreamBufBase() {
       value = para;
       value->SimplePanel = true;
       if(boClean)
         value->SimpleText = "";
       }

     virtual ~MyStatusStreamBuf(void) { value = 0; }

     virtual void Write(void) {
       value->SimpleText = os.str().c_str();
       return;
       }
   };
#endif

Als letztes müssen wir für die neuen Klassen Activate() Methoden im Wrapper "TMyStreamWrapper" aus dem Post RAII beim Verwenden der stream- Neuzuordnungen die Implementierungen vornehmen. Hier der Auszug mit diesen.

#if defined BUILD_WITH_VCL || defined BUILD_WITH_FMX
template<>
inline void TMyStreamWrapper::Activate<TEdit>(TEdit* element) {
   Check();
   old = str.rdbuf(new MyEditStreamBuf(element));
   }

template<>
inline void TMyStreamWrapper::Activate<TLabel>(TLabel* element) {
   Check();
   old = str.rdbuf(new MyLabelStreamBuf(element));
   }
#endif

#if defined BUILD_WITH_VCL
template<>
inline void TMyStreamWrapper::Activate<TStatusBar>(TStatusBar* element) {
   Check();
   old = str.rdbuf(new MyStatusStreamBuf(element));
   }
#endif

Damit haben wir für weitere Steuerelemente Klassen bereitgestellt, und mit Ausnahme der Statuszeile für beide Frameworks bereitgestellt. So können wir jetzt clog für die Statuszeile oder die Überschrift verwenden, und neben cout auch cerr mit einem Memofeld verbinden.
;