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
先在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;rowrowCount();row++)
{
qDebug()<data(model->index(row,0),Qt::DisplayRole)
<data(model->index(row,1),Qt::DisplayRole)
<data(model->index(row,2),Qt::DisplayRole);
}
});
}
当我们添加映射的时候,他会放到一个容器中
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);
}
}
参照Qt的Mapper,我做了一个简单的Mapper,没有delegate,只有手动submit提交修改。
#ifndef MYMAPPER_H
#define MYMAPPER_H
#include
#include
#include
//表示一个绑定关系的结构体
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 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;rowrowCount();row++)
{
qDebug()<data(model->index(row,0),Qt::DisplayRole)
<data(model->index(row,1),Qt::DisplayRole)
<data(model->index(row,2),Qt::DisplayRole);
}
});
}