Questions regarding QAbstractTableModel and QDataWidgetMapper
-
I was able to show a single row of data in a tableView and then I was trying to use QDataWidgetMapper to map the data both on a tableView and lineEdit. So that when I change the data in lineEdit, the table will reflect the changes as well (am I right about the function of QDataWidgetMapper here?)
Code of constructing a model for Table and Mapping:
amodel.h
#ifndef AMODEL_H #define AMODEL_H #include <QAbstractTableModel> class aModel:public QAbstractTableModel { Q_OBJECT public: aModel(QObject *parent = nullptr); // to add data to the model void populateData(const QList<QString> &contactName,const QList<QString> &contactPhone); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; private: QList<QString> tm_contact_name; QList<QString> tm_contact_phone; }; #endif // AMODEL_H
amodel.cpp
#include "amodel.h" aModel::aModel(QObject *parent) : QAbstractTableModel (parent) { } // Create a method to populate the model with data: void aModel::populateData(const QList<QString> &contactName,const QList<QString> &contactPhone) { tm_contact_name.clear(); tm_contact_name = contactName; tm_contact_phone.clear(); tm_contact_phone = contactPhone; return; } int aModel::rowCount(const QModelIndex &parent) const{ Q_UNUSED(parent); return tm_contact_name.length(); //return 2; } int aModel::columnCount(const QModelIndex &parent) const{ Q_UNUSED(parent); return 2; //return 3; } QVariant aModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || role != Qt::DisplayRole) { return QVariant(); } if (index.column() == 0) { return tm_contact_name[index.row()]; } else if (index.column() == 1) { return tm_contact_phone[index.row()]; } return QVariant(); } QVariant aModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { if (section == 0) { return QString("Name"); } else if (section == 1) { return QString("Phone"); } } return QVariant(); }
Mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QLabel> #include <algorithm> #include <QLineEdit> #include <QDataWidgetMapper> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QList<QString> contactNames; QList<QString> contactPhoneNums; contactNames.append("Thomas"); contactPhoneNums.append("123-456-7890"); aModel *PhoneBookModel = new aModel(this); PhoneBookModel->populateData(contactNames,contactPhoneNums); ui->tableView->setModel(PhoneBookModel); ui->tableView->horizontalHeader()->setVisible(true); ui->tableView->show(); QDataWidgetMapper *mapper = new QDataWidgetMapper; mapper->setOrientation(Qt::Horizontal); mapper->setModel(PhoneBookModel); mapper->addMapping(ui->lineEdit,0); mapper->addMapping(ui->lineEdit_2,1); mapper->toFirst(); } MainWindow::~MainWindow() { delete ui; }
This is the table and 2 lineEdits:
From the code, it seems like I have mapped the data in model to those lineEdits. Should the data have been shown in lineEdit? or it was not mapped at all?
-
@BadPistol97 said in Questions regarding QAbstractTableModel and QDataWidgetMapper:
Should the data have been shown in lineEdit?
No, you are missing 1 step (connecting the view to the mapper) described here: https://doc.qt.io/qt-5/qdatawidgetmapper.html#setCurrentModelIndex
P.S.
populateData
is wrong. When you add/remove rows or change the content of the model you have to signal it. See https://doc.qt.io/QT-5/qabstractitemmodel.html#subclassing -
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.