• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

C++ / Qt - QLabel dynamisch in Layout einfügen

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Hallo,

ich schreibe gerade an einer Anwendung in C++ mit QT und versuche krampfhaft dynamische Labels zu erzeugen und in eine Layoutform einzubinden...

Zur Zeit verwende ich ein fixes Array von QLabel Pointern um Speicher für die Elemente zu reservieren, allerdings bekomme ich trotz des Hinzufügens in die Form, keine Elemente angezeigt. Ich verwende in QtDesigner eine QVBoxLayout ("layoutSettings") für den Anfang, "ui" ist die Form in der das Layout eingebaut ist.

Folgenden Code verwende ich gerade:
[src=cpp]QLabel* settingLabels[100]; // Nicht schön aber meine Versuche mit QVector/QList <label*> sind ebenfalls gescheitert und haben nichts angezeigt oder sind abgestürzt...

// Etwas Code 'in between'...

for (int i = 0; i < 5; i++) {
settingLabels = new QLabel;
settingLabels[i+1] = new QLabel;

settingLabels->setText("Test");
settingLabels[i+1]->setText("ing");

ui->layoutSettings->addWidget(settingLabels);
ui->layoutSettings->addWidget(settingLabels[i+1]);
}[/src]


Als Resultat kommt nur ein leeres Layout, ich sehe weder in sich verschobene Texte noch sonst etwas. Es wird absolut nichts angezeigt. :unknown:

Kann mir jemand einen Tip geben was ich falsch mache?
 
Zuletzt bearbeitet:

drouwocu

Neu angemeldet

Registriert
27 Juli 2015
Beiträge
6
Der Code ist richtig, nur sollte das Layout schon vorher die Größe haben, die es für alle Objekte benötigt. Oder besser, du verpasst dem centralWidget (wenn dein Layout auf diesem liegt) ebenfalls ein Layout, dann wird die Größe des Fensters oder der Objekte automatisch angepasst.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
  • Thread Starter Thread Starter
  • #3
Also es scheint wirklich an der Größe der QLabel's bzw. des Layouts gelegen zu haben. Allerdings konnte ich trotz AdjustSize und BaseSize Parametern die Felder in dem mit MaximumContrains gesezten Layout nicht korrekt unterbringen, die Labels waren immer ineinandergeschoben. Vermutlich weil das Layout nicht weiter expandiert hat.

Jetzt habe ich es so gelöst das ich im Code ein QGridLayout in den besagten Tab eingefügt habe und dort die Felder mit obigen Code eingepflegt werden. Also SizeConstraint für das Layout habe ich dann "QLayout::SetMaximumSize" verwendet und das funktioniert. Ein scrollbare Liste für die Labels wäre zwar eleganter, aber für den Anfang ist die Lösung schon relativ praktikabel, zu mal das Layout mit dem Fenster expandiert, wie auch der Tab selbst in Breite und Höhe auf "Expanding" gestellt ist.

Vermutlich hätte ich es auch so ähnlich über das QVBoxLayout lösen können.

Eine Anmerkung noch zum obigen Code - der war so nicht ganz richtig, weil die Variable "i" immer nur um eins erhöht wird, daher würde das zweite Label immer überschrieben werden... ist mir auch erst heute beim davorsitzen aufgefallen... :)
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Vermutlich weil das Layout nicht weiter expandiert hat. [...]
Vermutlich hätte ich es auch so ähnlich über das QVBoxLayout lösen können.
Das Layout expandiert auch nur dann, wenn es selbst wieder Teil eines übergeordneten Layouts ist. Oder wenn es das Layout des centralWidget ist, denn das skaliert mit dem ganzen Fenster. So sollte es dann auch mit dem VBoxLayout funktionieren.

Btw:
Nein, das C-Array ist wirklich nicht schön. Wenn du ein stack-basiertes Array mit fester Größe haben willst, dann nimm std::array. Das ist im Wesentlichen ein dünner Wrapper um ein C-Array, aber ohne die unangenehme Eigenschaft, bei erstbester Gelegenheit implizit zum Pointer zu konvertieren.

