Questions regarding QAbstractTableModel and QDataWidgetMapper
-
Not wanting to be too OT to the question. But: I have been using Qt model/views for two years and until now I had no idea that it had a
QDataWidgetMapper
class, so that I could link my view-tables to dedicated, distinct widgets for editing each row, which I need. Not knowing, I have written explicit, separate code for all these widgets. The only mention in the overview https://doc.qt.io/qt-5/model-view-programming.html I now see is in the The Model/View Classes list at the foot. I have not come across the class mentioned in all my reading about the views I use.This is a shame :(
-
@VRonin said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
No, you are missing 1 step (connecting the view to the mapper) described here: https://doc.qt.io/qt-5/qdatawidgetmapper.html#setCurrentModelIndex
Okay, now I added code:
QDataWidgetMapper *mapper = new QDataWidgetMapper; mapper->setOrientation(Qt::Horizontal); mapper->setModel(PhoneBookModel); // added this line connect(ui->tableView->selectionModel(),SIGNAL(currentColumnChanged(QModelIndex,QModelIndex)), mapper,SLOT(setCurrentModelIndex(QModelIndex))); mapper->addMapping(ui->lineEdit,0); mapper->addMapping(ui->lineEdit_2,1); mapper->toFirst();
but it says:
-
- why did you use
currentColumnChanged
instead ofcurrentRowChanged
as suggested by the documentation? - the error you see has nothing to do with the code above, it refers
dataChanged
at line 362 - Use the new connection syntax to have those errors checked at compile time and solved faster
- why did you use
-
@SGaist said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
Besides the point made by @VRonin, you are only returning something for DisplayRole you need to also do that for EditRole.
I guessed you and @VRonin were referring this
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;So I added the function like this in amodel.cpp:
bool aModel::setData(const QModelIndex &index, const QVariant &value, int role ){ if(role == Qt::EditRole){ if(!checkIndex(index)) return false; QString m_gridData[this->rowCount()][this->columnCount()]; m_gridData[index.row()][index.column()] = value.toString(); QString result; for(int row = 0; row < this->rowCount(); row++){ for(int col=0; col < this->columnCount(); col++) result += m_gridData[row][col] + " "; } emit editComplete(result); return true; } return false; }
am I correct?
-
@VRonin Alright, I have changed the line to:
connect( ui->tableView->selectionModel(),&QItemSelectionModel::currentRowChanged, mapper,&QDataWidgetMapper::setCurrentModelIndex);
the data in tableView is still not shown in those lineEdits though.
-
@BadPistol97 said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
@SGaist said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
Besides the point made by @VRonin, you are only returning something for DisplayRole you need to also do that for EditRole.
I guessed you and @VRonin were referring this
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;So I added the function like this in amodel.cpp:
// snip am I correct?
No, you are wrong. I was talking about your "data" method.
Just return the same stuff for display and edit roles and you will be good to go.
-
P.S. to @SGaist post:
- Your
setData
implementation is wrong, you are not emittingdataChanged
. - As with anybody approaching model/view programming, I always suggest, instead of subclassing a model which is quite hard, just use something like
QAbstractItemModel* model = new QStandardItemModel(GiveMeAParent);
. This way you can have a model that works out of the box and you can always change it later to something more efficient without changing existing code
- Your
-
Alright, I have changed the if condition in
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; to:
if (!index.isValid() || role != Qt::DisplayRole && Qt::EditRole)Now the LineEdits show the data from tableView.
-
@VRonin said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
just use something like QAbstractItemModel* model = new QStandardItemModel(GiveMeAParent);.
May I know what will be the parent here?
-
@VRonin said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
Your setData implementation is wrong, you are not emitting dataChanged
Okay, I have finally found a clear documentation about this:
-
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-h.htm
-
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-cpp.html
And have changed the setData(const QModelIndex &index, const QVariant &value, int role) to:
bool aModel::setData(const QModelIndex &index, const QVariant &value, int role ){ if(index.isValid() && role == Qt::EditRole){ int row = index.row(); // contacts is a c++ struct auto contact = contacts.value(row); if (index.column() == 0) contact.name = value.toString(); else if (index.column() == 1) contact.phone = value.toString(); else return false; contacts.replace(row,contact); emit dataChanged(index,index,{role}); return true; } return false; }
Now the LineEdits can change the data of the tableView when new data is keyed in .
-
-
Now if I want to add one more row to the table and 2 more lineEdits for the new row in the table:
How to map the new row to the new LineEdits by using the same mapper?
-
Update the mapper current index.
-
I found that no matter I put toFirst(), toNext(), or toLast() the LineEdits being mapped by the mapper always contain the data from the first row of the table.
mapper->setOrientation(Qt::Horizontal); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setModel(PhoneBookModel); connect( ui->tableView->selectionModel(),&QItemSelectionModel::currentRowChanged, mapper,&QDataWidgetMapper::setCurrentModelIndex); mapper->addMapping(ui->lineEdit,0); mapper->addMapping(ui->lineEdit_2,1); mapper->toLast();
What did I do wrong?
-
When are you adding data to your model ?
-
I added data to the model before the tableView set to that model:
aModel *PhoneBookModel = new aModel(this); PhoneBookModel->setContactsInfo("Thomas","123-456-7890"); PhoneBookModel->setContactsInfo("Richard","222-333-4444"); ui->tableView->setModel(PhoneBookModel); ui->tableView->horizontalHeader()->setVisible(true); ui->tableView->show();
Then I mapped the data of the model to lineEdits.
-
Did you already took a look at the Simple Widget Mapper Example ?
-
I did take a look at it before, but the example uses only 3 widgets for all rows of the table.
I wanted to make the number of widgets flexible:
As in the first card has different number of lineEdits compared to the second card when expanded.
I am thinking of using a different mapper here, set to the same model and map to different widgets:
PhoneBookModel->setContactsInfo("Thomas","123-456-7890"); PhoneBookModel->setContactsInfo("Richard","222-333-4444"); mapper->setModel(PhoneBookModel); connect( ui->tableView->selectionModel(),&QItemSelectionModel::currentRowChanged, mapper,&QDataWidgetMapper::setCurrentModelIndex); mapper->addMapping(ui->lineEdit,0); mapper->addMapping(ui->lineEdit_2,1); mapper->toFirst(); // another mapper QDataWidgetMapper *anotherMapper = new QDataWidgetMapper(this); anotherMapper->setModel(PhoneBookModel); connect( ui->tableView->selectionModel(),&QItemSelectionModel::currentRowChanged, anotherMapper,&QDataWidgetMapper::setCurrentModelIndex); // toLast() doesn't work as what I thought it would anotherMapper->toLast(); anotherMapper->addMapping(ui->lineEdit_3,0); anotherMapper->addMapping(ui->lineEdit_4,1);
The code above doesn't work as the toLast() doesn't work as what I thought it would; changing the row currently pointing to in a model.