Modify a tab in a QTabWidget where each tab represents a QTableView - c++

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.

Related

Qt widget stacking child layouts on top of each other

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);

Close button only on active tab of QTabWidget

To save space in a QTabWidget, I would like to show the close icon only for the current tab, like e.g. Firefox is doing:
Is there a simple way using a style sheet, some thing like (not working like this)
QTabBar::tab::!selected::close-button {visible: false;}
or do I have to subclass QTabWidget to get the desired behavior?
You won't need to subclass anything, you can use QTabWidget::tabBar() method to obtain a reference (i.e. QTabBar *) to the tab bar associated with your QTabWidget. (Note that this method is no longer protected, so it can be accessed without subclassing the class)
QTabBar *tabBar = tabWidget->tabBar();
You can now use tabBar reference to hide close buttons on non-current tabs. For example to hide ith button, you can do:
tabBar->tabButton(i, QTabBar::RightSide)->hide();
So a simple workflow could be as follows:
Connect QTabWidget::currentChanged(int index) signal to a slot.
In that slot hide all close buttons other than the button at index.
You can subclass QTabWidget to get access to the QTabBar widget using protected method QTabWidget::tabBar. Then you can connect to the QTabBar::currentChanged signal and hide close button for not selected tabs manually:
QTabBar::ButtonPosition closeSide =
(QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this);
for (int i = 0; i < toolbar->count(); ++i)
{
if (i != toolbar->currentIndex())
{
QWidget *w = toolbar->tabButton(i, closeSide);
w->hide();
}
}
hide() leaves empty space for the invisible close button. This looks funny.
Set the width to 0 instead.

QSortFilterProxyModel how to handle QStandardItems correctly

I have QTreeView with some items and search QLineEdit with connected slot on textEdited signal.
With this code:
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(messagesModel);
proxyModel->setFilterFixedString(text);
ui.treeView->setModel(proxyModel);
text filtering is ok, but when I clicked on QTreeView QStandardItems checkboxes (after proxy model assigned to QTreeView), I have the program crashes in slot, that connected to this QTreeView original model (before proxy was assigned).
What is the right way to processing item checkbox clicks? Need I use new connect/slot to processing model changes, or I can use the same code for original model with some changes? I just need to hide filtered items in QTreeView. In QTreeWidget is hide() method, does QTreeView has something like this, or QSortFilterProxyModel - is what I need? Thx!
UPD crashed in slot, connected to treeView:
auto item = messagesModel->itemFromIndex(index); // item is NULL because proxyModel is set for TreeView now
if(item->whatsThis().isEmpty()) return; // error below
#ifndef QT_NO_WHATSTHIS
inline QString whatsThis() const {
return qvariant_cast<QString>(data(Qt::WhatsThisRole));
}
inline void setWhatsThis(const QString &whatsThis);
#endif
because I set proxyModel to treeView, but messagesModel have whatsThis...
I changed my code with that:
QStandardItem* item;
if(ui.leFilter->text().isEmpty())
item = messagesModel->itemFromIndex(index);
else
item = messagesModel->itemFromIndex(proxyModel->mapToSource(index));
if(item->whatsThis().isEmpty()) return;
and it works. Is that correct way? Proxy model is member of my UI class ... not local.
UPD how can I update source model when checkbox checked in proxyModel?
UPD 2 I have load "original" model for QtreeView and show it. When I edit text in QListEdit, I use proxyModel (code from 1st post). When text edited, I have check checkboxes in QtreeView (now proxyModel is active) and at this step all is ok. But when I do some changes in UI, in QTreeView set the original model and it has no changes that was made for proxyModel. How can I notify and update items in source Model with new data from proxyModel?
UPD3 Yes, source model is also modified ... I have just clear it)

QTabWidget tab displays nothing in one of the tabs

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.

How do I add a QTCreator-like left bar to my program?

I'm designing the GUI for a project, and I want a left bar like this ones
(source: patatux.net)
(source: tuxradar.com)
How do I put them in my .ui file?
You can try to use QToolBar with vertical orientation.
To emulate tabs behavior you should put actions to QActionGroup and make them checkable.
For example to create left panel Qt creator like:
welcomeAct = new QAction(...)
toolbar->addAction(welcomeAct)
editAct = new QAction(...)
toolbar->addAction(editAct)
designAct = new QAction(...)
toolbar->addAction(designAct)
...
//add spacing
QLabel *spacing = new QLabel;
spacing->setSizePolicy(Qt::Expanding, Qt::Expanding);
toolbar->addWidget(spacing);
//adding aditional actions
runAct = new QAction(...)
toolbar->addAction(runAct)
runDebugAct = new QAction(...)
toolbar->addAction(runDebugAct)
buildAct = new QAction(...)
toolbar->addAction(buildAct)
// put "tabs" action in QActionGroup
group = new QActionGroup(this);
group->addAction(welcomeAct)
group->addAction(editAct)
group->addAction(designAct)
...
Simplest way - is to use QtCreator's library libCorePlugin.so and corresponding includes (FancyTabBar.h) from QtCreator's srcs
You can most likely do it by putting everything into a QHBoxLayout, where the left hand side is a QVBoxLayout column of QPushButton's with icons matching what you want. Have the buttons trigger what the right hand pane looks like.
There is also the QTabBar which does most of this work for you. You just need to tell it to put the tabs on the left hand side.