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