I have an Impl of QAbstractTableModel, which has a vector<Item> items with all items of the table (one Item per Row)
int rowCount(..){ return items.size() }
QVariant data(const QModelIndex &index, int role){
Item i = items.at(index.row()));
switch(index.column()){
case 0: return i.zero;
(..)
}
}
Now I try to implement an add_row(Item i) Method:
void add_row(Item i){
int row = rowCount(QModelIndex())+1;
beginInsertRows(QModelIndex(), row, row);
items.push_back(i);
endInsertRows();
emit dataChanged(QModelIndex(), QModelIndex());
}
The QAbstractTableModel is instanciated in my widget:
MyQSortFilterProxyModel my_filter = new MyQSortFilterProxyModel();
MyTableModel* my_model = new MyTableModel(items, this);
my_filter->setSourceModel(my_model);
ui->my_table->setModel(my_filter);
and I like to add a new row like that:
my_model->add_item(item);
ui->my_table->reset();
This almost works fine with just one problem. The item is added to my table at the correct position and shows up correctly, but the last row always disappears.
Where am I doing something wrong? Why does the last row always disappear?!
Thanks!
Update:
void add_row(Item i){
int row = rowCount(QModelIndex())+1;
beginInsertRows(QModelIndex(), row, row);
items.push_back(i);
endInsertRows();
emit dataChanged(createIndex(0,0), createIndex(row,5));
}
this works but always returns the following error:
QSortFilterProxyModel: invalid inserted rows reported by source model
Without the FilterProxyModel everything works find. What am I missing in the FilterProxyModel?
Related
I have my custom ItemModel and ItemDelegate:
class ItemModel : public QAbstractListModel {
Q_OBJECT
public:
// return items_.size();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
// return items_[index.row()];
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
void Insert(const QVector<QString> &data);
private:
QVector<QString> items_;
};
void ItemModel::Insert(const QVector<QString> &data) {
// my question is the 'first' and 'last' args of beginInsertRows
beginInsertRows(QModelIndex(), 0, 0);
items_.insert(items_.begin(), data.begin(), begin.end());
endInsertRows();
}
From Qt Documentation, it say beginInsertRows has three args:
void QAbstractItemModel::beginInsertRows(const QModelIndex &parent, int first, int last)
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.
I am not sure how to pass last and first, in my ItemModel::Insert, whatever the size of the inserted data is 0 or 10 or other count, I pass first = 0 and last = 0, the view work correctly. When I insert 10 items, and pass first = 0 and last = 9, the view work correctly too. It confuse me.
void ItemModel::Insert() {
beginInsertRows(QModelIndex(), 0, 0);
for(int i = 0; i < 10; ++i) {
items_.push_back(QString::number(i);
}
endInsertRows();
}
// or
void ItemModel::Insert() {
beginInsertRows(QModelIndex(), 0, 9);
for(int i = 0; i < 10; ++i) {
items_.push_back(QString::number(i));
}
endInsertRows();
}
0, 0 is not correct because "first and last are the row numbers that the new rows will have after they have been inserted." The view may still look correct using these parameters but there might be problems from it you haven't seen yet.
0, 9 is correct, but only the first time Insert() is called, because you are adding the new numbers to the end. You need to add items_.size() to both parameters. I.e.:
beginInsertRows(QModelIndex(), items_.size(), items_.size() + 9);
As the doc you highlighted says, you need to call those two functions when you reimplement the virtual function QAbstractItemModel::insertRows
https://doc.qt.io/qt-5/qabstractitemmodel.html#insertRows
Which is obviously not what you are doing in your ItemModel::Insert function.
I'm trying to implement a qTableView structure.
Here's part of my code:
m_model = new PatientModel(this); //This is my QAbstractTableModel subclass
m_proxy = new QSortFilterProxyModel(this);
m_proxy->setSourceModel(m_model);
To append a row I say (I want to display patient objects):
void PatientModel::append(Patient* patient) {
beginInsertRows(QModelIndex(), m_data.count(), m_data.count());
m_data.append(patient);
endInsertRows();
}
Which works fine. The row gets added to the view and the data (m_data is a QList of
To remove a row I tried several things, at the moment I have this
bool PatientModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
this->layoutAboutToBeChanged();
beginRemoveRows(QModelIndex(), row, row+count-1);
m_data.removeAt(row);
endInsertRows();
this->layoutChanged(); //force refresh, keine Ahnung
return true;
}
I added the layoutAboutTobeChanged() and the layoutChanged() after some research. Before adding these 2 lines there was an empty row after deleting. Now there is not but when I remove line 3 for example I can't first click on Line 3+ anymore or the app will crash with the following error message:
QSortFilterProxyModel: index from wrong model passed to mapFromSource
Segmentation fault: 11
Is there anything I'm doing wrong?
Thanks in advance!
Nevermind, I guess I did something wrong. Changed RemoveRows to this and now it works:
bool PatientModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
beginRemoveRows(QModelIndex(), row, row+count-1);
for (int i=0; i < count; ++i) {
m_data.removeAt(row);
}
endRemoveRows();
return true;
}
I have a model that holds a sorted list of integers. New entries to the model look something like this:
if (!causesCollision(iCode))
{
beginInsertRows(QModelIndex(), this->rowCount(), this->rowCount());
this->_codes.append(iCode);
qSort(this->_codes);
endInsertRows();
return true;
}
I need the QListView that uses this model to automatically highlight new entries to the model, but currently I am unable to find the index of newly created row. this->_codes is a QList<int>.
First, I tried the rowsInserted(...) signal, but it only reports that a row was added at the end, not the position within the list that the new item was added.
I've tried something like this:
int iRow = this->_codes.indexOf(iCode);
QModelIndex index = this->index(iRow, 0);
emit ModelChanged(index);
With the ModelChanged(index) signal connected to the QListView's setCurrentIndex(index) slot, but it did not work.
If this->_codes is a list, you can easily get the index of the item you inserted after sorting. I would rewrite your insertion code in the following way:
if (!causesCollision(iCode))
{
// Calculate the insertion position in the list.
int row = 0;
while (row < _codes.size() && _codes.at(row) < iCode) {
row++;
}
beginInsertRows(QModelIndex(), row, row + 1);
_codes.insert(row, iCode); // Always insert sorted.
endInsertRows();
return true;
}
After this the rowsInserted() signal will indicate the right position where the new row has been inserted.
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.
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