Questions regarding QAbstractTableModel and QDataWidgetMapper
-
- 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.
-
How do you build these different widgets ?
-
should be something like:
#include <QLineEdit> #include <QStyledItemDelegate> #include <QFormLayout> #include <QPainter> #include <QAbstractItemModel> #include <QRegularExpressionValidator> class ContactEditor : public QWidget{ Q_OBJECT Q_DISABLE_COPY(ContactEditor) public: explicit ContactEditor(QWidget* parent = nullptr) :QWidget(parent) , m_nameEdit(new QLineEdit(this)) , m_phoneEdit(new QLineEdit(this)) { QFormLayout* mainLay = new QFormLayout(this); mainLay->addRow(tr("Name"),m_nameEdit); mainLay->addRow(tr("Phone"),m_phoneEdit); auto phoneValidator = new QRegularExpressionValidator(this); phoneValidator->setRegularExpression(QRegularExpression(QStringLiteral("[\\d-]*"))); m_phoneEdit->setValidator(phoneValidator); } QString name() const{return m_nameEdit->text();} void setName(const QString& val) {m_nameEdit->setText(val);} QString phone() const{return m_phoneEdit->text();} void setPhone(const QString& val) {m_phoneEdit->setText(val);} private: QLineEdit* m_nameEdit; QLineEdit* m_phoneEdit; }; template <class T> class WidgetDelegate : public QStyledItemDelegate{ #ifdef Q_COMPILER_STATIC_ASSERT static_assert(std::is_base_of<QWidget,T>::value,"Template argument must be a QWidget"); #endif Q_DISABLE_COPY(WidgetDelegate) public: explicit WidgetDelegate(QObject* parent = Q_NULLPTR) :QStyledItemDelegate(parent) , m_baseWid(new T) {} ~WidgetDelegate(){ delete m_baseWid; } void paint(QPainter *painter, const QStyleOptionViewItem &option,const QModelIndex &index) const Q_DECL_OVERRIDE{ setSubEditorData(m_baseWid,index); m_baseWid->resize(option.rect.size()); QPixmap pixmap(option.rect.size()); m_baseWid->render(&pixmap); painter->drawPixmap(option.rect,pixmap); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE{ Q_UNUSED(option); setSubEditorData(m_baseWid,index); return m_baseWid->sizeHint(); } QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE Q_DECL_FINAL { Q_UNUSED(option); Q_UNUSED(index); T* editor = new T; editor->setParent(parent); return editor; } virtual void setSubEditorData(T* editor, const QModelIndex &index) const =0; void setEditorData(QWidget* editor, const QModelIndex &index) const Q_DECL_OVERRIDE Q_DECL_FINAL { T* subEdit = qobject_cast<T*>(editor); Q_ASSERT(subEdit); return setSubEditorData(subEdit,index); } private: T* m_baseWid; }; class ContactDelegate : public WidgetDelegate<ContactEditor>{ Q_DISABLE_COPY(ContactDelegate) public: explicit ContactDelegate(QObject* parent=Q_NULLPTR) :WidgetDelegate<ContactEditor>(parent) {} void setSubEditorData(ContactEditor* editor, const QModelIndex &index) const Q_DECL_OVERRIDE{ editor->setName( displayText(index.data(),editor->locale())); editor->setPhone(displayText(index.data(Qt::UserRole),editor->locale())); } };
and then you can use it in a delegate like:
int main(int argc, char **argv) { QApplication app(argc,argv); QListWidget wid; wid.setEditTriggers(QAbstractItemView::AllEditTriggers); wid.setItemDelegate(new ContactDelegate(&wid)); wid.model()->insertRows(0,2); wid.model()->setData(wid.model()->index(0,0),"Thomas"); wid.model()->setData(wid.model()->index(0,0),"123456789",Qt::UserRole); wid.model()->setData(wid.model()->index(1,0),"Richard"); wid.model()->setData(wid.model()->index(1,0),"987654321",Qt::UserRole); wid.show(); return app.exec(); }