Further GUI elements and C++ streams

additional part, not visible

 

 

As already mentioned in the last post, today I want to add more GUI elements that I want to use as output medium for standard streams. There are no limits to your imagination, you can use edit fields, labels, the status bar and the form header, but also such complex interface elements as a VCL ListView element for Windows. You can use the standard streams cout, clog and cerr, or you can define your own stream which you can then use with the normal stream operators.


To prepare this, I use the object orientation and here a new abstract class for my implementation. This is the main conceptual advantage of C++. In contrast to Java and thus ultimately also C#, C++ has never been defined as a pure language. During the 90s the local camp often talked about the hybrid character of C++. Today, more rather than bad features of metaprogramming and functional development are being added to the once pure languages. C++ was and is designed as a multiparadigm language, thus not only supporting a number of different programming paradigms, but also enabling seamless interaction. Thus, in previous post, we have not only used global variables, we have taken advantage of templates and their specialization. Now an abstract class, and the implementation of the method in which the output is done centrally using this abstract method.

Let's start with the preparation and adapt the previous solution from the post using standard streams for output in memo fields. First I define a new base class for my existing class "MyMemoStreamBuf". This new class gets the previous base class std::streambuf. I move the ostringstream "os" used as buffer into this class, the visibility I define only as protected for simplicity. The constructor and destructor can remain empty.

Now I define a pure virtual method Write(). With this method I can move the implementation of the method Write() from the class "MyMemoStreamBuf" into this class, the access to the actual data element "value" I replace by calling the method Write(). Especially the access to this data element will differ from control to control.


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;
       }

   };

Modifications of the previous implementation for VCL- TListView

So I have a base class for the other reallocations and the buffer is available to everyone. The method Write() thus takes over the actual access to the data element. Here we must of course adapt our previous class "MyMemoStreamBuff" as well. To be able to instantiate it later, we have to implement the method Write().


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"");
         }
       }
   };

Now we can define further classes. There are, like the memo field, very simple variants, like normal input fields, because the two implementations are the same again. So it looks like this.

 


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 the implementation I use here the help method __ToVCL() from the post conversion of the Delphi string types. According to the control of the header files a further differentiation is not necessary here.

It is different with the labels. Here there is a regrettable difference. While the data field with the text to be displayed is called "Caption" in the VCL, in FMX it is the property "Text". But also here the conditional translation helps us, which we have already used in the previous examples.
 


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;
       }
   };

Now there are also controls for which an implementation for a certain framework makes no sense. While a status line in the VCL is complex, and with the property "SimplePanel" and "SimpleText" is done via direct labeling, in FMX this element is just a container that takes other elements. So you could drag a label into this container and make it a Simplepanel by aligning "alClient". Therefore I want to implement the status line only for the VCL. Again, as usual, the conditional translation.


#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

Finally, we have to implement the new Activate() class methods in the wrapper "TMyStreamWrapper" from the post RAII when using the stream reassignments. Here the excerpt with these.


#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&lt;&gt;
inline void TMyStreamWrapper::Activate<TLabel>(TLabel* element) {
   Check();
   old = str.rdbuf(new MyLabelStreamBuf(element));
   }
#endif

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

With this we have provided classes for more controls, and with the exception of the status bar we have provided classes for both frameworks. So we can now use clog for the status bar or the header, and besides cout we can also connect cerr with a memo field.

Kommentar verfassen

* These fields are required

Kommentare

Keine Kommentare

About the Author

 


Über den Autor

Volker Hillmann

CEO, Developer, Germany, Berlin
Volker Hillmann was born in 1965 and holds a degree in mathematics with a focus on databases and data security. He has been programming in C since 1988. After first touches on a Unix machine with Turbo C 1.5 on PCs. That's how he got to know C++ in 1991 and since then he is programming in different areas with C++. After some experience in the insurance and banking industry, he founded adecc Systemhaus GmbH in 2000, of which he is still the CEO. He is also MVP at embarcadero Germany.

More posts by this author