Und ggf. gibt es hier noch einen weiteren Textabschnitt, der im mobilen Bereich aber nicht zu sehen ist.
Im letzten Post ging es um die Nutzung von ListView- Steuerelementen der VCL als Standard- Stream für C++. Nun gibt es im FMX- Framework keine Unterstützung für das Windows- Steuerelement und es braucht eine andere Lösung. Dieses kann ein StringGrid sein.
Bevor ich mit diesem beginne, sortiere ich die bisherige Lösung um. Ich habe den obersten Teil der Headerdatei "MyStreamBuf.h" noch einmal komplett übernommen, um einen Abgleich zu ermöglichen.
#ifndef MyStreamBuf
#define MyStreamBuf
#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
#error Für diese Anwendung muss eine Framework- Variante ausgewählt werden
#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;
Da die Listenspalten im bisherigen Ansatz mit Hilfe einen Tuples aufgebaut wurden, in dem der VCL- Aufzählungstyp TAlignment für die Spaltenausrichtung verwendet wurde, den es in FMX so nicht gibt, bzw. der da anders aufgebaut ist, definiere ich mir zuerst eine eigene Aufzählung EMyAlign mit möglichen Spaltenausrichtungen. Mit Hilfe von diesem definiere ich den Tuple- Typ tplList zur Definition von Listenspalten neu.
Da ein wichtiges Element der Implementierung in der overfflow()- Methode liegt, und hier neben dem Zeichen '\n' auch eine Behandlung des Zeichens '\t' notwendig ist, definiere ich eine neue Basisklasse MyListStreamBufBase für alle Implementierungen von Listendarstellungen. Hier wird die overflow()- Methode zentral umgesetzt.
Da die jeweilige Zeilenschaltung nicht zentral ist, wird eine neue pure virtuelle Methode NewLine() definiert, die in den jeweils konkreten Klassen implementiert werden muss.
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;
};
Nun müssen einige Änderungen der bestehenden Klasse und Hilfsmethoden durchgeführt werden. Als erstes muss Methode AddColumns für die ListViews neu implementiert werden.
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);
}
}
Dafür definiere ich eine statische Variable mit einem assoziativen Array, in dem ich jedem Wert aus meinem vorher definierten EMyAlign - Aufzählung einen dazu passenden Wert aus der VCL- Aufzählung TAlignment zuordne. Dieses nutze ich in der Schleife zum Zuweisen der neuen zur Framework- abhängigen Aufzählung zum konkreten Wert. So habe ich in einem einfachen Schritt die Abhängigkeit von einem konkreten Framework eliminiert.
Abschließend muss ich die bisherige Klasse TMyListViewStreamBuf für die VCL anpassen. Dafür wird die bisherige Basisklasse ausgetauscht, die bisherige Methode overflow() wird hier gestrichen und stattdessen wird der Zeilenvorschub durch die neue virtuelle Methode NewLine() durchgeführt, die hier implementiert wird.
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;
}
};
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;
}
};
Die Umsetzung erfolgt mit Hilfe des privaten Datenelements lvValue vom Typ eines Zeigers auf ein TStringGrid. Dazu kommt eine Ganzzahl mit der aktuellen Spaltennummer. Diese werden im Konstruktor als Parameter übergeben, und die Spaltennummer auf 0 gesetzt. Dabei bedeutet die 0 eine neue leere Zeile, der Wert zeigt also später immer die Anzahl der schon zugefügten Spalten in einer neuen Zeile. Außerdem gibt es eine Variable mit der aktuellen Anzahl der Zeilen, die für die Write()- Methode genutzt wird. Auch diese wird im Konstruktur mit 0 initialisiert. Dann folgen noch einige Einstellungen für das StringGrid, um ein einheitliches Aussehen als Liste zu gewährleisten. Da es nur um eine Ausgabe geht, wird das Stringgrid auf nur lesen gesetzt, außerdem wird immer die ganze Zeile ausgewählt, und auch bei nicht aktivem Fokus angezeigt. Außerdem verhindere ich die Verschiebung von Spalten zur Laufzeit, um eine fehlerhafte Ausgabe zu gewährleisten.
Im Destruktur wird der Zeiger auf das konkrete Datenelement auf 0 gesetzt, nicht nur um ein Löschen zu verhindern, sondern auch zur Vereinfachung eines Reviews. Jeder wird erkennen, dass hier keine Löschung erfolgen darf.
In der Methode NewLine() wird lediglich die Spaltennummer iColumn auf 0 gesetzt.
Die eigentliche Ausgabe in das konkrete StringGrid- Steuerelement erfolgt auch hier in der virtuellen Write()- Methode durchgeführt. Wenn die Spaltennummer iColumn == 0 ist, wird durch den Zugriff auf die Eigenschaft RowCount des Steuerelements eine neue Zeile eingefügt. Nachdem dieses passiert ist, wird es in der Varaible iRow abgelegt. Im zweiten Absatz erfolgt über die Cells- Eigenschaft die eigentliche Ausgabe. Mit dem Postfix Inkrement- Operator wird die Spaltennummer nach der Ausgabe erhöht. Anschließend wird der Buffer für die neue Spalte geleert.
Auch hier brauchen wir eine Hilfsmethode AddColumns() zum Aufbau der Spalten. Dabei wird ebenfalls ein konkretes Steuerelement als erster Parameter übergeben, gefolgt von einem Vektor mit unseren, durch Einführung der Aufzählung EMyAlign plattformunabhängigen Spaltendefinitionen.
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);
}
}
Leider verändert sich das FMX- Framework aktuell noch. So werden Eigenschaften verschoben, der Zugriff ist teilweise durch spezielle, der COM- Syntax folgenden Schnittstellen etwas gewöhnungsbedürftig. Dieses trifft in der aktuellen Version des C++Builder 10.2 leider die Spaltenausrichtung im Grid. Ich habe das einfach auskommentiert, da es zur Darstellung des Vorgehens nicht notwendig ist. Gerade diese ständigen Veränderungen in Frameworks sind ja zentrale Begründung für die Kapslung dieser Frameworks von ihrem Business- Quellcode und wird hier gezeigt.
Als letztes wird die spezielle Activate()- Methode für das template in der Klasse TMyStreamWrapper ergänzt.
#if defined BUILD_WITH_FMX
template<>
inline void TMyStreamWrapper::Activate<TStringGrid>(TStringGrid* element) {
Check();
old = str.rdbuf(new MyListViewStreamBuf(element));
}
#endif
Keine Kommentare
Dieses Seminar richtet sich primär an Einsteiger, die über Programmierkenntnisse, vorzugsweise in C++ verfügen und auf den C++Builder wechseln wollen. Es ist aber auch für Umsteiger von einer wesentlich älteren Version geeignet.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an alle Entwickler, die ihre bestehenden Anwendungen vom klassischen C++- Compiler zum neuen, auf Clang basierenden Compiler im C++- Builder umstellen wollen.
3 Tage
8 Teilnehmer
In diesem Seminar erhalten Sie eine kompakte Einführung in die jeweils aktuelle Version des C++Builder (aktuell 10.3.3). Der C++Builder ist ein plattformübergreifendes Entwicklungstool, mit der Sie performante, native Anwendungen für die Zielplattformen Windows (32bit, 64bit), Mac OS (32bit), iOS und Android entwickeln kann.
5 Tage
8 Teilnehmer
Wie der Titel der Schulung sagt, geht es hier um die Entwicklung von nativen Anwendungen für mobile Endgeräte (App) mit dem C++Builder (aktuell 10.3.3).
2 Tage
8 Teilnehmer
Dieser Kurs richtet sich an C++ Entwickler, die den C++Builder nutzen und ihre Kenntnisse vertiefen wollen. Dabei geht es neben der Verwendung der Komponenten und den Eigenschaften der Sprache C++, besonders auch darum, beides vernünftig zu verbinden.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an C++ Programmierer, die den Zugriff auf Datenbanken in ihren Anwendungen erweitern wollen. In diesem Seminar werden die wichtigsten Komponenten des C++ Builders für Datenbankzugriff erläutert, aber es wird auch gezeigt, wie sie diese in eine saubere Anwendungsarchitektur integrieren können.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an C++ Programmierer, die Erfahrungen mit dem den Datenbankkomponenten des C++ Builders gesammelt haben, und nun skalierbare Datenbankanwendungen entwickeln wollen. In diesem Seminar wird die Bedeutung von ANSI C++ - Schnittstellen zwischen der Businesslogik einer Anwendung und der Datenbank gezeigt.
3 Tage
6 Teilnehmer
Dieser Kurs richtet sich an Softwareentwickler, die die Elemente der Programmiersprache C als Basis für einen späteren Einstieg in die objektorientierte Programmierung mit C++ lernen wollen.
3 Tage
8 Teilnehmer
Dieses Seminar richtigt sich an die Programmierer, die von C nach C++ umsteigen wollen. Es werden die wichtigsten Erweiterungen der Sprache C++, und die Unterschiede zu C ausführlich besprochen.
3 Tage
5 Teilnehmer
Dieses Seminar richtigt sich an die Programmierer, die von C nach C++ umsteigen wollen. Es werden die wichtigsten Erweiterungen der Sprache C++, und die Unterschiede zu C ausführlich besprochen.
5 Tage
8 Teilnehmer
Wir wurden oft gefragt, ob wir eine Schulung "mit allem" machen könnten. Ein größeres Beispiel, beginnend mit einer Idee, daraus eine Architektur entwickeln, und dann unabhängig in C++ implementieren. Die Erweiterungen nur verwenden, ohne das diese unsere Struktur und Architektur bestimmen.Und wenn es geht, auch noch mit Entwicklung eines Zugriffs für mobile Anwendungen verbinden.
5 Tage
8 Teilnehmer
In diesem Seminar lernen Sie, welche Open Source- Tools Sie für Ihre tägliche Arbeit mit dem aktuellen C++ Builder 10.3.2 nutzen können. Dabei geht es um Lösungen zu den Themen Dokumentieren, Metriken, Codeanalyse und Codeverwaltung.
2 Tage
6 Teilnehmer