Qt QAbstractItemModel - item removal crashes - c++

I'm writing an application using Qt classes where I have a hierarchical structure and I need to display it in a tree view (I'm using a QTreeView widget). The data itself look like this:
class StatisticsEntry
{
// additional data management methods
...
bool setData(int column, const QVariant &value)
{
if (column < 0 || column >= _data.size())
return false;
_data[column] = value;
return true;
}
private:
// each item has a parent item except the top-most one
StatisticsEntry *_parent;
// each item may have child items which are accessible through the following member
QList<StatisticsEntry*> _children;
// each item is defined by a set of vallues stored in the following vector
QVector<QVariant> _data;
}
I have a class called it StatisticsModel that implements QAbstractItemModel - use it to manipulate and present the data stored in the StatisticsEntry tree.
The class has a method called addStatisticsData which I use to push StatisticsEntry records into the model. The method roughly looks like this:
QModelIndex StatisticsModel::addStatisticsData(const QString &title, const QString &description, const QModelIndex &parent)
{
int row = rowCount(parent);
if (!insertRow(row, parent))
return QModelIndex();
// Get new item index
QModelIndex child = index(row, 0, parent);
// set item data
setTitle(child, title);
setDescription(child, description);
return child;
}
SetTitle and setDescription methods are identical - here's the setTitle one:
void StatisticsModel::setTitle(const QModelIndex &index, const QString& title)
{
QModelIndex columnIndex = this->index(index.row(), StatColumn::Title, index.parent());
this->setData(columnIndex, title, Qt::EditRole);
}
The setData method is as follows:
bool StatisticsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole)
return false;
int column = index.column();
StatisticsEntry *item = getItem(index);
if (!item->setData(column, value))
return false;
emit dataChanged(index, index);
return true;
}
What is left is the getItem method:
StatisticsEntry *StatisticsModel::getItem(const QModelIndex &index) const
{
if (index.isValid())
{
StatisticsEntry *item = static_cast<StatisticsEntry*>(index.internalPointer());
if (item)
return item;
}
return _rootItem;
}
That is about all there is when speaking of adding new and modifying existing entries. In my application I have implemented QSortFilterProxyModel as well - nothing special, just the lessThan method. I use the proxy model to provide a sort feature in the QTreeView that displays the data. There is the following code that connects the models to the treeview widget:
in the header of the main window:
...
StatisticsModel* _statisticsModel;
StatisticsProxyModel* _statisticsProxyModel;
...
in the constructor of the main widow
...
_statisticsModel = new StatisticsModel(ths);
_statisticsProxyModel = new StatisticsProxyModel(htis);
...
_statisticsProxyModel->setSourceModel(_statisticsModel);
ui->statisticsTreeView->setModel(_statisticsProxyModel);
...
The application also has a button that allows me to remove the selected item from the model - I'm currently only testing with the QTreeView::selectionModel()->currentIndex, no multi-selections for the time being.
I have the following code for the StatisticsModel::removeRows method
bool StatisticsModel::removeRows(int position, int rows, const QModelIndex &parent)
{
StatisticsEntry *parentItem = getItem(parent);
bool success1 = true;
bool success2 = true;
beginRemoveRows(parent, position, position + rows - 1);
for (int i = position + rows - 1; i >= position; i--)
{
QModelIndex child = index(i, 0, parent);
QString title = this->title(child); // the title method is the getter method that matches the setTitle one
success1 = success1 && removeRows(0, rowCount(child), child); //rowCount is implemented to return the number of items stored in StatisticsEntry::_children list for the specified parent index
success2 = success2 && parentItem->removeChild(i); // deletes an entry from the StatisticsEntry::_children list
}
endRemoveRows();
return success1 && success2;
}
The problem is that sometimes when I remove an item using QAbstractItemModel::removeRow method I get an exception and the stack trace looks like:
StatisticsModel::parent StatisticsModel.cpp 307 0x13e7bf8
QModelIndex::parent qabstractitemmodel.h 393 0x72e57265
QSortFilterProxyModelPrivate::source_to_proxy qsortfilterproxymodel.cpp 386 0x711963e2
QSortFilterProxyModel::mapFromSource qsortfilterproxymodel.cpp 2514 0x7119d28b
QSortFilterProxyModel::parent qsortfilterproxymodel.cpp 1660 0x7119a32c
QModelIndex::parent qabstractitemmodel.h 393 0x72e57265
QPersistentModelIndex::parent qabstractitemmodel.cpp 347 0x72e58b86
QItemSelectionRange::isValid qitemselectionmodel.h 132 0x70e3f62b
QItemSelection::merge qitemselectionmodel.cpp 466 0x711503d1
QItemSelectionModelPrivate::finalize qitemselectionmodel_p.h 92 0x7115809a
QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved qitemselectionmodel.cpp 623 0x71151132
QItemSelectionModel::qt_static_metacall moc_qitemselectionmodel.cpp 113 0x711561c2
QMetaObject::activate qobject.cpp 3547 0x72e8d9a4
QAbstractItemModel::rowsAboutToBeRemoved moc_qabstractitemmodel.cpp 204 0x72f08a76
QAbstractItemModel::beginRemoveRows qabstractitemmodel.cpp 2471 0x72e5c53f
QSortFilterProxyModelPrivate::remove_proxy_interval qsortfilterproxymodel.cpp 558 0x71196ce7
QSortFilterProxyModelPrivate::remove_source_items qsortfilterproxymodel.cpp 540 0x71196c7f
QSortFilterProxyModelPrivate::source_items_about_to_be_removed qsortfilterproxymodel.cpp 841 0x71197c77
QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved qsortfilterproxymodel.cpp 1291 0x711995cc
QSortFilterProxyModel::qt_static_metacall moc_qsortfilterproxymodel.cpp 115 0x7119d506
QMetaCallEvent::placeMetaCall qobject.cpp 525 0x72e883fd
QObject::event qobject.cpp 1195 0x72e894ba
QApplicationPrivate::notify_helper qapplication.cpp 4550 0x709d710e
QApplication::notify qapplication.cpp 3932 0x709d4d87
QCoreApplication::notifyInternal qcoreapplication.cpp 876 0x72e6b091
Oddly enough this seems to happen after all immediate method calls concerning the item removal are already over. It seems that the proxy model is looking for model indices that should not be present anymore (or so I think). The StatisticsModel::parent method is as follows:
QModelIndex StatisticsModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
StatisticsEntry *childItem = getItem(index);
StatisticsEntry *parentItem = childItem->parent();
if (NULL != parentItem)
return createIndex(parentItem->childNumber(), 0, parentItem);
return QModelIndex();
}
When the exception happens the values associated with the childItem and parentItem variables from the above method seem invalid - either the pointers themselves point to non-accessible memory or the member QLists either have no entries or their entries give Memory access violation.
It may be that the parent method is not correct, but then how to fetch the parent index - the qabstractItemModel documentation discourages the usage of QModelIndex::parent in that method for it will create an infinite recursion.
Any help would be appreciated,

