Und ggf. gibt es hier noch einen weiteren Textabschnitt, der im mobilen Bereich aber nicht zu sehen ist.
In einigen der vorherigen Posts ging es um die Verwendung von GUI- Elementen für die C++- Standard- Streams. Neben dem Memofeld waren das auch Labels und Editfelder. Und für die VCL auch die Statuszeile. Damit lassen sich sicher schon einige interessante Anwendungen zusammenbauen, aber ich möchte jetzt noch einen Schritt weiter gehen, und ein ListView- Steuerelement verwenden. Dieses gibt es in der Form nur unter Windows, und damit auch nur für die VCL. Für FMX könnte man einen ähnlichen Wrapper für ein StringGrid implementieren, und damit die Funktion eines ListViews nachbilden. Aber das originale Steuerelement unter Windows nicht zu verwenden wäre sicherlich dumm. Kompatibilität ist nicht Verzicht sondern das Suchen geeigneter Abstraktionen. Eigentlich sollten die bisherigen Posts schon gezeigt haben, welche mächtigen Abstraktionen uns als C++- Programmierer zur Verfügung stehen, und das Verzicht auf Möglichkeiten sicher nicht zu diesen gehört.
Aber zurück zu eine Implemetierung zur Nutzung eines Listview, ich möchte hier einfach ein zusätzliches Trennzeichen definieren, um von einer Listenspalte zur nächsten zu wechseln. Das kann ein Tabulator sein, aber auch ein ";"- Zeichen wäre denkbar. Beginnen wir und schauen uns das folgende Listing an. Auch hier wieder eine bedingte Übersetzung, da dieses nur bei Nutzung der VCL verfügbar sein kann. Auch wenn ich später eine eigene Implementierung für die overflow() Methode machen muss, nutze ich meine im Posting Weitere GUI- Elemente und C++- Streams eingeführte Basisklass "MyStreamBufBase". Ich kann also den in dieser definierten std::ostringstream "os" verwenden, muss aber auch die pure virtuelle Methode Write() implementieren.
#ifdef BUILD_WITH_VCL
class MyListViewStreamBuf : public MyStreamBufBase {
private:
TListView* lvValue;
TListItem* lvItem;
bool boNewItem;
public:
MyListViewStreamBuf(TListView* para, bool boClean = true) : MyStreamBufBase() {
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 int overflow(int c) {
if(c == '\n') {
Write();
//lvItem->MakeVisible(false);
boNewItem = true;
}
else {
if(c == '\t') {
Write();
}
else {
os.put(c);
}
}
return c;
}
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;
}
};
#endif
Zusätzliche benötigen wir drei Datenelemente. Das ist ein Zeiger auf TListView "lvValue", dieses entspricht dem gesamten TListView in der Anzeige (also der Tabelle mit Überschriften und Daten) und wird im Konstruktor als Parameter übergeben. Dazu ein Zeiger auf einen TListItem "lvItem", dieses entspricht einer Zeile innerhalb der Tabelle, sowie das boolsche Flag "boNewItem", das angibt ob sich der Stream gerade am Anfang einer neuen Zeile befindet, oder mitten in dieser.
Im Konstruktor wird das entsprechende konkrete TListView- Element als Zeiger übernommen, außerdem der Parameter "boClean", der, wie in den anderen Implementierungen schon gesehen, bestimmt, ob der bisherige Inhalt gelöscht werden soll. Hier wird auch das Element "lvItem" auf 0 gesetzt, und das boolsche Flag "boNewItem" auf true, um den kontrollierten Anfangszustand herzustellen. Der Sinn von Konstruktoren ist es ja, den inneren Zustand einer Instanz zu gewährleisten, deshalb stelle ich mit den Eigenschaften des TListView ViewStyle = vsReport und RowSelect = true einen definierten Ausgangszustand für meine Tabellen her (Anzeige als Tabelle, immer die ganze Zeile auswählen, Caption nicht editierbar). Sollte der 2. Parameter mit true übergeben werden, werden die bisherigen, eventuell vorhandenen Datenzeilen gelöscht.
Der Destrukor ist schnell fertig, hier werden die beiden Zeiger "lvValue" und "lvItem" auf 0 gesetzt. Generell gilt hier wieder eine Kompatibilität zu C++98, anderenfalls sollte besser nullptr verwendet werden. Das Datenelement bleibt Bestandteil der VCL- Oberfläche, und darf nicht gelöscht werden.
In der Methode overflow() muss ich die speziellen Zeichen heraussuchen. Neben dem Zeichen '\n' für den Zeilenwechsel ist dieses hier das Zeichen '\t" für den Spaltenwechsel. Ich habe hier zwei einfache Auswahl- Befehle (if) hintereinander geschachtelt, denkbar wäre hier auch alternativ eine Mehrfachauswahl (switch). Dank der aktuellen Entwicklungen in der Programmierung gibt es heute aber elegantere Lösungen, ich werde genau dieses Thema in einem späteren Post noch einmal aufgreifen. Aber zurück zum Thema.
switch(c) {
case '\n':
Write();
boNewItem = true;
break;
case '\t':
Write();
break;
default:
os.put(c);
}
Auch hier übernimmt die Write()- Methode die eigentliche Arbeit und schreibt die gepufferten Inhalte in die Tabelle. Nur der Zeilenumbruch wird durch das Setzen der Variable "boNewitem" auf true auch in der overflow()- Methode durchgeführt.
Die eigentliche Arbeit übernimmt auch hier die Write()- Methode. Dabei ist auch das eigentlich keine Magie. Wenn das Kennzeichen "boNewItem" true ist, wird eine neues Item mit Hilfe des VCL- Steuerelements erzeugt (lvValue->Items->Add()) und der Inhalt aus dem Puffer in die Eigenschaft Caption geschrieben. Danach wird die Eigenschaft "boNewItem" auf false gestellt. Falls "boNewItem" aber schon false ist, wird an das Item mit der Methode lvItem->SubItems->Add() eine neue Spalte mit dem Inhalt des Puffers angehängt. In beiden Fällen wird anschließend der Inhalt des Puffers geleert und den Text für die nächste Spalte zu sammeln.
Abschließend ergänzen wir wieder eine passende Methode zur template- Spezialisierung in unser Klasse "TMyStreamWrapper".
#if defined BUILD_WITH_VCL
template<>
inline void TMyStreamWrapper::Activate<TListView>(TListView* element) {
Check();
old = str.rdbuf(new MyListViewStreamBuf(element));
}
#endif
Abschließend definiere ich mir innerhalb der bedingten Übersetzung für die VCL einen neuen Typ als tuple für die Spaltenbeschreibung. Dabei definiert das erste Element eine Spaltenüberschrift, das zweite die Ausrichtung (Achtung, hier Abhängigkeit zum VCL- Typ "TAlignment") und das dritte die Spaltenbreite. Dazu kommt eine Hilfsmethode AddColumns() zum Setzen der Spalten im Listview, dem ich neben dem Zeiger auf das konkrete Element vom Typ TListView einen vector mit eben diesen Spaltendefinitionen übergebe. Hier werden erst die eventuell vorhandenen Spalten gelöscht, bevor die neuen Spalten mit Hilfe des Vektors und der tuple aufgebaut werden.
using tplList = std::tuple<std::string, TAlignment, int>;
inline void AddColumns(TListView* lv, std::vector<tplList> const& captions) {
lv->Columns->Clear();
for(auto const& caption : captions) {
TListColumn* nc = lv->Columns->Add();
nc->Caption = std::get<0>(caption).c_str();
nc->Alignment = std::get<1>(caption);
nc->Width = std::get<2>(caption);
}
}
Damit sind die Vorbereitungen abgeschlossen. Schauen wir uns dafür folgenden Testprogramm an. Mit Hilfe der VCL und der IDE habe ich folgendes Hauptfenster zusammengestellt. Es besteht aus 2 Panels, dem ListView- Element und einem Memofeld, der Statuszeile und dem Schalter. Zwischen dem ListView und dem Memofeld befindet sich ein Splitter, alle drei sind zusammen auf dem 2. Panel angeordnet. Auf dem ersten Panel befindet sich ein Schalter zum Ausführen der Aktion. Für das Memofeld habe ich die Schriftart auf "Courier New" gesetzt, eine Schriftart mit fixem Zeichenabstand. Das Listview ist einfach nur abgelegt, mit dem Align = alClient.
Das folgende Listing zeigt das Testprogramm. Hier werden cout auf das Listview, cerr auf das Memofeld und clog auf die Statuszeile umgelegt. In der FormCreate()- Methode werden die Standard- Streams umgelenkt, einige Einstellungen für die Streams vorgenommen, und die Listenspalten in der Tabelle aufgebaut. Dabei nutze ich die neue C++11 Initialisierungssyntax. Außerdem definiere ich für die Lokalisierung der Streams eine Hilfsstruktur "TMyNum", die von numpunct<char> abgeleitet ist und für die Ausgabesteuerung genutzt wird.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MyStreamBuf.h"
#include "MainFormListVCL.h"
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
TMyStreamWrapper old_cout(cout), old_cerr(cerr), old_clog(clog);
//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner) { }
//---------------------------------------------------------------------------
struct TMyNum : public numpunct<char> {
char do_decimal_point () const { return ','; }
char do_thousands_sep () const { return '.'; }
string_type do_truename () const { return "ja"; }
string_type do_falsename () const { return "nein"; }
std::string do_grouping () const { return "\3"; }
};
TMyNum newNumPunct;
void __fastcall TfrmMain::FormCreate(TObject *Sender) {
AddColumns(ListView1, {
tplList { "Nummer", taRightJustify, 100 },
tplList { "Anrede", taLeftJustify, 120 },
tplList { "Vorname", taLeftJustify, 220 },
tplList { "Name", taLeftJustify, 220 },
tplList { "Umsatz", taRightJustify, 170 },
tplList { "Prov.-Satz", taCenter, 120 },
tplList { "Provision", taRightJustify, 170 } } );
old_cout.Activate(ListView1);
old_cerr.Activate(Memo1);
old_clog.Activate(StatusBar1);
locale loc(locale("de_DE"), &newNumPunct);
cout.imbue(loc);
cout << setiosflags(ios::fixed);
cerr.imbue(loc);
cerr << setiosflags(ios::fixed);
clog << "Programm bereit." << endl;
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnActionClick(TObject *Sender) {
using tplData = tuple<string, string, string, double, double>;
char tab = '\t';
vector<tplData> datas = {
tplData { "Herr", "Gustav", "Meyer", 0.20, 223134.50 },
tplData { "Herr", "Bernd", "Karenbauer", 0.15, 718452.00 },
tplData { "Frau", "Lena", "Wagner", 0.25, 526784.50 },
tplData { "Herr", "Hans", "Holle", 0.20, 235120.50 },
tplData { "Frau", "Conny", "Lehmann", 0.30, 128720.00 },
tplData { "Herr", "Jens", "Xanten", 0.15, 925721.50 },
tplData { "Frau", "Anne", "Schmidt", 0.20, 130312.00 },
tplData { "Frau", "Maria", "Lohmen", 0.10, 1245372.00 } };
double flSales = 0.0, flShares = 0.0;
int iCount = 0;
for(auto data : datas) {
double flShare = get<3>(data) * get<4>(data);
flSales += get<4>(data);
flShares += flShare;
cout << ++iCount << tab << get<0>(data) << tab
<< get<1>(data) << tab << get<2>(data) << tab
<< setprecision(2) << get<4>(data) << tab
<< setprecision(0) << get<3>(data) * 100 << "%" << tab
<< setprecision(2) << flShare << endl;
}
clog << "Daten ausgegeben." << endl;
cerr << "Ingesamt " << iCount << (iCount != 1 ? " Sätze" : " Satz")
<< " ausgegeben" << endl
<< setw(15) << left << "Umsatz: "
<< setw(15) << right << setprecision(2) << flSales << endl
<< setw(15) << left << "Provision:"
<< setw(15) << right << setprecision(2) << flShares << endl;
}
Die Methode btnActionClick() ist das eigentliche Testprogramm. Dieses besteht wieder zu 100% aus Standard C++, ist als mit jedem entsprechenden C++- Compiler auf jeder denkbaren Plattform übersetzbar. Darin liegt die eigentliche Qualität eines Softwarearchitekten, geeignete Abstraktionen zu entwickeln, um später wiederverwendbare und portablen Code zu schreiben. Das kann von keinem Framework geleistet werden.
Auch in diesem Code bediene ich mich der Einfachheit halber eines Vektors mit tuple, die die Daten enthalten, die in der Tabelle angezeigt und ausgewertet werden sollen. Dabei wird das im deutschen übliche Komma und der Tausender- Punkt verwendet. Im Stream cerr erfolgt die Zusammenfassung, der Stream clog dient zum loggen der Informationen und cout für die Ausgabe der Werte. Das folgende Bild zeigt die erfolgreiche Ausgabe.
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