Qt QTreeView not updating when adding to model - c++

The source code relating to this question is available on my public Git repository on BitBucket.
I'm trying to dynamically add some items to a QTreeView model using the following code in mainwindow.cpp:
if(dlg->exec() == QDialog::Accepted) {
QList<QVariant> qList;
qList << item.name << "1111 0000" << "0x00";
HidDescriptorTreeItem *item1 = new HidDescriptorTreeItem(qList, hidDescriptorTreeModel->root());
hidDescriptorTreeModel->root()->appendChild(item1);
}
This works when run from within my MainWindow constructor, just after ui->setupUi(this), but I need this to run from within an event filter, but the same code doesn't get the QTreeView updating. When I set a breakpoint at mainwindow.cpp:70 and step through the next few lines, I can see the data is being added to the Model, but I need the QTreeView to refresh.
I understand this is done by emitting dataChanged(), but not really sure how to do this. The signal signature for the dataChanged signal looks as follows:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
so I need to come up with topLeft and bottomRight QModelIndex instances. How do I build/obtain these from item1 in the above snippet?
Also, where does beginInsertRows() and endInsertRows() come into view with this, should I be calling these functions?

From QAbstractItemModel documentation:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent, int first, int last ) [protected]
Begins a row insertion operation.
When reimplementing insertRows() in a subclass, you must call this function before inserting data into the model's underlying data store.
The parent index corresponds to the parent into which the new rows are inserted; first and last are the row numbers that the new rows will have after they have been inserted.
The other protected functions say similar things.
And insertRows() says:
If you implement your own model, you can reimplement this function if
you want to support insertions. Alternatively, you can provide your
own API for altering the data. In either case, you will need to call
beginInsertRows() and endInsertRows() to notify other components that
the model has changed.
Take a look to QAbstractItemModel protected functions and signals
Views connect to those signals to know when model data changes and rearrange data inside. The functions emit the signals internally to make it easy for you to warn the view when it has happenned. But signals can only be emitted by abstract class.
Components connected to this signal use it to adapt to changes in the
model's dimensions. It can only be emitted by the QAbstractItemModel
implementation, and cannot be explicitly emitted in subclass code.
So you will have to stick to the methods.
Edit in answer to your comment:
Indeed, Items should have a reference to model and tell it about changes, check theses lines from QStandardItem:
void QStandardItem::emitDataChanged()
void QStandardItem::removeRows(int row, int count)
( Note, how, in second, it calls model's rowsAboutToBeRemoved() and rowsRemoved() )
Maybe you should try to use QStandardItem and QStandardItemModel.
Either direct or subclassing. It will hide a lot of ugly stuff.

There's also a less proper but much easier way to achieve this - emit layoutChanged() instead of dataChanged(). More info - https://stackoverflow.com/a/41536459/635693

Related

How to communicate across multiple Qt Widgets that all share a model

I have a collection widgets in a mainwindow that all work with a single bit of data on a model, I've attached a drawing of the structure below. Currently only the tree widget has access to the model and also has a treeview. Communication with the other widgets is done through signals and slots. When a new selection is made in the tree, every other widget on the screen changes to represent the new selected tree node. This works, however sending signals through one widget to all others is extremely cumbersome.
I'd like to pass the model that the tree uses to all the other widgets and make it work off of the signals that the model sends upon any changes to it. So instead of having all the classes sending signals to each other communication can be done through the model itself.
I've created and passed a model to the tree and the other widgets in the main window class's constructor but I'm not sure how I should proceed from this point as I am relatively unfamiliar with MVC. The tree class is the only class with a view at this point, do I need to add views to the other classes as well to be able to get access to signals that are emitted when a certain field is selected or would the model be enough?.
I'd appreciate any input, thanks.
Yes, notify model if something changed and make it emit signal, subscribe to that signal in relevant widgets. In other words decouple widgets from each other removing widget-widget subscriptions, subscribe only to model.
Selection is not a property of the model, but of the view. Multiple views can be attached to a single model, each displaying a different "item". Thus, there is no out of the box method to signal a change in selection from the model itself.
There is a dedicated QItemSelectionModel class, that deals with exactely what you need: keep track of selection on a single model across multiple views.
You would wrap your data model in a selection model and base your communication upon it, e.g.:
MyMainWindow::MyMainWindow() {
myModel = new MyModel;
treeView = new MyTreeView(myModel);
QItemSelectionModel *selectionModel = new QItemSelectionModel(myModel);
treeView->setSelectionModel(selectionModel);
detailWidget = new MyDetailWidget(this, selectionModel);
}
////////////
MyDetailWidget::MyDetailWidget(QWidget *parent, QItemSelectionModel *selectionModel) {
// React to changed selection
connect(selectionModel, &QItemSelectionModel::currentChanged, this,
[=](auto current, auto /*previous*/) {
updateView(current);
}
);
// React to change in selected item
connect(selectionModel->model(), &QAbstractItemModel::dataChanged, this,
[=](auto topLeft, auto bottomRight, auto /*roles*/) {
QItemSelection sel(topLeft, bottomRight);
if (sel.contains(selectionModel->currentIndex()) {
updateView(selectionModel->currentIndex());
}
}
);
}
void MyDetailWidget::updateView(const QModelIndex &idx) {
// Assume some label on this widget
m_name->setText(idx.data(Qt::DisplayRole).toString());
}

Unable to obtain sender object in slot of dataChanged signal of QAbstractItemModel

I have subclassed QAbstractItemModel and trying to retrieve a widget in slot of dataChanged signal.
connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(slotDataChanged(const QModelIndex&, const QModelIndex&)));
void MyEditor::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
QComboBox* widget = dynamic_cast<QComboBox*>(sender());
if (widget)
{
// do something
}
}
Here I am getting a null widget everytime, same result with qobject_cast.
I am setting combobox widget in my tableview a delegate class which derives QStyledItemDelegate.
MyDelegate* myDelegate;
myDelegate = new MyDelegate();
tableView->setItemDelegate(myDelegate);
tableView->setModel(model);
QWidget* MyDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QComboBox* cb = new QComboBox(parent);
cb->addItem(QString("All"));
cb->setCurrentIndex(0);
return cb;
}
How can I obtain sender object in this case? Thanks.
Not sure of what are your intentions. Acquiring the editor widget when the data is already updated in the model is unnecessary in general.
Seems to me that a brief introduction to Model-View-Delegate concept is needed to solve your problem.
In short, the view, which in your case is the QTableView, has no data by itself. View acquires data from the attached model by calling data method. When user tries to edit some data, delegate createEditor and setEditorData methods are called. The latter has model pointer as one of the arguments so it can access actual data which needs to be represented.
When user finishes editing setModelData is called which has the editor widget available to acquire the updated value. It also has the model available to change the proper data entry normally done via setData method. At this point the dataChanged signal is emitted which notifies the view that corresponding data was updated so it can refresh the displayed value(s).
Hence, try rethinking your design. Maybe what you want to achieve can be implemented differently or your implementation can be slightly modified to conform the described flow.
You can also check the Qt site for Star Delegate Example to see some sample implementations or Model View Tutorial for a broader description of the Model-View topic.
My Model/View design was fine. I just needed to obtain a widget when user double clicks on a cell in my editor.
QComboBox* widget = dynamic_cast<QComboBox*>(tableView->indexWidget(topLeft));
if (widget)
{
// Do something
}
Here in slotDataChanged I obtained the required widget using QModelIndex.
Thanks for helping me out.