Außerdem hast du ein potenzielles Speicherleck. Was machst du, wenn zwischen dem "new QLabel" und dem "addWidget()" eine Exception fliegt? Dann wird das frische Label nicht weggeräumt und leckt. Jede Ressource sollte sofort bei ihrer Erzeugung einen Owner haben, der sich ums Wegräumen kümmert. Das kann einer der std-Smartpointer sein oder ein Qt-Parent. Als Faustregel sollte C++-Code nirgendwo ein nacktes "new" enthalten. Bei dem Minibeispiel ist das zwar eher akademisch – "new" könnte std::bad_alloc werfen, aber dann ist eh schon alles zu spät ;) –, aber wenn die Komplexität steigt, garantiere ich dir bei nackten news Speicherlecks.

Solange du in deinem Beispiel die Liste der Widgets brauchst, lässt sich das Leckage-Problem nur entschärfen. Ich würde so umstellen:
[src=cpp]for (int i = 0; i < 5; i++) {
settingLabels = new QLabel;
ui->layoutSettings->addWidget(settingLabels);
settingLabels->setText("Test");

settingLabels[i+1] = new QLabel;
ui->layoutSettings->addWidget(settingLabels[i+1]);
settingLabels[i+1]->setText("ing");
}[/src]
So sind Allokation und Owner-Zuweisung maximal nah beieinander. Da Qt selbst keine Exceptions verwendet, ist der Code pragmatisch betrachtet gut genug. Bis auf "new" kann da praktisch keiner schmeißen … solange nicht später wieder Code zwischenrein rutscht. Und das könnte man mit einem Kommentar absichern.

Eine Anmerkung noch zum obigen Code - der war so nicht ganz richtig, weil die Variable "i" immer nur um eins erhöht wird, daher würde das zweite Label immer überschrieben werden... ist mir auch erst heute beim davorsitzen aufgefallen...
Noch eine Idee dazu, unter der Voraussetzung, dass du letztenlich alle 100 Label über diesen Weg erzeugen willst. Weiß nicht, ob das exakt auf deinen Anwendungsfall passt, aber es ist eine nette C-Style-vs-C++-Style-Demo. Die Labels kommen ja offenbar semantisch immer in Zweierpäckchen. Warum das nicht im Typsystem genau so abbilden?
[src=cpp]struct LabelPair {
QLabel* first;
QLabel* second;
}

std::array<LabelPair, 50> settingLabels;

for (auto& pair: settingLabels) {
pair.first = new QLabel;
ui->layoutSettings->addWidget(pair.first);
pair.first->setText("Test");

pair.second = new QLabel;
ui->layoutSettings->addWidget(pair.second);
pair.second->setText("ing");
}[/src]
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
  • Thread Starter Thread Starter
  • #5
Danke dir, das probiere ich gern am Montag aus. :T

Ich bin gerade schon froh das die Labels, übrigens ein Key/Value Pair - angezeigt worden sind... - und da ich mich mit C++ kaum auskenne und bisher nur auf Qt zurückgreifen mußte, war die Entwicklung natürlich sehr Qt-Class lastig und weniger C++ oder gar noch C (das was ich kenne wie structs) eingemischt.

Aber du hast schon den richtigen Riecher, C++ steht definitiv noch auf meiner Agenda, aber ich hätte trotzdem versucht Qt-Klassen vor Standard C++ zu verwenden. Todo für Montag, eine C++ Sprachreferenz ansurfen, bookmarken und konsultieren... ;)
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
http://cppreference.com
Zum schnellen Nachschauen ist das wahrscheinlich *die* beste Referenz. Und es gibt auch ein QCH-Paket zum Einbinden in die Qt-Creator-Hilfe. :)

Was dich auch interessieren könnte, weil du ja aus der C-Ecke kommst, ist Kate Gregorys Vortrag von der CppCon 2015:
Stop Teaching C
Als C++-Einsteiger mit C-Hintergrund kriegst du dort eine sehr feine Reihe von grundlegenden Unterschieden zwischen den Sprachen mit. V.a. darauf gemünzt, was in C einwandfrei sauberer Code ist und in C++ ein dickes No-No, weil es viel besser passende Alternativen gibt.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
  • Thread Starter Thread Starter
  • #7
