PDA

View Full Version : QScrollArea in a QTabWidget page



CoderMan
27th February 2012, 19:49
This should be easy and I found topics similar to this but I'm still stuck, so I'll
ask in a new thread: How do I get the contents of a tab in a QTabWidget to scroll?

I create a scroll area, set a layout to it and then insert it to a tab
widget. Later, I add widgets to that layout. However, scroll bars never
appear. Instead, the widgets get partially stacked on top of each other.
What am I doing wrong? I'm trying to avoid having to create a custom
widget for each and every tab and then using scrollarea->setWidget() but
is that really the problem?



//in header for base class:
protected:
QVBoxLayout* vlayout;

//in header for this class:
private:
QGridLayout* grid;
QTabWidget* tabs;

//in constructor for this class:
tabs = new QTabWidget(this);
grid = new QGridLayout();
grid->setAlignment(Qt::AlignLeft | Qt::AlignTop);
QScrollArea* scrollarea = new QScrollArea(this);
scrollarea->setLayout(grid);
tabs->addTab(scrollarea, tr("Look, items!"));
vlayout->addWidget(tabs);

//items get added some time after construction:
SomeItem* item = new SomeItem(this); //not derived from base class, does have a layout
item->setStuff("stuff");
grid ->addWidget(item);

ChrisW67
27th February 2012, 22:22
Are you trying to achieve:

multiple tabs each with content that can be scrolled about, or
a single tab with scroll area that grows to accommodate more content added later?

CoderMan
28th February 2012, 20:38
I don't see the difference between those two points... I will need to have several tabs that each scroll on their own, but once that is established, all of them can receive more items to display. The entire QTabWidget, however, will not need to be viewed through a scrolling area.

I must say, the interactions between layouts, parent pointers, basic widgets and "container" widgets can get pretty complicated. Very many possible permutations in this case, for example: when to set parent to "this" and when not, what gets added to what and in which order.

ChrisW67
28th February 2012, 22:17
I don't see the difference between those two points...
The difference is that one adds new tabs, and the other adds new content to an existing layout. You talk a bit about adding content to existing layouts but then also talk about having to make custom widgets for each tab. It was worth clarifying whether you wanted one or the other because your code only demonstrates one.

I will need to have several tabs that each scroll on their own, but once that is established, all of them can receive more items to display. The entire QTabWidget, however, will not need to be viewed through a scrolling area.
OK, you want both.

Here is a single tab example:


#include <QtGui>
#include <QDebug>

class Widget: public QFrame {
Q_OBJECT
public:
Widget(QWidget *p = 0): QFrame(p) {
setFrameStyle(QFrame::Panel | QFrame::Plain);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(new QLabel("A Label", this));
layout->addWidget(new QLabel("B Label", this));
setLayout(layout);
}
};

class TabWidget: public QTabWidget {
Q_OBJECT

QTimer *timer;
QVBoxLayout *layout;
public:
TabWidget(QWidget *p = 0): QTabWidget(p) {
// The stuff inside the scroll area
QWidget *container = new QWidget(this);
layout = new QVBoxLayout(container);
layout->setSizeConstraint(QLayout::SetFixedSize); // <<< this is the magic
addAnother();
addAnother();
container->setLayout(layout);

QScrollArea *scroll = new QScrollArea(this);
scroll->setWidget(container);

// Put the scrolling area in to a tab page
addTab(scroll, "One");

// Cause a new one to be added every 2 seconds for demo purposes
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), SLOT(addAnother()));
timer->start(2000);
}

public slots:
void addAnother() {
layout->addWidget(new Widget(this));
}

};


int main(int argc, char *argv[])
{
QApplication app(argc, argv);

TabWidget tw;
tw.show();

return app.exec();
}
#include "main.moc"


The magic is the sizeConstraint on the container's layout. This causes the layout to adopt the size of the contained widget's sizeHint()s rather than resize the contained widgets. For a variation on the theme look at the Extension Example

In general, if I create a QObject or derived class on the heap I always give it a parent thereby ensuring the memory is freed when the program exits (cleanly or by crashing, not that the latter ever happens to me ;) ).

CoderMan
2nd March 2012, 19:15
Yep, the size constraint was the solution:


//header for class holding tab widget
QGridLayout* dataLayout;
QWidget* page;
QTabWidget* tabs;
QVBoxLayout* mainLayout;

//constructor
dataLayout = new QGridLayout();
page = new QWidget(this);
QScrollArea* scrollArea = new QScrollArea(this);
dataLayout->setSizeConstraint(QLayout::SetFixedSize); //important!
page->setLayout(dataLayout);
scrollArea->setWidget(page);
tabs->addTab(scrollArea, tr("Some items"));
mainLayout->addWidget(tabs);
this->setLayout(mainLayout);

//test add after construction
for (int n = 0; n < 40; ++n)
{
SomeItem* item = new SomeItem(this);
item->setStuff("whatever");
dataLayout->addWidget(item);
}


Without setSizeConstraint(QLayout::SetFixedSize) the items will not even be displayed
Adding items in the constructor does not work (tried after every phase)
Not using a "page" QWidget and calling scrollArea->setLayout(dataLayout)
does not work. Subclassing an actual custom Page widget from QWidget, however, is not
required.


I wonder what use does QScrollArea::setLayout() even have?

CoderMan
10th March 2012, 19:16
I have discovered a refinement: setSizeConstraint(QLayout::SetFixedSize) stops the page widget from expanding in any direction when the entire tab widget is expanded, but these can be used instead.

page->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
scrollArea->setWidgetResizable(true);
Now the page widget will use all the space in can get, yet scroll bars will appear if that space is not enough.