Qt: QTabWidget Größe ändern und Tab-Inhalt aktualisieren



  • Und wieder mal Hallo,

    Ich stehe wieder vor 2 Problemen, die ich einfach nicht gelöst bekomme.

    Kurz die Erklärung des TabWidgets:
    Mein TabWidget hat 3 Tabs (Gebäude, Werft und Forschung (Ja, ich versuche ein kleines Spielchen zu bauen um Qt zu lernen 😉 )).
    Jeder Tab ist ein Widget und enthält eine ScrollArea, welche weitere Widgets in Form einer Liste enthält, die die Gebäude etc. mit den Werten usw. darstellen.
    Die Struktur ist also:

    QTabWidget (3 TabElemente)
        ||
        \/
    TabElement : QWidget
        ||
        \/
    QScrollArea
        ||
        \/
    Qwidget (Layout = VBoxLayout)
        ||
        \/
    ListenElement1 : QWidget (im VBoxLayout)
    ListenElement2 : Qwidget (im VBoxLayout)
        .
        .
        .
    

    Ich hoffe das ist verständlich 😃

    Das erste Problem betrifft die Weite des QTabWidgets und der ListenElemente.
    Die ScrollArea ist bei einen Tab immer nicht breit genug, dagegen ist sie in anderen TabElementen quasi zu breit.
    Das liegt daran, dass die ListenElemente in den verschiedenen Tabs unterschiedlich breit sind.
    Da ich aber die genaue Breite der ListenElemente am Anfang nicht kenne, möchte ich sie über eine Funktion alle auf die selbe breite setzen.
    Funktioniert nur leider nicht. Ich bekomme die größte Breite der 3 verschiedenen ListenElemente, setze alle anderen auf diese Breite und baue erst dann die TabElemente. Trotzdem sind nicht alle ListenElemente gleich breit...
    Muss ich die größte Breite ermitteln und dann alle ListenElemente neu erstellen und die Breite im Konstruktor übergeben?

    Problem Nr 2:

    Die Sichtbarkeit von bestimmten ListenElementen ist (natürlich) abhängig von der Ausbaustufe von z.B. Gebäuden oder Forschungen.
    Nach einem Funktionsaufruf soll die Sichtbarkeit vom TabElement geregelt werden, die Sichtbarkeit bleibt aber immer gleich. Die ListenElemente werden alle im Konstruktor als Pointer übergeben, dann dem VBoxLayout hinzugefügt und die aktuelle Sichtbarkeit gesetzt. Anschließend wird das VBoxLayout der ScrollArea übergeben.
    Später kann ich die Sichtbarkeit von einer TabElement-Funktion aus nicht ändern, vom Hauptfenster (das die ListenElemente in Form eines private vector besitzt) jedoch schon.
    Der Funktionsaufruf enthält natürlich pointer auf die ListenElemente.
    Im Debug-Modus kann ich sehen, das es sich um die richtigen ListenElemente handelt, nur die Sichtbarkeit kann eben nicht verändert werden. 😕

    Ich habe auch schon versucht, das Layout neu zu bauen, in dem ich aus dem VBoxLayout alle Widgets (also die ListenElemente) entfernt, dann neu hinzugefügt, die Sichtbarkeit gesetzt und anschließend das Layout neu gesetzt habe. Anschließend habe ich für alles im TabElement repaint() aufgerufen. Wieder das gleiche.

    Nur nach einem Neustart des Programms ist die Sichtbarkeit der ListenElemente korrekt.

    Ich hoffe, das ist halbwegs verständlich und ihr könnte mir weiterhelfen 😃

    Viele Grüße
    Cherup



  • So, für das Update-Problem habe ich eine Lösung gefunden.

    Ich lösche das entsprechende TabElement und baue es wieder neu.
    Ist nicht schön, aber funktioniert...

    Edit:
    Funktioniert doch nicht, beim ersten Durchlauf lief es, jetzt nicht mehr.
    Bekomme immer segmentation faults wenn das ListenElement dem neue Layout hinzugefügt wird oder wenn die Sichtbarkeit gesetzt wird.

    Es kann doch nicht sein, dass etwas so simples wie die Sichtbarkeit eines in ein Layout eingebetteten Widgets zu ändern einfach nicht funktioniert... 😡



  • Ich bin immer noch zu faul, das zu lesen. Schreib das alles bitte viel kürzer und mach am besten Screenshots.



  • Ich hab ein QTabWidget.
    Jeder Tab besteht aus einer QScrollArea.
    Die QScrollArea hat ein VBoxLayout als Layout.
    Das VboxLayout hat gleichartige Widgets.

    Jetzt möchte ich die Sichtbarkeit eines solchen Widgets ändern. Geht nicht.

    Kurz genug? 😃



  • Schon besser.
    Fehlt da nicht was? Die QScrollArea sollte kein Layout haben, sondern ein Widget. Das Widget kann dann ein Layout haben.
    Die Widgets, die wiederum in dem Layout stecken, kannst du problemlos sichtbar und unsichtbar machen.



  • Stimmt, hab ich vergessen zu schreiben.
    Grundsätzlich funktioniert das setVisible(), allerdings nur, wenn ich das Programm starte. Zur Laufzeit funktioniert es eben nicht, genau das ist mein Problem 😃 .

    Oder genauer: es funktioniert vom Hauptfenster aus (auch ein QWidget), aber nicht in einer Funktion in der Tab-Klasse. Da wird das einfach ignoriert...
    Ich müsste das aber von der Tab-Klasse aus machen, da zwischen den Widgets ein QFrame als Trennstrich liegt, der wiederum benötigt wird, weil die Groupbox nicht geht 😃



  • Zeig mal den Code, wahrscheinlich machst du was falsch.



  • ok,

    Die "ResearchListItems" sind die Zeiger auf die Widgets, bei denen die Sichtbarkeit geändert werden soll.
    Tab-Klasse (cpp):

    TabElementResearch::TabElementResearch(vector<ResearchListItems*> ListItems, map<int,bool> ListIDAvailable, QWidget *parent) : QWidget(parent)
    {
    
        scaWidget = new QWidget();
    
        scaScrollArea = new QScrollArea;
        vblResearchList = new QVBoxLayout;
        vblWindowLayout = new QVBoxLayout;
    
        for(uint i=0; i<ListItems.size(); i++){
            SeperatorLine.push_back(new QFrame);
            SeperatorLine[i]->setFrameShape(QFrame::HLine);
            SeperatorLine[i]->setFrameShadow(QFrame::Sunken);
    
            vblResearchList->addWidget(ListItems[i]);
            vblResearchList->addWidget(SeperatorLine[i]);
    
            if(ListIDAvailable[i+1] == false){
                ListItems[i]->setVisible(false);
                SeperatorLine[i]->setVisible(false);
            }
            else{
                ListItems[i]->setVisible(true);
                SeperatorLine[i]->setVisible(true);
            }
        }
    
        scaWidget->setLayout(vblResearchList);
    
        scaScrollArea->setWidget(scaWidget);
    
        vblWindowLayout->addWidget(scaScrollArea);
    
        setLayout(vblWindowLayout);
    }
    
    TabElementResearch::~TabElementResearch(){}
    
    void TabElementResearch::changeVisible(vector<ResearchListItems*> ListItems, bool visibility){
        ListItems[0]->setVisible(visibility);
        SeperatorLine[0]->setVisible(visibility);
    }
    

    Der Aufruf vom Hauptfenster:

    void GUI_GameWindow::buildListItems(){
    //Hier werden die ListenElemente 
    //erstellt und in den privaten 
    //vector eingefügt 
        for(...){
            ResearchListItems* ListItem = new ResearchListItems(...);
            myResearchListItems.push_back(ListItem);
        }
        buildLayout();
    } 
    
    void GUI_GameWindow::buildLayout(){
    
        myTabElementResearch = new TabElementResearch(myResearchListItems, ListIDAvailable);
        myGUITabWidget->insertTab(2,myTabElementResearch, trUtf8("Forschung"));
    
        grlWindowLayout->addWidget(myGUITabWidget,2,2);
        setLayout(grlWindowLayout);
    
        QTimer::singleshot(1000, this, SLOT(Update()));
    }
    
    void GUI_GameWindow::Update(){
    
        if( [Bedingung für Sichtbarkeit]  == true ){
            myTabElementResearch->changeVisible(myResearchListItems, true);
        }
    }
    

    Ich habe den Code auf das nötigste gekürzt (hoffentlich nicht zuviel 🙄 ) und ihn teilweise der übersicht halber vereinfacht.



  • Und myResearchListItems ist eine Membervariable?

    Parameter sollte man nach Möglichkeit als const& oder & übergeben, und möglichst nie als Kopien. Macht keinen Sinn, den vector beim Aufruf zu kopieren (im Gegensatz zur Qt ist es bei der STL eine deep copy).

    Aber an sich seh ich grad nichts offensichtlich falsches (außer, das nicht ganz klar ist, wo die Liste der Widgets herkommt).



  • Moin,

    ja, die myResearchListItems sind eine Membervariable.
    Die Widgetliste wird in der Funktion buildListItems() erstellt (2.Codefenster ganz oben).

    Ich glaube, ich habe jetzt das genauere Problem gefunden.
    Ich habe mal getestet, ob ich die ListItems im "Gebäude"-Tab Sichtbar bzw. Unsichtbar machen kann, dass funktioniert problemlos.
    Nach einem weiteren schnellen Test habe ich festgestellt, dass sich die Sichtbarkeit nur ändern lässt, wenn sie im Konstruktor der Tab-Klasse auf (true) gesetzt wird.

    Ich werde das nochmal genauer austesten, aber ich denke, damit ist die Lösung gefunden, auch wenn es für mich seeeehr seltsam und irgendwie unlogisch ist... 😕

    Edit:
    OK, im Prinzip ein sehr logischer und doch blöder Fehler:
    das isVisible() ist natürlich false, wenn man sich in einem anderen Tab befindet.
    Da ich immer nur das isVisible() abgefragt und gesetzt habe und nicht darauf geachtet habe, in welchem Tab ich mich befinde erklärt es das Problem und das teilweise sehr merkwürdige Verhalten (gelegentliche Segmentations faults in funktionen, in die eigentlich nicht gesprungen werden soll etc) .
    Ich muss quasi ein zusätzliches Flag für die Sichtbarkeit setzen und bei einem Tab-change event abfragen und die sichtbarkeit dementsprechend ändern oder eben die Update-Funktion zur Sichtbarkeit erst aufrufen wenn das Tabchange-Event eintrifft... hoffe ich zumindest 😃
    Sowas muss man doch mal gesagt bekommen 😃
    Ich werde das mal umsetzen und dann hier posten obs die Lösung ist oder nicht.

    Viele Grüße
    Cherup



  • Ok,

    ich denke es ist gelöst 😃

    Ich nutze das currentChanged()-Signal des TabWidgets und leite es durch einen eigenen Slot. in dem Slot wird dann ein Timer-SingleShot ausgelöst, der nach 100ms ein weiteres Signal an einen zweiten Slot sendet. In dem zweiten Slot wird dann die entsprechende TabElement-funktion zur sichtbarkeit aufgerufen.
    Ist nicht schön aber funktioniert.

    Den Timer muss ich nutzen, damit erst der entsprechende Tab gezeigt wird. Vorher lässt sich die Sichtbarkeit nicht ändern...

    Das ListenElement sieht nur noch nicht schön aus, das nutzt die Höhe von 2 ListenElementen, aber das bekomm ich auch noch hin 😉



  • Es gibt noch die Möglichkeit, den Slot über eine Queued Connection zu verbinden, das sollte eigentlich auch funktionieren (ohne den Timer).
    Du solltest dich denke ich etwas besser mit dem Debugger vertraut machen. So bald man sich einigermaßen an Qt gewöhnt hat und versteht, wie das grundsätzlich funktioniert, kann man bei Problemen einfach reindebuggen und schauen was passiert oder nicht passiert.



  • Ich arbeite fast ausschließlich mit dem Debugger, der Rest ist größtenteils Hirn 😉
    Ohne Debugger weiß man ja nicht, wo ein Fehler herkommt. Ernsthaft, programmieren ohne zu debuggen ist doch bei allem, was nur etwas größer als "Hello World" ist, fast unmöglich 😃

    Ich bin noch neu in Qt, ist meine erste GUI damit und seit laaanger Zeit überhaupt wieder meine erste GUI.
    Ich bau die GUI ja um Qt zu lernen 🙂

    Bei mir fehlt einfach noch das Verständnis, das Wissen und die Gewöhnung.

    BTW, kannst du mir erklären, warum die Höhe eines ListenElements beim "zeichnen" geändert wird, obwohl ich die Höhe im Konstruktor mit "setFixedHight()" festgelegt habe (ernst gemeinte Frage, keine Kritik 🙂 )?

    Sowas wie den Hinweis auf die Queued Connection mag ich sehr gerne, ich wußte bis jetzt nicht, dass es sowas gibt und werde gleich mal danach googlen 🙂

    Viele Grüße
    Cherup



  • Cherup schrieb:

    BTW, kannst du mir erklären, warum die Höhe eines ListenElements beim "zeichnen" geändert wird, obwohl ich die Höhe im Konstruktor mit "setFixedHight()" festgelegt habe (ernst gemeinte Frage, keine Kritik 🙂 )?

    So spontan nicht. Aber vielleicht ist das was du siehst, auch nicht die Größe von dem Widget. Oder es ist das falsche Widget. Auf jeden Fall sollte Fixed Height schon ausgewertet werden.
    Wir haben uns so ein Debug Widget gebaut, das im laufenden Programm angezeigt werden kann. Mit dem kann man dann Widgets selektieren, sich den Widget Baum anschauen, Widget Eigenschaften usw. Ist recht praktisch zum Untersuchen, was man da überhaupt hat (kann schon mal passieren, dass etwas von jemandem geschrieben wurde, der seit fünf Jahren nicht mehr da ist), und in was für Containern das drinhängt und ob da irgendwelche komischen Werte gesetzt sind.



  • Ja, so ein Debug-Widget kann bei sowas sehr praktisch sein.
    In diesem Fall stammt alles von mir.

    Du hast Recht, die Höhe des Widgets ist korrekt, es wird nur in die Mitte von 2 Widgets gesetzt (eines davon nicht sichtbar). Das bekomm ich auch noch irgenwie hin...



  • Ooookay,

    und ich habe wieder ein Problem 😃

    Aus Erfahrung klug 😃 :

    Kurzform:
    Ich habe eine ScrollArea mit eine Liste von Qlabel -> Funktioniert.
    Die Liste der Label soll geändert werden -> Funktioniert nicht.

    Langform:
    Ich habe einen neuen Tab gebaut, in dem eine Liste von QLabels, getrennt von QFrame-Linien angezeit werden soll. Funktioniert grundsätzlich.
    Jetzt soll aber eine neue Liste von QLabels angezeigt werden, die ScrollArea bleibt aber leer. Die Listen sind unterschiedlich lang und werden erst zur Laufzeit gebaut, daher arbeite ich nicht mit setText().

    Die Label sind die richtigen, das habe ich schon überprüft.

    Hier der Code der "ChangeList"-Funktion:

    //Klasse:
    class TabElementLists : public QWidget{
    
        Q_OBJECT
    
    public:
        (...)
    
    private:
        QWidget* scaWidget;           //Widget für die ScrollArea
        QScrollArea *scaScrollArea;
        QVBoxLayout* vblListLayout;   //Layout für die Label und Trennlinien
        QVBoxLayout* vblWindowLayout; //Layout des Tabs
    
        map<int,map<int, QFrame*> > SeperatorLine;  //Speichert die Trennlinien
        map<int,map<int, QLabel*> > PlanetLabels;   //Speichert die Labels
    
        int myActualList;
    
        (...)
    };
    
    //ChangeList-Funktion
    void TabElementLists::ChangeList(int newList){
        //Aktuelle Widgets aus dem ListLayout entfernen
        for(auto it=LabelLists[myActualList].begin(); it!=LabelLists[myActualList].end(); ++it){
            vblListLayout->removeWidget(it->second);
            vblListLayout->removeWidget(SeperatorLine[myActualList][it->first]);
            //Die Label und Trennlinien sollen ja später wieder benutzt werden können
            it->second->setParent(NULL);
            SeperatorLine[myActualList][it->first]->setParent(NULL);
        }
    
        //neues Layout
        delete vblListLayout;
        vblListLayout = new QVBoxLayout;
    
        //Neue Label und Trennlinien in das neue Layout einfügen
        for(auto it=LabelLists[newList].begin(); it!=LabelLists[newList].end(); ++it){
            vblListLayout->addWidget(it->second);
            vblListLayout->addWidget(SeperatorLine[newList][it->first]);
        }
    
        //In dem Widget, das der ScrollArea gehört, das neue Layout setzen
        scaWidget->setLayout(vblListLayout);
    
        //Alles neu zeichnen
        scaWidget->repaint();
        scaScrollArea->repaint();
        this->repaint();
    }
    

    Habe ich irgendwas übersehen?
    Wie ich ja bereits bewiesen habe bin ich noch recht unwissend 😃

    Viele Grüße
    Cherup



  • Keine Ahnung, was LabelLists[newList], sieht man ja nicht. Ansonsten, reindebuggen 😉 Mir fällt grad nichts gravierend falsches auf.
    Die repaint Aufrufe brauchst sicher nicht.



  • Hab schon reindebugged, die Label stimmen auch, habs mit nem vector getestet, in den jeder neue labeltext gepusht wird. Es wird ja kein Fehler geworfen, der Mist wird nur nicht angezeigt 😞
    Naja, wenigstens ist es grundsätzlich korrekt. Langsam fange ich an, das QTabWidget zu hassen 😃

    Edit:
    Ich hab ne Lösung dafür gefunden, ich habe bei der Scrollarea das widgetSizeable auf true gesetzt. nur die optik gefällt mir noch nicht, aber immerhin seh ichs jetzt 😃



  • Du hast geschrieben, aus irgendeinem abstrusen Grund arbeitest du nicht mit setText, vielleicht sind die Labels einfach leer und werden nicht gezeichnet?



  • die Label sind korrekt.
    Ich arbeite nicht mit setText, weil ich die Anzahl der Label erst zur Laufzeit kenne. In einer Liste können es nur 5 sein, in der nächsten dann vielleicht schon 25, es gibt keine Obergrenze. Und zur Laufzeit werden noch weitere Listen eingefügt 😃


Log in to reply