What is the best way to be notified of changes in an QAbstractItemView

I have a QListView that has its model (derived from QAbstractItemModel) regularly changed based on a some criteria in the UI. I would like to be notified when the view itself believes a new row has been added/removed either when the current model updates or when the model is changed. I need this notification so that I can call setIndexWidget and add a custom control under a particular column. I would prefer not to call setIndexWidget repeatedly because the population of the widget is expensive. So once per row would be ideal.
I've tried rowsInserted/rowsAboutToBeRemoved and dataChanged but those don't get called if the model being set into the view already has items in it.
Any thoughts would be greatly appreciated.
You're wrong about dataChanged. If the contents of any data item change, then dataChanged is signaled. The following invariant holds, assuming that the dataChanged slot is connected to the same signal on the model.
class ... : public QObject {
QModelIndex m_index;
bool m_changed;
Q_SLOT void dataChanged(const QModelIndex & tl) {
m_changed = m_changed || tl == m_index;
}
...
};
m_index = QModelIndex(...);
QVariant before, after;
m_changed = false;
before = model->data(index);
after = model->data(index);
Q_ASSERT(before == after || m_changed);
What you're describing is most likely caused by incorrect behavior of your model. There is a model test suite you could use to verify compliance of your model with required invariants.
Addressing your question points specifically:
I would like to be notified when the view itself believes a new row has been added/removed either when the current model updates...
The view doesn't need to believe anything. Your model must be emitting relevant signals to that effect. Simply connect to those signals from some QObject. That's all the view is doing. If the signals don't fire, the view won't be notified. End of story.
... or when the model is changed.
There's no signal for that since the entire model is replaced. You're the one who calls setModel on the view, so that shouldn't be a problem. You better know when the call is made :)