When doing your StatisticsModel::removeRows, You are nesting begingRemoveRows/endRemoveRows, which, AFAIK, does not work. You should do:
bool StatisticsModel::removeRows(int position, int rows, const QModelIndex &parent)
{
StatisticsEntry *parentItem = getItem(parent);
bool success1 = true;
bool success2 = true;
for (int i = position + rows - 1; i >= position; i--)
{
QModelIndex child = index(i, 0, parent);
QString title = this->title(child); // the title method is the getter method that matches the setTitle one
success1 = success1 && removeRows(0, rowCount(child), child); //rowCount is implemented to return the number of items stored in StatisticsEntry::_children list for the specified parent index
beginRemoveRows(parent, i, i);
success2 = success2 && parentItem->removeChild(i); // deletes an entry from the StatisticsEntry::_children list
endRemoveRows();
}
return success1 && success2;
}

Related

Qt: Updating data in a TableView with QAbstractTableModel

I try to implement a table view with two columns. The right columns shows parameters, which should not be changed by runtime, and left column shows values, that should be updated constantly at runtime. For this I implement a data model (derived from QAbstractTableModel). After setting up this I got a table with 3 rows and 2 columns and the right columns shows the parameters. The left columns however remains empty. And after hours I didn't find a solution for this.
This is the relevant code of my data model I think:
QVariant DataTableModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= m_parameter.size()) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch (index.column()) {
case 0:
return m_parameter.at(index.row());
break;
case 1:
return m_value.at(index.row());
break;
}
}
return QVariant();
}
bool DataTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() && role != Qt::EditRole) {
return false;
}
//change only values in column 1
if (index.column() == 1) {
m_value.replace(index.row(), value.toString());
emit dataChanged(index, index, { role });
return true;
}
return true;
}
bool DataTableModel::insertRows(int row, int count, const QModelIndex& parent)
{
//the following two methods emits signals that tells the view that the data should be changed
beginInsertRows(QModelIndex(), row, row + count - 1);
for (int i = 0; i < count; ++i) {
m_parameter.insert(row, "");
m_value.insert(row, "");
}
endInsertRows();
return true;
}
In the main class I have a function that calles the functions of the model:
void QVideoMeter::QAddTableContent()
{
QVariant value(QValueList());
m_tableDataModel->insertRows(0, 3, QModelIndex()); //insert 3 rows
m_tableDataModel->AddData(QParameterList());
m_tableDataModel->setData(QModelIndex(), value, 2);
}
QList<QString> QVideoMeter::QValueList()
{
QList<QString> values;
values.append("Test");
values.append("Hallo");
values.append("Welt");
return values;
}
Please, can someone of you review my code and tell me what I'm doing wrong?
Refer to the below two links. They provide the necessary concepts and examples:
QAbstractItemModel Subclass
Editable Tree Model Example
An editable model needs to provide implementations of setData() and
setHeaderData(), and must return a suitable combination of flags from
its flags() function.
Since this example allows the dimensions of the model to be changed,
we must also implement insertRows(), insertColumns(), removeRows(),
and removeColumns().
void MainWindow::insertRow()
{
const QModelIndex index = view->selectionModel()->currentIndex();
QAbstractItemModel *model = view->model();
if (!model->insertRow(index.row()+1, index.parent()))
return;
updateActions();
for (int column = 0; column < model->columnCount(index.parent()); ++column) {
const QModelIndex child = model->index(index.row() + 1, column, index.parent());
model->setData(child, QVariant(tr("[No data]")), Qt::EditRole);
}
}