Der von dir verlinkte Talk ist allerdings ziemlich aus der Perspektive wie man C++ vermittelt ohne in die Pitfalls von C zu kommen weil C++ das, was in C gemacht werden muß, schon in etwa macht.

Das Problem habe ich aber auch gerade wenn ich deinen Ansatz verwende, muß ich beim schließen der Anwendung darauf achten meinen Array/Vector zu löschen oder macht das C++ bzw. Qt für mich? :)

Also wenn die Variable im globalen Scope der Klasse ist, wird diese beim aufrufen des Dekonstruktors/beim schließen automatisch aufgeräumt. Bei C muß ich es machen aber ich weiß das es gemacht wird, diese Sicherheit hab ich derzeit bei der Verwendung von Qt bisher auf die Library gelegt und gehofft das die Bibliothek die ganze Arbeit für mich macht.

In dem Talk wird auch zum Teaching mit Vector anstatt von Array geraten, aber soweit ich die Erklärung verstanden habe sollte es wegen der Abstraktion von Pointern geschuldet sein.

Also wann immer man "new" aufruft, müsste ich dafür sorgen das ein "delete" stattfindet?
Oder übernimmt hier Qt das Speichermanagement so fern das Widget in der GUI ist?
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Also wann immer man "new" aufruft, müsste ich dafür sorgen das ein "delete" stattfindet?
Oder übernimmt hier Qt das Speichermanagement so fern das Widget in der GUI ist?
Ja: Ein new verpflichtet dich zum delete. Da gibt’s keine Ausnahme. Es kann dir nur evtl. ein schlaues Framework die Arbeit abnehmen. Dass kann z.B. der Parent-Mechanismus von QObject sein. In deinem Code wird mit jedem layout->addWidget() das Layout zum Parent (= Owner) des Labels und ist ab sofort dafür zuständig, das Label wieder wegzuräumen. Das Layout selbst hat auch wieder einen Parent (das zugeordnete QWidget), und so zieht sich das den kompletten Widgetbaum durch.

Qt ist in Sachen Ressourcenmanagement allerdings eher irreführend. Das liegt daran, dass Qt viel älter als C++11 ist, und damals war in C++ halt *nicht* alles besser. ;) Deswegen siehst du in Qt-Code recht viele Pointer. Vergiss die mal für einen Moment. C++-Ressourcenmanagement funktioniert so:

Der Konstruktor (Ctor) sollte Ressourcen anfordern, der Destruktor (Dtor) sollte sie wieder freigeben (Stichwort: RAII). Ctors und Dtors laufen automatisch am Anfang bzw. Ende eines Scopes auf dem Stack. Scope kann eine Funktion sein, oder ein namenloses {}-Paar innerhalb einer Funktion, oder der {}-Block einer for-Schleife oder if-Verzweigung.

Owning-Pointer[1] haben keinen Ctor/Dtor. Deshalb willst du die komplett vermeiden.

[1] Das ist ein wichtiger Unterschied. Raw-Pointer, an denen Ressourcenmanagement hängt (owning): böse(tm). Reine »zeigende« Raw-Pointer, die nur auf ein Objekt verweisen, ohne dass es ihnen gehört: gut(tm).

Beispiel:
[src=cpp]
void func() {
std::vector<SomeObject> v;
v.push_back(SomeObject());
// ... anderer hochwichtiger Code ...
} // Keine Pointer weit und breit, alles stirbt automatisch.
[/src]
Im Gegensatz zu:
[src=cpp]
void func2() {
std::vector<SomeObject*> v2;
v2.push_back(new SomeObject);
// ... anderer hochwichtiger Code ...
// Verdammt! Owning Pointer. Da räumt keiner automatisch auf. Also, naja, doch.
// Der vector räumt brav die Pointer-Variablen weg. Aber halt *nur* die, nicht
// die Objekte, auf die die Pointer zeigen.
for (SomeObject* ptr: v2)
{
delete ptr;
}
}
[/src]
Preisfrage: Was gibt dieses Programm aus und wo ist der Bug?
[src=cpp]
#include <iostream>
#include <memory>
#include <string>