Is there any way to save the order of columns?

I currently have a tableview attached to a class that is derived from QSortFilterProxyModel. Now I wanted to know if there is any way by which I can store the order of columns since the users tend to move the columns back and forth. Also is there any signal that is emitted when a user changes the order of the columns.I search this but I cannot find anything that might tel me when a column is moved around and how i can save the tablew columns orders.
Any suggetsions would be appreciated
You need to obtain a QHeaderView object using QTableView::horizontalHeader. You can use QHeaderView::saveState and QHeaderView::restoreState to save state of columns. You can use QHeaderView::sectionMoved signal to detect column moving.
The reason why you cannot find the relevant signal in the documentation because you are checking an about 6-7 years old documentation. That is, it is Qt 4.1. The signal in question was added to Qt in version 4.6.
QAbstractItemModel has this signal recently:
void QAbstractItemModel::columnsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationColumn) [signal]
This signal is emitted after columns have been moved within the model. The items between sourceStart and sourceEnd inclusive, under the given sourceParent item have been moved to destinationParent starting at the column destinationColumn.
Note: Components connected to this signal use it to adapt to changes in the model's dimensions. It can only be emitted by the QAbstractItemModel implementation, and cannot be explicitly emitted in subclass code.
This function was introduced in QtCore 4.6.
This looks like what you are looking for. See the documentation for further details:
http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html#columnsMoved
Also, do not forget the fact that you will actually need QAbstractTableModel in the end of the day.
If you really wish, you could catch this signal as well:
void QHeaderView::sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) [signal]
This signal is emitted when a section is moved. The section's logical index is specified by logicalIndex, the old index by oldVisualIndex, and the new index position by newVisualIndex.
Please refer to the documentation for further details:
http://qt-project.org/doc/qt-5.1/qtwidgets/qheaderview.html#sectionMoved

Which model to subclass / view to use for a list of custom objects

