Qt has a layout system kind of separate from the widgets, which is causing me problems. I know that when I have a QSplitter, I can add stuff to it just by doing
new SomeCustomQWidget(splitter);
However, if I have a QWidget, shouldn't I be able to do something similar? The Qt web pages only explain how to add layoutItems to layouts, only connecting them to widgets by inheriting both in a subclass. So, right no I am doing something like this
item = new QWidget(parentsplitter); //this works
subitem = new customWidget(item); //this too
subitem2 = new QSlider(item); //this isn't laid out with subitem
However this results in the slider being on top of the subitem instead of being next to it (either right or below I'd expect). Calling
//either
new QVBoxLayout(item);
//or
item->setLayout(new QVBoxLayout());
//or
item->setLayout(new QVBoxLayout(item));
after initializing the item doesn't help. (My logic is that the item takes the layout as it's child and will add subsequent children to it).
I went around this problem earlier but now I'm facing the exact same problem when making a widget inside a QGraphicsView. I feel like it shouldn't be necessary to make a custom subclass of every item I could ever want to dynamically add to those widgets, like the Qt website seems to expect. (If I simply want to add some text and a couple buttons, do I really need to make 2 custom subclasses?) I am using a .ui file for basic layout but these items need to be created dynamically.
Solution
Forgot to call item->show(); on the widget used for layout.
Turns out, I had tested otherwise correct code, but since some of my subclasses called show() but others didn't, it just seemed broken to me.
This however isn't the issue with the graphicswidgets, as graphicslayout can only be used to add graphicslayoutitems, which don't include for example graphicstextitems.
Solution 2
Using the QGraphicsProxyWidget instead of QGraphicsWidget fixed the issue!
QWidget *parent = new QWidget();
QVBoxLayout *rootLayout = new QVBoxLayout(parent);
QHBoxLayout *subLayout1 = new QHBoxLayout();
QHBoxLayout *subLayout2 = new QHBoxLayout();
rootLayout->addLayout(subLayout1);
rootLayout->addLayout(subLayout2);
subLayout1->addWidget(new QLabel("Foo"));
subLayout1->addWidget(new QLabel("Bar"));
subLayout2->addWidget(new QPushButton("Foo button"));
subLayout2->addWidget(new QPushButton("Bar button"));
parent->show();
Note once layout is assigned to a widget by setLayout or when widger is passed to constructor fo layout, the are in 1 to 1 relationship which last until widget or layout is destroyed. See documentation:
If there already is a layout manager installed on this widget, QWidget won't let you install another. You must first delete the existing layout manager (returned by layout()) before you can call setLayout() with the new layout.
That is why subitem2 = new QSlider(item) didn't work for you.
I think you're confusing parent/child relationship with managing layout.
Lets start with layouts. When set on a widget the layout becomes a child of that widget. When constructed with a widget parent the widget becomes a parent and the layout is set on the widget. When adding widgets to layout they become children of the widget that layout is set on, not the layout itself. Setting a parent on a widget or constructing widget with a parent does not automatically add the widget to its parent layout. Deleting a layout does not delete widgets governed by it, because they are not its children.
To summarize - layouting and parenting are two distinct mechanisms. first is for governing position and size of a widget, the other is for object hierarchy and resource management (parents delete their children). Layouts have a convenience constructor though, that does both - sets a parent of the layout and sets that layout on the parent widget.
In code:
item = new QWidget()
layout = new QVBoxLayout(item);
is equivalent to:
item = new QWidget();
layout = new QVBoxLayout();
layout->setParent(item);
item->setLayout(layout);
This code adds a child to a layout and item becomes its parent:
item = new QWidget();
layout = new QVBoxLayout(item);
child = new QWidget();
layout->addWidget(child);
This adds a child to a parent item, but does not add child to a layout:
item = new QWidget();
layout = new QVBoxLayout(item);
child = new QWidget(item);
A QSplitter is special in that it is a widget and it does its own layout of children i.e. when you use a splitter as a parent of a widget that widget's geometry is governed by the splitter. Other widgets don't behave like that.
So to comment your code:
item = new QWidget(parentsplitter); //this works because splitter does layout
subitem = new customWidget(item); //item is not splitter so subitem geometry is not managed
subitem2 = new QSlider(item); //item is not splitter so subitem2 geometry is not managed
If you want to lay out subitems in the item then item either has to be a splitter too or have a layout that will manage children, so:
childsplitter = new QWidget(parentsplitter); //note that parentsplitter has only 1 item
subitem = new customWidget(childsplitter);
subitem2 = new customWidget(childsplitter);
or
widget = new QWidget(parentsplitter); //note that parentsplitter has only 1 item
lay = new QHBoxLayout(widget);
subItem = new customWidget();
subItem2 = new customWidget();
lay->addWidget(subItem);
lay->addWidget(subItem2);
If you want parent splitter to have two items then you do:
item = new customWidget(parentsplitter);
item2 = new customWidget(parentsplitter);
Related
I have a problem displaying two labels, the label "label" is displayed with no problem ( it has some buttons adjusted Horizontally ) and the second label "label1" is not being displayed ( it contains a scene which has a view , in the scene i have a title and a picture ). This is the code i am trying to use.
widget1 = new QWidget;
setCentralWidget(widget1);
label1 = new QLabel(widget1);
scene = new QGraphicsScene(label1);
vue = new QGraphicsView(scene);
label1->move(100,100);
label = new QLabel(widget1);
layout = new QHBoxLayout(label);
label->resize(500,100);
It's my first time using QGraphicsScene/View and it is confusing me a little bit.
Thanks for the answers
You're definitly not using all those widgets the right way.
you don't need a label to display a QGraphcisView, neither the QGraphicsScene to be the child of the label.
If you want to display, let's say, a label and a view in the center widget you could write this :
//==== Central widget part
QWidget* widget = new QWidget(this);
setCentralWidget(widget);
//==== Graphics part
QGraphicsScene* scene = new QGraphcisScene(this);
QGraphicsView* view = new QGraphicsView(widget);
view->setScene(scene);
//==== Label part
QLabel* label = new QLabel(widget);
//==== Layout part
QHBoxLayout* layout = new QHBoxLayout(widget);
layout.addWidget(label);
layout.addWidget(view);
widget.setLatout(layout);
This should show a label and a graphics view in row.
If you allow me a piece of advice : you should understand the parenting system before trying to use the QGraphics module. It's one of the cornerstones of Qt and that what you did wrong here. Starts here for the parenting system : https://doc.qt.io/qt-5/objecttrees.html
and here for the graphics : https://doc.qt.io/qt-5/graphicsview.html
Hope that helps you.
I am trying to create a gui that has a QTabWidget with multiple tabs. As a preliminary test I have created one that has two tabs using the same layouts. The first tab (Page 1) is blank but then the second one shows the QTableView I created. Both tabs were created the exact same way, yet they perform differently. Anyone know why the first one is blank...?
I noticed that if I comment out the line int8Window->setLayout(_layout);. Page 1 shows up with the proper layout and the second tab is blank this time...
Here is the code for the gui:
// Main window and layout
QWidget* mainWindow = new QWidget;
QVBoxLayout *mainLayout = new QVBoxLayout;
// Tab widget
QTabWidget* tabWidget = new QTabWidget;
// The pages in the tab widget
QWidget* uInt8Window = new QWidget;
uInt8Window->setWindowTitle(QString("Page 1"));
QWidget* int8Window = new QWidget;
int8Window->setWindowTitle(QString("Page 2"));
QTableView* tableView = new QTableView;
QStandardItemModel* model = new QStandardItemModel(5, 5);
for (int row = 0; row < 5; ++row) {
model->setItem(row, 0, new QStandardItem("3"));
model->setItem(row, 1, new QStandardItem(5));
model->setItem(row, 2, new QStandardItem(2));
model->setItem(row, 3, new QStandardItem(1));
model->setItem(row, 4, new QStandardItem(5));
}
tableView->setModel(model);
// Setting the tab page layouts
_layout = new QVBoxLayout;
_layout->addWidget(tableView);
uInt8Window->setLayout(_layout);
int8Window->setLayout(_layout);
// Add the pages to the tab widget
tabWidget->addTab(uInt8Window, "Page 1");
tabWidget->addTab(int8Window, "Page 2");
// Add the tab widget to the main layout and show
mainLayout->addWidget(tabWidget);
mainWindow->setLayout(mainLayout);
mainWindow->show();
Read logs! I'm sure you have a respective warning.
You are assigning same layout to two different widgets. Once layout is assigned to a widget, it is owned by this widget forever.
You need create separate layout for each widget.
I recommend to split this onto couple methods. One is creating a widget for a page (you can use this couple times). Other creating a data model, and other composing tab widget.
Please remember also about memory management! Best approach is to set parent during construction (as parameter of constructor). You have a leak in data model.
So I have this code:
QStackedLayout *layout = new QStackedLayout;
QMdiArea *mdi1 = new QMdiArea;
mdi1->addSubWindow(new QMdiSubWindow);
layout->addWidget(mdi1);
QMdiArea *a = (QMdiArea *) layout->currentWidget();
a->addSubWindow(new QMdiSubWindow);
Which for some reason doesn't work. What I want to do is get the widget that is being displayed in layout - as a QMdiArea, then add a sub window to it.
P.S. this is a simplified version on the full app. adding a sub window directly to mdi1 will work but it is NOT what I'm looking for (as there are many QMdiArea's in the QStackedLayout).
So the answer was that I needed to use QStackedWidget instead of QStackedLayout.
I would like to add two widgets (say QPushButton) to the status bar, one to the left and other to the right side.
I am thinking of adding horizontal spacer in between the two widgets, but don't know how to add.
PS: I tried using addWidget() to add to the left and addPermanentWidget() to add to the right but it doesn't look neat and also it doesn't feel right.
You can add two buttons to a layout in a widget and add the widget to the status bar using QStatusBar::addWidget :
QWidget * widget = new QWidget();
QPushButton * leftBut = new QPushButton("Left");
QPushButton * rightBut = new QPushButton("Right");
QGridLayout * layout = new QGridLayout(widget);
layout->addWidget(leftBut,0,0,1,1,Qt::AlignVCenter | Qt::AlignLeft);
layout->addWidget(rightBut,0,1,1,1,Qt::AlignVCenter | Qt::AlignRight);
ui->statusBar->addWidget(widget,1);
I am thinking of adding horizontal spacer in between the two widgets, but don't know how to add.
Here is a way to use a "fake" spacer.
QPushButton *leftButton = new QPushButton("Left");
QPushButton *rightButton = new QPushButton("Right");
QLabel *spacer = new QLabel(); // fake spacer
ui->statusBar->addPermanentWidget(leftButton);
ui->statusBar->addPermanentWidget(spacer, 1);
ui->statusBar->addPermanentWidget(rightButton);
The second parameter in addPermanentWidget is "used to compute a suitable size for the given widget as the status bar grows and shrinks".
Demo:
I think the simplest way is using a QGridLayout (honestly I never tried to modify a status bar anyway) supposing that the status bar is or descends from widget you can do this:
QGridLayout *myGridLayout = new QGridLayout();
statusbar->setLayout(myGridLayout)
QPushButton *button1 = new QPushButton(this);
myGridLayout->addWidget(button1,0,0,1,1);
QPushButton *button2 = new QPushButton(this);
myGridLayout->addWidget(button2,X,0,1,1);
The biggest is X the more space you want to leave in between, I would suggest to start with 3 and then make few tests to see how it looks.
I have a tab widget where every tab is a QTableView. I would like to be able to pass an updated model (QModelIndex) into each tab whenever the contents of that tab need to change.
The alternative (and nastier way) is for me to delete all the tabs, and then recreate them.
I know I can get the widget in the tab by doing something like:
tabWidget->widget(i);
This will return a widget, which is really a QTableView, but I want to update the model that is in that widget without having to delete and recreate the tab.
Thank you!
P.S. This is my current attempt...
for (int i = 0; i < tableView.size(); i++)
{
tabWidget->setCurrentWidget(tableView.at(i));
QTableView* updatedTable = (QTableView*)tabWidget->currentWidget();
updatedTable->setModel(dataModel);
tableView.replace(i, updatedTable);
}
It's not clear why you can't keep the QTableView widget and just change the model, as in your code. Doesn't the view refresh without this tableView.replace thing?
There doesn't appear to be a direct API for replacing the widget you put in with addTab() without going through a tab removal step. But instead of inserting the QTableView directly, you could instead call addTab() on a dummy widget that has a layout in it with a single item. A QStackedLayout, for instance:
QWidget* dummy = new QWidget;
QStackedLayout stackedLayout = new QStackedLayout;
stackedLayout->addWidget(tableView);
dummy->setLayout(stackedLayout);
tabWidget->addTab(dummy);
Then later, when you want to replace the tableView with a new one:
QWidget* dummy = tabWidget->currentWidget();
QStackedLayout newStackedLayout = new QStackedLayout;
newStackedLayout->addWidget(newTableView);
delete dummy->layout();
dummy->setLayout(newStackedLayout);
I still wonder what this is buying you that reusing the old table view couldn't do.