Lists in FMX (StringGrid)

Lists with FMX, using of StringGrids

additional part shouldn't visible

 

 

The last post was about using ListView controls of the VCL as the standard stream for C++. Now there is no support for the Windows control in the FMX- Framework and it needs another solution. This can be a StringGrid.

New type for column alignment

Before I start with this one, I rearrange the previous solution. I have taken over the uppermost part of the header file "MyStreamBuf.h" once again completely, in order to make a comparison possible.



#ifndef MyStreamBuf
#define MyStreamBuf

#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
   #error A framework variant must be selected for this application
#endif

#if defined BUILD_WITH_VCL
   #include <Vcl.StdCtrls.hpp>
#endif

#if defined BUILD_WITH_FMX
   #include <Fmx.StdCtrls.hpp>
   #include <FMX.Grid.hpp>
   #include <FMX.Grid.Style.hpp>
#endif

#include < MyDelphiHelper.h >

#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <tuple>
#include <map>

enum class EMyAlign : int { undefined, left, center, right, unknown };
using tplList = std::tuple;

Since the list columns in the previous approach were built using a tuple in which the VCL enumeration type TAlignment was used for column alignment, which is not available in FMX, or which is structured differently, I first define my own enumeration EMyAlign with possible column alignments. With the help of this I redefine the tuple type tplList for the definition of list columns.


Since an important element of the implementation lies in the overfflow() method, and since the character '\n' and the character '\t' must be handled here, I define a new base class MyListStreamBufBase for all implementations of list displays. Here the overflow() method is implemented centrally.

Since the respective line feed is not central, a new pure virtual method NewLine() is defined, which must be implemented in the respective concrete classes.

 


class MyListStreamBufBase : public MyStreamBufBase {
   public:
     MyListStreamBufBase(void) { }
     virtual ~MyListStreamBufBase(void) { }

     virtual int overflow(int c) {
       switch(c) {
         case '\n':
            Write();
            NewLine();
            break;
         case '\t':
            Write();
            break;
         default:
            os.put(c);
         }
       return c;
       }

     virtual void NewLine(void) = 0;
   };

Modifications of the previous implementation for VCL- TListView

Now some changes to the existing class and auxiliary methods have to be made. First of all, method AddColumns must be newly implemented for the ListViews.


inline void AddColumns(TListView* lv, std::vector<tplList> const& captions) {
   static std::map<EMyAlign, TAlignment> Align2 = {
                    { EMyAlign::undefined, taLeftJustify },
                    { EMyAlign::left,      taLeftJustify },
                    { EMyAlign::center,    taCenter },
                    { EMyAlign::right,     taRightJustify },
                    { EMyAlign::unknown,   taLeftJustify } };

   for(auto const& caption : captions) {
     TListColumn* nc = lv->Columns->Add();
     nc->Caption   = std::get<0>(caption).c_str();
     nc->Alignment = Align2[std::get<1>(caption)];
     nc->Width     = std::get<2>(caption);
     }

   }
  

To do this, I define a static variable with an associative array in which I assign a matching value from the VCL enumeration TAlignment to each value from my previously defined EMyAlign enumeration. I use this in the loop to assign the new framework dependent enumeration to the concrete value. So I eliminated the dependency on a concrete framework in a simple step. 

Finally I have to adapt the previous class TMyListViewStreamBuf for the VCL. For this the previous base class is exchanged, the previous method overflow() is deleted here and instead the line feed is performed by the new virtual method NewLine(), which is implemented here.
 


class MyListViewStreamBuf : public MyListStreamBufBase {
   private:
     TListView*  lvValue;
     TListItem*  lvItem;
     bool boNewItem;
   public:
     MyListViewStreamBuf(TListView* para, bool boClean = true) : MyListStreamBufBase() {
       lvValue = para;
       lvItem    = 0;
       if(boClean) lvValue->Items->Clear();
       lvValue->ViewStyle = vsReport;
       lvValue->RowSelect = true;

       boNewItem = true;
       }


     virtual ~MyListViewStreamBuf(void) {
       lvValue = 0;
       lvItem  = 0;
       }

     virtual void NewLine(void) { boNewItem = true; }

     virtual void Write(void) {
       if(boNewItem) {
         lvItem = lvValue->Items->Add();
         lvItem->Caption = os.str().c_str();
         boNewItem = false;
         }
       else {
         lvItem->SubItems->Add(os.str().c_str());
         }
       os.str("");
       return;
       }
   };

