additional paragraph, normally not visble
Some of the previous posts were about using GUI elements for the standard C++ streams. Besides the memo field, these were labels and edit fields. And for the VCL also the status line. I'm sure you can build some interesting applications with this, but I'd like to go a step further and use a ListView control. This control is only available for Windows, and therefore only for the VCL. For FMX you could implement a similar wrapper for a StringGrid, and thus emulate the function of a ListView. But not to use the original control under Windows would be stupid. Compatibility is not abandonment but searching for suitable abstractions. Actually, the previous posts should have already shown what powerful abstractions are available to us as C++ programmers, and the renunciation of possibilities is certainly not one of them.
But back to an implementation for using a listview, I just want to define an additional separator to switch from one list column to the next. This can be a tabulator, but also a ";"- character would be conceivable. Let's start and look at the following listing. Again a conditional translation, as this can only be available when using the VCL. Even if I will have to make my own implementation for the overflow() method later, I will use my base class "MyStreamBufBase", which was introduced in the posting Additional GUI elements and C++ streams. So I can use the std::ostringstream "os" defined in this, but I also have to implement the pure virtual method Write().
Link to Additional GUI elements and C++ streams if finished
#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
Additionally we need three data elements. This is a pointer to TListView "lvValue", this corresponds to the whole TListView in the display (i.e. the table with headings and data) and is passed as parameter in the constructor. Additionally a pointer to a TListItem "lvItem", this corresponds to a row within the table, and the boolean flag "boNewItem", which indicates whether the stream is currently at the beginning of a new row or in the middle of it.
In the constructor, the corresponding concrete TListView element is taken as a pointer, as well as the parameter "boClean", which, as seen in the other implementations, determines whether the previous content should be deleted. Here also the element "lvItem" is set to 0, and the boolean flag "boNewItem" to true, to create the controlled initial state. The purpose of constructors is to guarantee the inner state of an instance, so I use the properties of TListView ViewStyle = vsReport and RowSelect = true to create a defined initial state for my tables (display as table, always select the whole row, caption not editable). If the 2nd parameter is passed with true, the previous, possibly existing data rows will be deleted.
The destructor is quickly finished, here the two pointers "lvValue" and "lvItem" are set to 0. In general, this is compatible to C++98, otherwise it is better to use nullptr. The data element remains part of the VCL interface and must not be deleted.
In the method overflow() I have to find the special characters. Besides the character '\n' for the line break, this is the character '\t' for the column break. I have nested two simple selection commands (if) one behind the other, alternatively a multiple selection (switch) would be conceivable here. Thanks to the current developments in programming there are more elegant solutions today, I will take up exactly this topic again in a later post. But back to the topic.
switch(c) {
case '\n':
Write();
boNewItem = true;
break;
case '\t':
Write();
break;
default:
os.put(c);
}
Again, the Write() method does the actual work and writes the buffered contents to the table. Only the line break is done by setting the variable "boNewitem" to true also in the overflow() method.
The actual work is also done by the Write() method. This is not really magic. If the flag "boNewItem" is true, a new item is created with the help of the VCL control (lvValue->Items->Add()) and the content from the buffer is written into the property Caption. Afterwards the property "boNewItem" is set to false. But if "boNewItem" is already false, a new column with the content of the buffer is appended to the item with the method lvItem->SubItems->Add(). In both cases the content of the buffer is then emptied and the text for the next column is collected.
Finally we add again a suitable method for template specialization in our class "TMyStreamWrapper".
#if defined BUILD_WITH_VCL
template<>
inline void TMyStreamWrapper::Activate<TListView>(TListView* element) {
Check();
old = str.rdbuf(new MyListViewStreamBuf(element));
}
#endif
Finally, I define a new type as tuple for the column description within the conditional translation for the VCL. The first element defines a column heading, the second the alignment (attention, here dependence to the VCL type "TAlignment") and the third the column width. In addition, there is a helper method AddColumns() for setting the columns in the list view, to which I pass a vector with these column definitions next to the pointer to the concrete element of type TListView. Here, the possibly existing columns are first deleted before the new columns are built using the vector and the tuple.
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);
}
}
This concludes the preparations. Let us look at the following test program. With the help of the VCL and the IDE I have created the following main window. It consists of 2 panels, the ListView element and a memo field, the status bar and the button. Between the ListView and the memo field there is a splitter, all three are arranged together on the 2nd panel. On the first panel there is a button to execute the action. For the memo field I set the font to "Courier New", a font with fixed character spacing. The list view is simply stored, with the Align = alClient.
The following listing shows the test program. Here cout is mapped to the list view, cerr to the memo field and clog to the status line. In the FormCreate() method, the standard streams are redirected, some settings for the streams are made, and the list columns are built in the table. I use the new C++11 initialization syntax. I also define a helper structure "TMyNum" for the localization of the streams, which is derived from numpunct<char> and is used for output control.
//---------------------------------------------------------------------------
#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) { }
//---------------------------------------------------------------------------
// settings for german language
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;
}
The method btnActionClick() is the actual test program. This consists again of 100% of standard C++ and can be compiled with any appropriate C++ compiler on any conceivable platform. Therein lies the real quality of a software architect to develop suitable abstractions in order to write later reusable and portable code. This cannot be achieved by any framework.
Also in this code I use a vector with tuple for simplicity, which contains the data to be displayed and evaluated in the table. The usual German comma and the thousand point is used. In the stream cerr the summary is done, the stream clog is used to log the information and cout for the output of the values. The following picture shows the successful output.
Keine Kommentare
3 days
8 Participants
3 Days
8 Participants
In this seminar you will get a compact introduction to the latest version of C++Builder (currently 10.3.3). C++Builder is a cross-platform development tool that allows you to develop high-performance, native applications for the target platforms Windows (32bit, 64bit), Mac OS (32bit), iOS and Android.
5 Days
8 Participants
As the title of the course says, this course is about developing native applications for mobile devices (app) with C++Builder (currently 10.3.3).
2 Days
8 Participants
This course is designed for C++ developers who want to use the C++Builder and deepen their knowledge. In addition to using the components and the properties of the C++ language, it is especially important to combine both in a sensible way.
3 Days
8 Participants
This seminar is aimed at C++ programmers who want to extend access to databases in their applications. This seminar explains the main components of the C++ Builder for database access, but also shows how to integrate them into a clean application architecture.
3 Days
8 Participants
This seminar is aimed at C++ programmers who have gained experience with the database components of the C++ Builder and now want to develop scalable database applications. In this seminar the importance of ANSI C++ - interfaces between the business logic of an application and the database is shown.
3 Days
6 Participants
This course is aimed at software developers who want to learn the elements of the C programming language as a basis for a later entry into object-oriented programming with C++.
3 Days
8 Participants
This seminar is aimed at programmers who want to switch from C to C++. The most important extensions of the C++ language and the differences to C are discussed in detail.
3 Days
5 Participants
This seminar is aimed at programmers who want to switch from C to C++. The most important extensions of the C++ language and the differences to C are discussed in detail.
5 Days
8 Participants
We were often asked if we could do a training "with everything". A larger example, starting with an idea, developing an architecture from it, and then implementing it independently in C++. Just use the extensions without letting them determine our structure and architecture, and if possible, combine this with developing access for mobile applications.
5 Days
8 Participants
In this seminar you will learn which Open Source tools you can use for your daily work with the current C++ Builder 10.3.3. This includes solutions for documenting, metrics, code analysis and code management.
2 Days
6 Participants