Seite 1 von 1

[gelöst] QScrollArea & QGroupBox (Automatische Höhe)

Verfasst: 21. Oktober 2010 11:33
von Pixtar
Hiho Leute,

ich scheiter gerade an dem versuch eine "Sidebar" für mein Programm zu bauen. Das Gerüst steht, aber es gibt noch ungereimtheiten. (Qt 4.6.2.)

Ich habe eine QScrollArea in mein Hauptlayout (HBox) gepackt. Um meine GroupBoxen in die Area zu schieben gibt es die Methode setLayout bei der Area. Diese habe ich genutzt und musste feststellen, dass sich das Layout in der Area immer an der Größe der Area orientiert und somit NIE ein Scrollbar auftaucht. Der Inhalt wird einfach gestaucht.

Kann ich das mit einer Methode umgehen/abschalten?

Kurzaufbau derzeit:
QWidget -> QHBoxLayout -> QScrollArea -> QGroupBox -> QVBoxLayout -> QGroupBox + QGroupBox ...

Ich habe für mein Work-A-Round nun anstatt das Layout zu setzen über setWidget der Area eine GroupBox hinzugefügt in der das Layout hängt in dem ich die anderen GB's platziert habe.

Jetzt taucht der Scrollbar in der Area auf und ich kann endlich scrollen. Haken bei der Sache ist, das die GB in der Area ihre Initialisierungsgröße beibehält.
Warum das so ist weiß ich nicht, es ist mir aufgefallen, weil alle GB's in dem Layout checkable und connected mit einer Funktion sind, die die Sender-GB auf 20pxl verkleinert.

Halt eine Sidebar wie man sie von vielen Programmen kennt (Inventor, 3DS Max, GIMP etc.)

Sprich, wenn ich all GB's verkleiner (uncheck), bleibt die äußere GroupBox bei der Initialisierungsgröße und es ist immernoch der Scrollbar der Area zu sehen - was allerdings nicht mehr nötig wäre.

Mein Quelltext zu dem Vorhaben schaut wie folgt aus:

Code: Alles auswählen

    ...
    gridLayer = new QHBoxLayout(this);
    scrollArea = new QScrollArea;
    scrollArea->setLayoutDirection(Qt::RightToLeft);
    scrollArea->setContentsMargins(0,0,0,0);
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea->setFixedWidth(220);
    scrollArea->setFrameShape(QFrame::NoFrame);

    scrollGrp = new QGroupBox;
    scrollGrp->setLayoutDirection(Qt::LeftToRight);
    scrollGrp->setFlat(true);
    scrollGrp->setContentsMargins(0,0,0,0);
    scrollGrp->setFixedWidth(200);

    scrollLayer = new QVBoxLayout();
    scrollLayer->addWidget(recordGrp,0,Qt::AlignTop);
    scrollLayer->addWidget(cameraGrp,0,Qt::AlignTop);
    scrollLayer->addWidget(optionGrp,0,Qt::AlignTop);
    scrollLayer->addWidget(scaleGrp,0,Qt::AlignTop);
    scrollLayer->addWidget(statisticGrp,0,Qt::AlignTop);

    scrollGrp->setLayout(scrollLayer);
    scrollArea->setWidget(scrollGrp);
    gridLayer->addWidget(scrollArea);
    ...
Wenn da jemand ne super Idee hat, welche Variablen ich noch setzen muss, das sich die Höhe der GB in der Area anhand der Größe des VBoxLayouts orientiert - OHNE mehrere Klassen ableiten zu müssen - wäre ich sehr dankbar.

Grüße,
Pixtar

NACHTRAG: Ich sehe gerade hier, hier und hier, dass mein Workaround schon okay ist. Das Problem ist aber, dass QScrollArea wohl noch nicht das ist, für das man es hält. Komme somit wohl ums Ableiten nicht herum. Andere Ideen sind trotzdem gern willkommen. :)