I don't have enough experience with Qt yet to make a good design choice. Any help by experienced Qt programmers would be very appreciated.
I'm trying to find out which model to subclass, which view to use, what delegate subclassing / extending I should do...
My problem is similar to: I have these zones I would like to display, 1 per row:
class Zone{
//inputs
string country; //edited with a QComboBox
string city; //edited with a QComboBox
int ageMin;
//stored result
int nbInhabitantsOlderThanMin;
}
Here's what I'd like to do, and the design choices each requirements makes me think of:
I would like to display a list of them (--> QListView )
But to display 1 item I need several columns (--> QTableView )
I would like a double click on a row to trigger editing in a custom widget, since nbInhabitantsOlderThanMin can not be edited, and choosing a country restricts the list of cities that can be chosen in the QComboBox (and vice versa in my real example) (--> I should probably use a QDataWidgetMapper (or subclass?) somewhere...)
So whereas the edition of a row should happen in a widget, the display is simple / not custom, and subclassing a delegate (QStyledItemDelegate for instance) (I'm not so sure about this one) doesn't seem to be the right way to have 1 custom widget with many child input widget to edit the 3 fields at the same time.
I think the data to model would favor a model subclassing QAbstractListModel, but the display with many columns compatible with default delegate viewing favors a QAbstractTableModel..
So I don't really know which design to go for. Any experienced help connecting the dots is very welcome :)
QDataWidgetMapper is a slightly different thing. It is a way to display one item from a Model (ex. QStandardItemModel), using custom controls. You can read more about it here, with accompanying snapshots and an example of how to implement one.
While it is certainly cool, I don't think it is what you want here. Mostly because you specified that you want to view your items in a list format. However, you could display all your items in a simple list, double-click which would open a dialog using the QDataWidgetMapper. In which case all you would need to do with a QListView/QListWidget is implement the double-click event.
Still, I personally don't like the added burden of the extra window on a user. I prefer to use popups sparingly. But if you like that approach, then go ahead. This is another example of the QDataWidgetMapper which is pretty nice.
My preferred approach is still to use the QTableView, and provide delegates for the columns that need specialized editing. Here is a great walk-through of all things Model/View. So if you decide to use the QListView or QTableView it will give you a great start. It also talks about how you can create delegates to edit fields however you want.
So, how do you create a custom delegate? Basically, you just inherit from QItemDelegate. There are some examples in the link above, but I'll highlight a few salient points.
QWidget *ComboBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &index) const
{
QComboBox *editor = new QComboBox (parent);
// Add items to the combobox here.
// You can use the QModelIndex passed above to access the model
// Add find out what country was selected, and therefore what cities
// need to be listed in the combobox
return editor;
}
void ComboBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QComboBox *comboBox= static_cast<QComboBox *>(editor);
int _SelectedItem = // Figure out which is the currently selected index;
comboBox->setCurrentIndex(_SelectedItem);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QComboBox *comboBox= static_cast<QComboBox *>(editor);
comboBox->interpretText();
int value = comboBox->currentIndex();
// Translate the current index to whatever you actually want to
// set in your model.
model->setData(index, value, Qt::EditRole);
}
Fill in the gaps that I left in my example and you have your Delegate.
Now, how to use this in your QTableView:
You can set a delegate for a particular column of your table as follows:
setItemDelegateForColumn(_ColumnIndex, new ComboBoxDelegate(_YourTableModel));
And, if you want to prevent certain columns from being editable:
_YourTableModel->setColumnEditable(_ColumnIndex, false);
Once you have your model set up, everything else should take care of itself.
Hope that helps.
First, you should subclass QAbstractItemDelegate (or QItemDeleage, that could be more convenient), where reimplement createEditor, setEditorData and setModelData.
Than, you should set your own itemDelegate (see QAbstractItemView::setItemDelegate).
It's commonly no difference, what widget to use to present data: It could be either QTreeWidet, or QTreeView, or QTableWidget, or QTableView. Note, that "widgets" are easier to use, than "views", but they are not so powerfull
I just finished something very similar to this in that I needed multiple fields for each object which I wanted in a row. The way I did it, which worked out very well, was to subclass QAbstractListmodel and use a custom ListItem. I kind of faked the columns by having a custom delegate and using some javascript to figure out the size of the largest thing in each field, and then set the column size to that width. I think this is the easiest way of doing it.
For the comment below, zone would inherit from:
class ListItem: public QObject {
Q_OBJECT
public:
ListItem(QObject* parent = 0) : QObject(parent) {}
virtual ~ListItem() {}
virtual QString id() const = 0;
virtual QVariant data(int role) const = 0;
virtual QHash<int, QByteArray> roleNames() const = 0;
signals:
void dataChanged();
};