Implementation for FMX- StringGrid

Now I can take care of the implementation for StringGrids as output for C++ standard streams. For this purpose I add the following source code to the header file "MyStreamBuf.h" and use the conditional translation again.

Damit kann ich mich jetzt um die Implementierung für StringGrids als Ausgabe für C++ Standard- Streams kümmern. Dazu füge ich den folgenden Quelltext in die Headerdatei "MyStreamBuf.h" ein und nutze hier auch wieder die bedingte Übersetzung.

 


class MyListViewStreamBuf : public MyListStreamBufBase {
   private:
     TStringGrid*  lvValue;
     int iColumn, iRow;
   public:
     MyListViewStreamBuf(TStringGrid* para, bool boClean = true) : MyListStreamBufBase() {
       lvValue = para;
       iColumn    = 0;
       iRow       = 0;
       lvValue->ReadOnly = true;

       lvValue->Options <<= TGridOption::RowSelect;
       lvValue->Options >>>= TGridOption::ColumnMove;
       lvValue->Options <<<= TGridOption::AlwaysShowSelection;

       if(boClean) lvValue->RowCount = 0;
       }


     virtual ~MyListViewStreamBuf(void) {
       lvValue = 0;
       }

     virtual void NewLine(void) { iColumn = 0; }

     virtual void Write(void) {
       if(iColumn == 0) {
         lvValue->RowCount += 1;
         iRow = lvValue->RowCount - 1;
         }
       lvValue->Cells[iColumn++][iRow] = os.str().c_str();
       os.str("");
       return;
       }
   };

The conversion is done with the help of the private data element lvValue of the type of a pointer to a TStringGrid. In addition there is an integer with the current column number. These are passed as parameters in the constructor and the column number is set to 0. The 0 means a new empty row, so the value always shows the number of already added columns in a new row. There is also a variable with the current number of rows, which is used for the Write() method. This is also initialized with 0 in the structure. Then there are some settings for the StringGrid to ensure a uniform appearance as a list. Since it is only about output, the StringGrid is set to read only, and the whole row is always selected and displayed even if the focus is not active. I also prevent columns from being moved at runtime to ensure that output is not correct.

In the destructure, the pointer to the concrete data element is set to 0, not only to prevent deletion, but also to simplify a review. Everyone will recognize that no deletion is allowed here.

In the method NewLine() only the column number iColumn is set to 0.

The actual output to the concrete StringGrid control is also done here in the virtual Write() method. If the column number iColumn == 0, a new row is inserted by accessing the RowCount property of the control. After this is done, it is stored in the iRow variable. In the second paragraph, the actual output is done using the Cells property. The Postfix Increment Operator is used to increase the column number after the output. The buffer for the new column is then emptied. 

Here, too, we need an auxiliary method AddColumns() to build the columns. Here, too, a concrete control is passed as the first parameter, followed by a vector with our, by introducing the enumeration EMyAlign platform independent column definitions.

 


inline void AddColumns(TStringGrid* lv, std::vector<tplList> const& captions) {
   static std::map<EMyAlign, TTextAlign> Align2 = {
                    { EMyAlign::undefined, TTextAlign::Leading },
                    { EMyAlign::left,      TTextAlign::Leading },
                    { EMyAlign::center,    TTextAlign::Center },
                    { EMyAlign::right,     TTextAlign::Trailing },
                    { EMyAlign::unknown,   TTextAlign::Leading } };

   for(auto const& caption : captions) {
     TStringColumn* col = new TStringColumn(lv);
     //col->TextSettings->HorzAlign = Align2[std::get<1>(caption)];
     col->Header = std::get<0>(caption).c_str();
     col->Width  = std::get<2>(caption);
     lv->AddObject(col);
     }
   }

Unfortunately the FMX- Framework is still changing. Thus properties are moved, the access is partly by special interfaces following the COM syntax a little bit getting used to. Unfortunately, in the current version of C++Builder 10.3 this affects the column alignment in the grid. I have simply commented this out, since it is not necessary for the representation of the procedure. These constant changes in frameworks are the main reason for the encapsulation of these frameworks from their business source code and are shown here.


Finally, the special Activate()- method for the template in the class TMyStreamWrapper is added.


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

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