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:

    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.


 

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