Why can't you use setData() to set background color of a Cell in QTreeView?

I'm using the following code to try to change the background color of a cell at a given QModelIndex.
ui->TreeView->model()->setData(index, QVariant(QBrush (QColor(Qt::green))) , Qt::BackgroundRole);
where index is given by the dataChanged() signal.
This isn't working. Any ideas why?
Here's my reimplemented setData function.
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeItem *item = getItem(index); //gets item a given index
bool result = item->setData(index.column(), value);
if (result)
emit dataChanged(index, index);
return result;
}
And here is the setData method for the underlying item:
bool TreeItem::setData(int column, const QVariant &value)
{
if (column < 0 || column >= itemData.size())
return false;
itemData[column] = value;
return true;
}
Apologies for the vague question. I've managed to solve it by myself so I will post here in case anyone is ever stuck on a similar issue.
The problem for me was that I hadn't reimplemented QAbstractItemView's data() method to account for the new role.
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
TreeItem *item = getItem(index);
if (role == Qt::BackgroundRole)
return item->data(index.column());
//and so on...
AFAIK the data() method gives the treeview the data out of the model that it needs to present. Within this method I hadn't accounted for the case when role == Qt::BackgroundRole so the view was never given the appropriate information out of the model.

QTableView and unique IDs

I'm new to Qt and coming from C# .Net. I am trying to replicate a fairly simple program I wrote in C# in Qt as a learning tool. I have a data model that inherits QAbstractTableModel and implements:
rowCount,
columnCount,
data,
setData,
headerData
flags
My data structure is a map
std::map<int, CBDataRow>
So the idea was that each row would have a unique int ID and a struct containing the rest of the row information.
What I am stuck on now is how to update my data model when the user makes an edit in the QTableView object. The setData function does get called. Here it is:
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole) {
success = m_data.UpdateRow(index, value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
Now you see that the UpdateRow() function gets called here on an edit. That function should find the unique id in the map and update the appropriate members of its CBDataRow struct. My problem is that I have no idea how to get the unique ID out of the QModelIndex object that gets passed into the edit function.
For example:
User edits the "CB Name" cell of row 3. The data in row three has a unique ID of 100. That value of 100 is in the QTableView in a hidden column, column index 0. So what I need to do is simply:
(Psuedo code)
it = m_data.find(unique_id);
it->second.cb_name = value.toString();
Since the user was editing column 1, how do i find the unique ID that is contained in column 0?
I would recommend to reimplement index() method of your model and there create indexes by using the call createIndex(row,col, unique_id);
Then in any place where you got QModelIndex, you can always extract unique_id = model_index.internalId();
In my opinion you can store your data in an array and index your element simply accessing through index.row():
QVector<CBDataRow> m_data;
....
bool CBDatabaseModel::setData(const QModelIndex &index, const QVariant &value, int role) {
bool success = false;
if(role == Qt::EditRole && index.row() < m_data.size()) {
success = m_data.at(index.row()).UpdateRow(index.column(), value);
}
if(success) {
emit dataChanged(index, index);
return true;
} else {
return false;
}
}
if you are worrying about element sorting you can derive your model from a QSortFilterProxyModel (instead of a QAbstractTableModel) and then reimplement
bool CBDatabaseModel::lessThan(const QModelIndex &left,
const QModelIndex &right) const
without define a internal id by yourself.
I hope this can help you.

Insert and delete rows in QTreeView

Good day, I Have base model inherited from QAbstractItemModel, and some background threads which notify this model from time to time, in examples the insertions rows implemens somthing like this
bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
TreeItem *parentItem = getItem(parent);
bool success;
beginInsertRows(parent, position, position + rows - 1);
success = parentItem->insertChildren(position, rows, rootItem->columnCount());
endInsertRows();
return success;
}
But I can't do it like this because my model is single which uses 4 views, I've implemented my insertion this way:
void notifyEventImpl(file_item_type *sender,helper<ITEM_ACTION_ADDED>)
{
base_class::setSize(file_item_type::size()+sender->size());
m_listDirectory.push_back(sender);
file_item_type::filesystem_type::s_notify.insert(this); // notify my model
}
Where s_notify is a class with implementation:
void Notifaer::dataChange(void * item){emit dataChanged(item);}
void Notifaer::remove(void * item){emit removed(item);}
void Notifaer::insert(void * item){emit inserted(item);}
void Notifaer::push_back(const FileItemModel * model)
{
VERIFY(QObject::connect(this,SIGNAL(dataChanged(void*)),model,SLOT(dataChangeItem(void*)) ));
VERIFY(QObject::connect(this,SIGNAL(removed(void*)),model,SLOT(removeItem(void*)) ));
VERIFY(QObject::connect(this,SIGNAL(inserted(void*)),model,SLOT(insertItem(void*)) ));
}
Given this, I invoke the method:
void FileItemModel::insertItem(void *it)
{
file_item_type *item = dynamic_cast<file_item_type*>(static_cast<file_item_type*>(it));
{
QModelIndex index = createIndex(0,0,item);
if (index.isValid())
{
beginInsertRows(index, 0, item->childCount()-1);
endInsertRows();
}
}
}
void FileItemModel::removeItem(void *it)
{
file_item_type *item = static_cast<file_item_type*>(it);
{
QModelIndex index = createIndex(0,0,item);
if (index.isValid())
{
beginRemoveRows(index, 0, item->childCount()-1);
endRemoveRows();
}
}
}
Remove rows works perfectly, but insert does not work. What's wrong in my implementation?
Try with
beginInsertRows(QModelIndex(), 0, item->childCount()-1);
Have you checked QT doc http://qt-project.org/doc/qt-4.8/qabstractitemmodel.html or QT examples to get any clue http://qt-project.org/doc/qt-4.8/itemviews-editabletreemodel.html?
As you said threads, maybe this could be interesting to read:
Design Pattern, Qt Model/View and multiple threads
QTreeView & QAbstractItemModel & insertRow

QSortFilterProxyModel and columnCount

I have 2 models:
MyModel (inherits QAbstractItemModel, its tree) and MyProxyModel (inherits QSortFilterProxyModel).
Column count of MyModel is 1, and items in MyModel contain information which should be showed in QTableView using MyProxyModel. I used MyProxyModel with MyProxyModel::columnCount() == 5.
I overloaded function MyProxyModel::data(). But table view shows only data from column 1(MyModel::columnCount).
After debugging I found that MyProxyModel::data() gets only indexes with column < MyModel::columnCount() (It seems that it uses MyModel::columnCount() and ignores MyProxyModel::columnCount()).
In the table view header sections count is equal to MyProxyModel::columnCount() (It's OK ;)).
How can I show information in cells with column > MyModel::columnCount()?
MyModel.cpp:
int MyModel::columnCount(const QModelIndex& parent) const
{
return 1;
}
MyProxyModel.cpp:
int MyProxyModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
QVariant MyProxyModel::data(const QModelIndex& index, int role) const
{
const int r = index.row(),
c = index.column();
QModelIndex itemIndex = itemIndex = this->index(r, 0, index.parent());
itemIndex = mapToSource(itemIndex);
MyModel model = dynamic_cast<ItemsModel*>(sourceModel());
Item* item = model->getItem(itemIndex);
if(role == Qt::DisplayRole)
{
if(c == 0)
{
return model->data(itemIndex, role);
}
return item->infoForColumn(c);
}
return QSortFilterProxyModel::data(index, role)
}
As Krzysztof Ciebiera had said, in not as many words: your data and columnCount methods are never called, because they are not declared correctly. The virtual methods you're supposed to implement have the signatures
int columnCount(const QModelIndex&) const;
QVariant data(const QModelIndex&, int) const;
while your methods have differing signatures
int columnCount(QModelIndex&) const;
QVariant data(QModelIndex&, int);
and thus they won't get called. Notice that your methods incorrectly expect a non-const reference to a model index. Your data() method also expects non-const object instance.