QDataWidgetMapper,数据Model与组件属性的绑定

0.前言

QDataWidgetMapper类提供数据model与widget属性之间的映射,可以看成是Qt ItemView的Item现在成了一个已有的widget组件,并且通过绑定该widget的属性来达到获取和设置对应值的功能,以此完成与model数据的映射。如果你会QML的话,会发现这就类似于QML的属性绑定。

Qt属性系统是基于Qt元对象系统的,一个属性可以使用函数QObject::property()和QObject::setProperty()进行读写,不用知道属性所在类的任何细节,除了属性的名字。

可以在QtCreator中搜mapper的示例,本文不讲解该类的API。

本文代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/TestQt_20200615_DataMapper

1.简单的例子

先在Ui上拖了三个展示的组件

然后参照示例,一顿操作猛如虎,就映射好了

void MainWindow::initQtMapper()
{
    //QDataWidgetMapper提供数据model与widget属性之间的映射
    //示例搜mapper有简单的例子
    QDataWidgetMapper *mapper=new QDataWidgetMapper(this);
    //可以属性改变时更新到model,或者手动submit
    mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);

    //mapper是靠属性来获取和设置值的
    //combobox没有items属性,就展示切换index吧
    //也可以派生给combox加个items属性
    QStringList items{"A","B","C"};
    ui->comboBox->addItems(items);

    //QDataWidgetMapper接受QAbstractItemModel作为model
    //这里我使用派生类QStandardItemModel
    QStandardItemModel *model=new QStandardItemModel;
    for(int row=0;row<3;row++)
    {
        QStandardItem *item1=new QStandardItem;
        item1->setData(items.at(row),Qt::DisplayRole);
        model->setItem(row,0,item1);
        QStandardItem *item2=new QStandardItem;
        item2->setData("Test"+QString::number(row),Qt::DisplayRole);
        model->setItem(row,1,item2);
        QStandardItem *item3=new QStandardItem;
        item3->setData(10+row,Qt::DisplayRole);
        model->setItem(row,2,item3);
    }
    mapper->setModel(model);

    //关联widget
    //可以指定绑定的属性
    mapper->addMapping(ui->comboBox,0,"currentText");
    mapper->addMapping(ui->lineEdit,1);
    mapper->addMapping(ui->spinBox,2);
    mapper->toFirst();

    //保存修改
    connect(ui->btnSave,&QPushButton::clicked,mapper,&QDataWidgetMapper::submit);
    //上一section
    connect(ui->btnPrev,&QPushButton::clicked,mapper,&QDataWidgetMapper::toPrevious);
    //下一section
    connect(ui->btnNext,&QPushButton::clicked,mapper,&QDataWidgetMapper::toNext);
    //打印Model
    connect(ui->btnModel,&QPushButton::clicked,this,[this,model]{
        for(int row=0;row<model->rowCount();row++)
        {
            qDebug()<<model->data(model->index(row,0),Qt::DisplayRole)
                   <<model->data(model->index(row,1),Qt::DisplayRole)
                  <<model->data(model->index(row,2),Qt::DisplayRole);
        }
    });
}

2.研究下源码

当我们添加映射的时候,他会放到一个容器中

void QDataWidgetMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName)
{
    Q_D(QDataWidgetMapper);

    removeMapping(widget);
    d->widgetMap.push_back({widget, section, d->indexAt(section), propertyName});
    widget->installEventFilter(d->delegate);
}

【A.model数据怎么传递给组件】

调用toFirst、toNext之类的函数时会调用setCurrentIndex

void QDataWidgetMapper::setCurrentIndex(int index)
{
    Q_D(QDataWidgetMapper);

    if (index < 0 || index >= d->itemCount())
        return;
    d->currentTopLeft = d->orientation == Qt::Horizontal
                            ? d->model->index(index, 0, d->rootIndex)
                            : d->model->index(0, index, d->rootIndex);
    d->populate();

    emit currentIndexChanged(index);
}

此时会根据容器里的关联对象将model值设置给widget 

void QDataWidgetMapperPrivate::populate()
{
    for (WidgetMapper &e : widgetMap)
        populate(e);
}
void QDataWidgetMapperPrivate::populate(WidgetMapper &m)
{
    if (m.widget.isNull())
        return;

    m.currentIndex = indexAt(m.section);
    if (m.property.isEmpty())
        delegate->setEditorData(m.widget, m.currentIndex);
    else
        m.widget->setProperty(m.property, m.currentIndex.data(Qt::EditRole));
}

 【B.组件属性值怎么设置给model】

