Questions regarding QAbstractTableModel and QDataWidgetMapper


  • Qt Champions 2018

    1. why did you use currentColumnChanged instead of currentRowChanged as suggested by the documentation?
    2. the error you see has nothing to do with the code above, it refers dataChanged at line 362
    3. Use the new connection syntax to have those errors checked at compile time and solved faster


  • @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.


  • Lifetime Qt Champion

    @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.


  • Qt Champions 2018

    P.S. to @SGaist post:

    • Your setData implementation is wrong, you are not emitting dataChanged.
    • 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


  • @SGaist

    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

    @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:

    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:
    0_1559262286056_Capture.PNG

    How to map the new row to the new LineEdits by using the same mapper?


  • Lifetime Qt Champion

    Update the mapper current index.



  • @SGaist

    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?


  • Lifetime Qt Champion

    When are you adding data to your model ?



  • @SGaist

    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.


  • Qt Champions 2018

    Stop. Step back.
    It looks like you want 2 labels and 2 line edits for each row in your model. Can I ask why?
    You can edit the items directly inside the view, you don't need external editors



  • @VRonin

    I wanted to make something like this:
    0_1559577164419_Capture.PNG
    This is the page showing table

    0_1559577321826_Capture.PNG
    And another view to edit the record, so I would like to have each column of a row mapped to a lineEdit


  • Lifetime Qt Champion

    Did you already took a look at the Simple Widget Mapper Example ?



  • @SGaist

    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:
    0_1559754900281_Capture.PNG

    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.


  • Lifetime Qt Champion

    How do you build these different widgets ?



  • @SGaist

    in UI :
    0_1559806652589_Capture.PNG

    The first two lineEdits are for the first row of the table. The third and fourth are for the second row of the table.


  • Qt Champions 2018

    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();
    }
    


  • @VRonin @SGaist

    Alright, thanks guys.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.