using std::string;

struct MyObject
{
MyObject(const string& name): name_{name}
{
std::cout << "Constructing " << name_ << std::endl;
}

~MyObject()
{
std::cout << "Destructing " << name_ << std::endl;
}

private:
string name_;
};

int main()
{
std::cout << "Line " << __LINE__ << ": scope main() begin\n";
MyObject* o_rawptr = new MyObject{"o_rawptr"};

auto o_uniqueptr = std::unique_ptr<MyObject>(new MyObject{"o_uniqueptr"});
// Obige Zeile zur Verdeutlichung, was passiert. Normalerweise würde man das new
// auch noch wegabstrahieren und schreiben:
// auto o_uniqueptr = std::make_unique<MyObject>("o_uniqueptr");

{
std::cout << "Line " << __LINE__ << ": inner scope begin\n";
MyObject o_stack {"o_stack"};
std::cout << "Line " << __LINE__ << ": inner scope about to end\n";
}
std::cout << "Line " << __LINE__ << ": inner scope ended\n";
std::cout << "Line " << __LINE__ << ": scope main() about to end\n";
}
[/src]

Puh, das ist länger geworden als geplant. Das bestätigt Kate Gregory. Wenn man Pointer nicht komplett weglassen kann, ist das mit dem Ressourcenmanagement schnell reichlich kompliziert.
 

Anhänge

  • Preisfrage inkl. QMake-Projekt.zip
    802 Bytes · Aufrufe: 284

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
  • Thread Starter Thread Starter
  • #9
Also ich hab das mal getestet und bin zu folgendem Ergebnis gekommen:
Das MyObject o_rawptr verursacht ein Speicherleak und ohne den Zusatz eines expliziten "delete o_rawptr" wird auch dessen Dekonstruktor nicht aufgerufen, selbst wenn main beendet ist und das Objekt "out of scope" geht....

Aber den ganzen Code kann ich noch nicht 100% nachvollziehen um ehrlich zu sein, dafür fehlen mir die Kenntnisse über C++ und auch was genau das std::unique_ptr genau macht, das Keyword auto gibt ja soweit ich das aus dem Code lesen kann einen automatische Typ der Variable an. Auch wundert mich die Struct verwendung etwas, da es wie eine Klasse anmutet, weil eben die "Funktionen" des De- und Konstruktors vorhanden sind - auch wenn es laut Definition ein Objekt/Struktur ist... aber ich will dir jetzt nicht aufbäumen mir hier nen Crashkurs in C++ zu geben - ich nerve dann selbst mit Fragen wenn ich wirklich intensiv mit C++ anfangen sollte. ;)

Wie der Konstruktor und Dekonstruktor nun verarbeitet werden habe ich aber nun dank deines Beispiels begriffen :T
Auch leuchtet mir ein und über diese Erkenntnis bin ich relativ froh, das Qt die Objekte aufräumt die in der GUI liegen und ein Eltern-Element besitzen, danke für den Wink mit dem Zaunpfahl.

Jetzt macht es auch Sinn, warum du das erzeugen mit "new" und das "addWidget" so dicht beieinander gesetzt hast in deinem ersten Post. :)
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Genau so ist das mit dem o_rawptr. :)

Zwei Einheiten Crashkurs kann ich mir nicht verkneifen. Muss ne Sucht sein. :D

Es gibt in C++ genau einen Unterschied zwischen »struct« und »class«. Beim struct sind Member standardmäßig public, bei der class standardmäßig private. Ansonsten sind die beiden vollständig austauschbar. Es ist reine Konvention, dass »struct« eher für einfache Datencontainer und »class« eher für komplexere Klassen verwendet wird.

unique_ptr ist ein Smartpointer; einfach gesagt ein kleines Managerobjekt für einen Owning-Pointer, das dir das manuelle new und delete abnimmt. Ansonsten kannst du einen std::unique_ptr<MyType> im Wesentlichen genau so benutzen wie einen simplen MyType*.
 
Oben