submit的时候会把widget绑定属性的数据设置给model

bool QDataWidgetMapper::submit()
{
    Q_D(QDataWidgetMapper);

    for (auto &e : d->widgetMap) {
        if (!d->commit(e))
            return false;
    }

    return d->model->submit();
}
bool QDataWidgetMapperPrivate::commit(const WidgetMapper &m)
{
    if (m.widget.isNull())
        return true; // just ignore

    if (!m.currentIndex.isValid())
        return false;

    // Create copy to avoid passing the widget mappers data
    QModelIndex idx = m.currentIndex;
    if (m.property.isEmpty())
        delegate->setModelData(m.widget, model, idx);
    else
        model->setData(idx, m.widget->property(m.property), Qt::EditRole);

    return true;
}

自动submit借助的delegate,配合eventFilter让delegate过滤一些如回车之类的操作

void QDataWidgetMapper::setItemDelegate(QAbstractItemDelegate *delegate)
{
    Q_D(QDataWidgetMapper);
    QAbstractItemDelegate *oldDelegate = d->delegate;
    if (oldDelegate) {
        disconnect(oldDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(_q_commitData(QWidget*)));
        disconnect(oldDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)),
                   this, SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)));
    }

    d->delegate = delegate;

    if (delegate) {
        connect(delegate, SIGNAL(commitData(QWidget*)), SLOT(_q_commitData(QWidget*)));
        connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)),
                SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)));
    }

    d->flipEventFilters(oldDelegate, delegate);
}

void QDataWidgetMapperPrivate::flipEventFilters(QAbstractItemDelegate *oldDelegate,
                          QAbstractItemDelegate *newDelegate) const
{
        for (const WidgetMapper &e : widgetMap) {
            QWidget *w = e.widget;
            if (!w)
                continue;
            w->removeEventFilter(oldDelegate);
            w->installEventFilter(newDelegate);
        }
}

3.简单的实现

参照Qt的Mapper,我做了一个简单的Mapper,没有delegate,只有手动submit提交修改。

#ifndef MYMAPPER_H
#define MYMAPPER_H

#include <QObject>
#include <QWidget>
#include <QStandardItemModel>

//表示一个绑定关系的结构体
struct MapperItem
{
    //widget通过属性获取和设置值
    QWidget *widget;
    QByteArray property;
    //model通过column和role获取和设置值
    int column;
};

//自定义mapper,手动设置和获取值
class MyMapper : public QObject
{
    Q_OBJECT
public:
    explicit MyMapper(QObject *parent = nullptr);

    //设置数据model
    void setModel(QStandardItemModel *model);
    //设置映射的widget
    void addMapping(QWidget *w, int column, const QByteArray &property);
    //设置model当前映射的row
    void setRow(int row);
    void toFirst();
    void toNext();
    void toPrev();
    //更改model
    void submit();

private:
    //mapper列表
    QList<MapperItem> mapperList;
    //model
    int currentRow=-1;
    QStandardItemModel *model=nullptr;
};

#endif // MYMAPPER_H
#include "MyMapper.h"

MyMapper::MyMapper(QObject *parent) : QObject(parent)
{

}

void MyMapper::setModel(QStandardItemModel *model)
{
    this->model=model;
}

void MyMapper::addMapping(QWidget *w, int column, const QByteArray &property)
{
    mapperList.push_back(MapperItem{w,property,column});
}

void MyMapper::setRow(int row)
{
    if(row>=0&&model&&model->rowCount()>row){
        for(MapperItem & item:mapperList){
            //将model的值设置给widget属性
            item.widget->setProperty(item.property,model->data(model->index(row,item.column),Qt::DisplayRole));
        }
    }
}

void MyMapper::toFirst()
{
    currentRow=0;
    setRow(currentRow);
}

void MyMapper::toNext()
{
    if(model&&model->rowCount()>currentRow+1){
        ++currentRow;
        setRow(currentRow);
    }
}

void MyMapper::toPrev()
{
    if(currentRow>0){
        --currentRow;
        setRow(currentRow);
    }
}