Verfasst: 21. Oktober 2010 13:49
von franzf
setWidget() ist die EINZIGE Art, eine QScrollArea zu verwenden. Ein ScrollArea hat einen viewport(). auf dem das eigentliche Zeichnen (oder auch Layouten eines Widgets) stattfindet. Also kein Workaround.
Und dein Codefragment enthält (mir momentan) zu viel unnötiges Rauschen, essentielles (wie resizen, Policies, ...) enthält es nicht.

Bitte erstelle ein miminales, kompilierbares Beispiel, das dein Problem verdeutlicht. Halte dich dabei an deine bisherigen Versuche. Reduziere die Anforderungen auf ein Minimum (also keine großen eigenen Widget-Klassen, die kompliziert aus Dateien Daten extrahieren - verwende einfache, statische QLabels).

Verfasst: 21. Oktober 2010 23:31
von Pixtar
Okay, hier mal ein einfaches Beispiel, wie es NICHT funktioniert, aber es demonstriert genau den Effeckt den ich beschrieben habe - diesmal mit dem gesamten Code.

widget.cpp

Code: Alles auswählen

#include "widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{

    mainLayer = new QHBoxLayout(this);
    scrollArea = new QScrollArea;
    scrollArea->setFixedWidth(220);

    scrollGrp = new QGroupBox;
    scrollGrp->setLayoutDirection(Qt::LeftToRight);
    scrollGrp->setFixedWidth(200);

    nameValues << "Recorder"<<"Cars"<<"Trees"<<"Computers"<<"Cellphones"<<"Flowers"<<"Games"<<"Fireworks"<<"Pets"<<"Houses"<<"Airplanes"<<"Underware"<<"Toys"<<"Holiday"<<"Sports";

    scrollLayer = new QVBoxLayout(this);

    for(int i=0;i<10;i++){
        grpList.append( new QGroupBox(QString(nameValues.at(i)),this) );
        layerList.append( new QVBoxLayout() );
        btnList.append( new QPushButton(QString(nameValues.at(i)),this) );

        grpList.at(i)->setLayout(layerList.at(i));
        layerList.at(i)->addWidget(btnList.at(i));

        grpList.at(i)->setCheckable(true);
        connect(grpList.at(i),SIGNAL(toggled(bool)),this,SLOT(sizeGrpToMinimum(bool)));

        scrollLayer->addWidget(grpList.at(i));
    }

    scrollGrp->setLayout(scrollLayer);
    scrollArea->setWidget(scrollGrp);
    mainLayer->addWidget(scrollArea);

}

Widget::~Widget()
{

}

void Widget::sizeGrpToMinimum(bool val){
    QGroupBox* cache = dynamic_cast<QGroupBox*>(QObject::sender());
    if(val==true){
        cache->setMinimumHeight(0);
        cache->setMaximumHeight(16000);
    }else{
        cache->setMinimumHeight(14);
        cache->setMaximumHeight(14);
    }
}
widget.h

Code: Alles auswählen

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QGroupBox>
#include <QList>
#include <QStringList>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();
private slots:
    void sizeGrpToMinimum(bool val);
private:
    QHBoxLayout* mainLayer;

    QScrollArea* scrollArea;
    QGroupBox* scrollGrp;
    QVBoxLayout* scrollLayer;

    QStringList nameValues;
    QList <QGroupBox*> grpList;
    QList <QVBoxLayout*> layerList;
    QList <QPushButton*> btnList;

};

#endif // WIDGET_H
main.cpp

Code: Alles auswählen

#include <QtGui/QApplication>
#include "widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

Verfasst: 22. Oktober 2010 09:30
von franzf
Die Zaubercodezeile ist

Code: Alles auswählen

scrollArea->setWidgetResizable(true);
du kannst dem scrollLayer auch noch einen stretch am Ende geben.
Aber diese set[Minimum,Maximum]Size gefallen mir nicht. Du igorierst die verschiedenen Werte der verschiedenen Styles. Hier schaut deine Vorgabe grauenhaft aus, die CheckBox wird nur halb dargestellt - einfach abgeschnitten!
Besser wäre es, die children auf hidden/visible zu setzen:

Code: Alles auswählen

        foreach(QWidget* child, cache->findChildren<QWidget*>())
            child->show();
Das lässt aber einen unschönen Rahmen.
Die allerbeste Lösung wäre, dass du statt einer QGroupBox ein eigenes Widget schreiben würdest, welches garantiert, dass es sich von anderen Objekten seiner Klasse gut absetzt (macht ja die GroupBox), und sich aus- und einfahren lässt und dabei in allen Fällen gut ausschaut.

//edit:
Ah! Es gibt ja das flat-Property bei der QGroupBox. setze true wenn eingefahren, andernfalls false. Sollte so also optisch auch gehen.

Und ganz zum Schluss: Statt sender() solltest du gleich auf QSignalMapper umstellen.

Verfasst: 22. Oktober 2010 11:39
von Pixtar
Das "Object::sender()" kein OOP ist weiß ich. Meine letzten zwei Versuche mit QSignalMapper zu arbeiten sind fehlgeschlagen - aber naja alle guten Dinge sind drei. :)

Du schreibst ich solle nicht set[Minimum/Maximum]Size() verwenden, sondern hide oder visible. Dann werden die GroupBoxen aber nicht mehr angezeigt und ich bräuchte ein extra Menü, über das ich die versteckten GroupBoxen wieder anzeigen lassen kann. Ich finde das viel zu umständlich und auch unübersichtlich - deshalb muss das mit der Größenänderung der GroupBoxen bestehen bleiben.
Anderen Vorschlag außer set[Minimum/Maximum]Size() / hide / visible ??
Die allerbeste Lösung wäre, dass du statt einer QGroupBox ein eigenes Widget schreiben würdest, welches garantiert, dass es sich von anderen Objekten seiner Klasse gut absetzt
Ich hatte ja schon Eingangs erwähnt, dass ich eigentlich wegen so etwas banalem keine eigene Klasse schreiben wollte - aber wie es scheint, kann das nicht vermieden werden.

Code: Alles auswählen

scrollArea->setWidgetResizable(true);
Werde ich mal ausprobieren.

Pixtar

Verfasst: 22. Oktober 2010 11:46
von franzf
Nein, nicht die ganze GroupBox verstecken, sondern nur die widgets-children. Der SLOT schaut bei mir grad so aus:

Code: Alles auswählen

void Widget::sizeGrpToMinimum(bool val){ 
    QGroupBox* cache = dynamic_cast<QGroupBox*>(QObject::sender());
    if(val==true){ 
//         cache->setMinimumHeight(0); 
//         cache->setMaximumHeight(16000);
        foreach(QWidget* child, cache->findChildren<QWidget*>())
            child->show();
        cache->setFlat(false);
    }else{ 
//         cache->setMinimumHeight(14); 
//         cache->setMaximumHeight(14); 
        foreach(QWidget* child, cache->findChildren<QWidget*>())
            child->hide();
        cache->setFlat(true);
    } 
}
Und in Kombi mit setWidgetResizable funktioniert es blendend und schaut recht ansehnlich aus.

Verfasst: 22. Oktober 2010 11:56
von padreigh
Ich hab ein wenig rumgespielt (*) aber klappte auch nicht so doll. Trozdem ein paar Anmerkungen zum Code:

Code: Alles auswählen

nameValues = QString("Recorder,Cars,Trees,Computers,Cellphones,Flowers,Games,Fireworks,Pets,Houses,Airplanes,Underware,Toys,Holiday,Sports").split(","); // kürzer als "<<" dazwischen :) mE lesbarer

    foreach (QString s, nameValues) { // einfach für alle
        grpList.append( new QGroupBox(s ,this) );
        layerList.append( new QVBoxLayout() );
        btnList.append( new QPushButton(s ,this) );

        grpList.last()->setLayout(layerList.last());  // .last() statt .at(i)
        layerList.last()->addWidget(btnList.last());

        grpList.last()->setCheckable(true);
        connect(grpList.last(),SIGNAL(toggled(bool)),this,SLOT(sizeGrpToMinimum(bool)));

        scrollLayer->addWidget(grpList.last());
    }


