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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章