void MyMapper::submit()
{
    if(currentRow>=0&&model&&model->rowCount()>currentRow){
        for(MapperItem & item:mapperList){
            //将widget的属性值设置到model
            model->setData(model->index(currentRow,item.column),item.widget->property(item.property),Qt::EditRole);
        }
    }
}

 

void MainWindow::initMyMapper()
{
    MyMapper *mapper=new MyMapper(this);

    //mapper是靠属性来获取和设置值的
    //combobox没有items属性,就展示切换index吧
    //也可以派生给combox加个items属性
    QStringList items{"A2","B2","C2"};
    ui->comboBox->addItems(items);

    //model
    QStandardItemModel *model=new QStandardItemModel;
    for(int row=0;row<3;row++)
    {
        QStandardItem *item1=new QStandardItem;
        item1->setData(items.at(row),Qt::DisplayRole);
        model->setItem(row,0,item1);
        QStandardItem *item2=new QStandardItem;
        item2->setData("Test"+QString::number(row),Qt::DisplayRole);
        model->setItem(row,1,item2);
        QStandardItem *item3=new QStandardItem;
        item3->setData(10+row,Qt::DisplayRole);
        model->setItem(row,2,item3);
    }
    mapper->setModel(model);

    //关联widget
    //可以指定绑定的属性
    mapper->addMapping(ui->comboBox,0,"currentText");
    mapper->addMapping(ui->lineEdit,1,"text");
    mapper->addMapping(ui->spinBox,2,"value");
    mapper->toFirst();

    //保存修改
    connect(ui->btnSave,&QPushButton::clicked,mapper,&MyMapper::submit);
    //上一section
    connect(ui->btnPrev,&QPushButton::clicked,mapper,&MyMapper::toPrev);
    //下一section
    connect(ui->btnNext,&QPushButton::clicked,mapper,&MyMapper::toNext);
    //打印Model
    connect(ui->btnModel,&QPushButton::clicked,this,[this,model]{
        for(int row=0;row<model->rowCount();row++)
        {
            qDebug()<<model->data(model->index(row,0),Qt::DisplayRole)
                   <<model->data(model->index(row,1),Qt::DisplayRole)
                  <<model->data(model->index(row,2),Qt::DisplayRole);
        }
    });
}

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
QSqlTableModel和QDataWidgetMapper可以一起使用,将数据库表中的数据绑定到控件上,以便用户可以直接在控件上编辑数据,然后将更改保存回数据库。 具体步骤如下: 1. 创建一个QSqlTableModel对象,并设置其表名、数据库连接等属性。 2. 创建需要绑定的控件,比如QLineEdit、QComboBox等。 3. 创建一个QDataWidgetMapper对象,并将其与QSqlTableModel对象绑定。 4. 将每个控件与QDataWidgetMapper对象中的一个列绑定。 5. 调用QDataWidgetMapper的toFirst()方法,将第一行数据显示在控件上。 6. 当用户编辑控件上的数据时,调用QDataWidgetMapper的submit()方法,将更改保存回数据库。 示例代码如下: ```python # 创建一个QSqlTableModel对象 model = QSqlTableModel() model.setTable("person") model.setEditStrategy(QSqlTableModel.OnManualSubmit) # 手动提交更改 model.select() # 创建需要绑定的控件 nameEdit = QLineEdit() ageSpin = QSpinBox() genderCombo = QComboBox() genderCombo.addItems(["Male", "Female"]) # 创建一个QDataWidgetMapper对象,并将其与QSqlTableModel对象绑定 mapper = QDataWidgetMapper() mapper.setModel(model) # 将每个控件与QDataWidgetMapper对象中的一个列绑定 mapper.addMapping(nameEdit, 0) mapper.addMapping(ageSpin, 1) mapper.addMapping(genderCombo, 2) # 调用QDataWidgetMapper的toFirst()方法,将第一行数据显示在控件上 mapper.toFirst() # 当用户编辑控件上的数据时,调用QDataWidgetMapper的submit()方法,将更改保存回数据库 nameEdit.editingFinished.connect(mapper.submit) ageSpin.editingFinished.connect(mapper.submit) genderCombo.currentIndexChanged.connect(mapper.submit) ``` 注意:使用QSqlTableModel和QDataWidgetMapper绑定控件时,需要手动提交更改,即将QSqlTableModel的编辑策略设置为QSqlTableModel.OnManualSubmit。否则,当用户编辑控件上的数据时,更改会立即保存回数据库,而不是等待用户确认后再保存。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值