Wie schon gesagt wäre ein QSignalMapper besser, ansonsten kannst du das casten auch anders machen und solltest testen obs auch gelingt (vermutlich hier weggelassen da demo):

Code: Alles auswählen

QGroupBox* cache = qobject_cast<QGroupBox*>(QObject::sender());
if (cache) { 
// ... 
}
Ansonsten ist das was du brauchst recht ähnlich einem Treeview:

Code: Alles auswählen

o
+[x]App 
|      + Fkt 1
|      + Fkt 2
|
+[x] App 2
|      + Fkt 1
|      + Fkt 2
|      + Fkt 3
|
+[ ] App 3
+[x] App 4
       + Fkt 1
Vielleicht ließe sich da über Styles etwas machen das nur die TopLevel Decoration angezeigt wird (zum aus/einklappen) aber die Striche im Tree selbst nicht ...

(*) meine Spielerrei etwa so:

Code: Alles auswählen

class Silly : public QWidget
{
    Q_OBJECT
public:

    Silly ( const QString & title, QWidget * parent = 0 ) :
            QWidget(parent),
            lay(new QVBoxLayout),
            checkBox(new QCheckBox(title)),
            groupBox(new QGroupBox(title))
    {
        QVBoxLayout * vlay = new QVBoxLayout(this);
        vlay->addWidget(checkBox);
        vlay->addWidget(groupBox);

        checkBox->hide();
        checkBox->setCheckState(Qt::Unchecked);

        groupBox->show();
        groupBox->setLayout(lay);
        groupBox->setCheckable(true);
        groupBox->setChecked(true);

        connect(checkBox,SIGNAL(clicked(bool)),this,SLOT(switchEm()) );
        connect(groupBox,SIGNAL(clicked(bool)),this,SLOT(switchEm()) );
    }


    void addWidget(QWidget * w)             { lay->addWidget(w);}
    void addLayout(QLayout * l,int stretch) { lay->addLayout(l,stretch);}

signals:
    void toggled(bool);

private slots:
    void switchEm()
    {
        const bool cbShown = checkBox->isVisible();
        
        checkBox->setVisible(!cbShown);
        checkBox->setEnabled(!cbShown);
        checkBox->setChecked(false);

        groupBox->setVisible(cbShown);
        groupBox->setEnabled(cbShown);
        groupBox->setChecked(true);

        emit toggled(!cbShown);
    }

private:
            QVBoxLayout * lay;
            QCheckBox   * checkBox;
            QGroupBox   * groupBox;
};

Verfasst: 22. Oktober 2010 12:23
von Pixtar
franzf hat geschrieben:Nein, nicht die ganze GroupBox verstecken, sondern nur die widgets-children. Der SLOT schaut bei mir grad so aus:

Code: Alles auswählen

void Widget::sizeGrpToMinimum(bool val){ 
    QGroupBox* cache = dynamic_cast<QGroupBox*>(QObject::sender());
    if(val==true){ 
//         cache->setMinimumHeight(0); 
//         cache->setMaximumHeight(16000);
        foreach(QWidget* child, cache->findChildren<QWidget*>())
            child->show();
        cache->setFlat(false);
    }else{ 
//         cache->setMinimumHeight(14); 
//         cache->setMaximumHeight(14); 
        foreach(QWidget* child, cache->findChildren<QWidget*>())
            child->hide();
        cache->setFlat(true);
    } 
}
Und in Kombi mit setWidgetResizable funktioniert es blendend und schaut recht ansehnlich aus.
Einwandfrei, das ist ja besser als ich mir das erdacht hatte. Besten Dank für eure Unterstützung bei der etwas mürrischen "SideBar".