1. 委托的概念执行过程
在传统的MVC设计模式中,模型Model负责数据组织,视图View负责数据显示,控制器Contorller负责与用户的输入交互。如下图:
在Qt的模型-视图设计模式中对传统的MVC设计模式做了演变:
视图中集成了处理用户的输入功能,将与用户输入交互功能作为视图内部独立的子功能而实现,即委托(Delegate)。委托对象负责创建/显示用户输入上下文,如编辑框的创建和显示,除此,委托作为视图的一部分,还承担了部分内容的显示,关于后者,我们放在后面说,先看委托与用户输入交互部分。委托类的关系图为:
QAbstractItemDelegate定义了委托的功能接口:
QAbstractItemDelegate ( QObject * parent = 0 )
virtual ~QAbstractItemDelegate ()
//编辑数据时调用此函数创建编辑器组件
virtual QWidget *createEditor( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const
//编辑编辑框时产生的事件调用此函数
virtual bool editorEvent ( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index )
//绘制组件
virtual void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const = 0
//通过索引从模型中拿数据并设置到编辑器
virtual void setEditorData ( QWidget * editor, const QModelIndex & index ) const
//将编辑器上的数据写到模型中
virtual void setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const
virtual QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const = 0
//更新编辑器组件的大小,可能用户需要输入的文字较多,原先的大小不适用所以需要调用此函数更新组件大小
virtual void updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
QItemDelegate和QStyledItemDelegate都是标准委托功能的实现类。后者是Qt4.4版本后引入的基类,视图中默认的委托是QStyledItemDelegate,也就是说即不设置视图内的委托类,默认的就是QStyledItemDelegate。建议程序员在自定义委托时最好继承自QStyledItemDelegate,因为QStyledItemDelegate使用了当前的界面风格来绘制组件,即若程序中设置了总体风格,QStyledItemDelegate就会使用这个新建的风格来绘制组件。
下面例子用于验证委托的存在,且委托实现了与用户的交互功能:
//Widget.h
class Widget : public QWidget
{
Q_OBJECT
QTableView m_view; //表格视图
QStandardItemModel m_model; //标准模型
QPushButton m_btn; //按钮
void initView(); //视图初始化函数
void initModel(); //模型初始化函数
private slots:
void onBtnClicked(); //按钮按下槽函数
public:
Widget(QWidget *parent = 0);
~Widget();
};
//Widget.cpp
Widget::Widget(QWidget *parent) : QWidget(parent), m_btn(this)
{
initView();
initModel();
m_view.setModel(&m_model);
m_btn.move(10, 120);
m_btn.resize(300, 30);
m_btn.setText("check");
//连接按钮按下信号和槽
connect(&m_btn, SIGNAL(clicked()), this, SLOT(onBtnClicked()));
}
//模型初始化函数
void Widget::initModel()
{
QStandardItem* root = m_model.invisibleRootItem(); //虚拟根节点数据项
QStandardItem* item_a = new QStandardItem(); //数据项a
QStandardItem* item_b = new QStandardItem();
QStandardItem* item_c = new QStandardItem();
QStandardItem* item_d = new QStandardItem();
//为每个数据项添加数据,数据角色为Qt::DisplayRole
item_a->setData("python", Qt::DisplayRole);
item_b->setData("c++", Qt::DisplayRole);
item_c->setData("c", Qt::DisplayRole);
item_d->setData("kotlin", Qt::DisplayRole);
//将数据项加入(表格)模型
root->setChild(0, 0, item_a);
root->setChild(0, 1, item_b);
root->setChild(1, 0, item_c);
root->setChild(1, 1, item_d);
}
//视图初始化函数
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(300, 100);
}
//按钮按下的槽函数,用于打印模型中的数据项
void Widget::onBtnClicked()
{
qDebug() << "Model Data:";
//遍历行
for (int i = 0; i < m_model.rowCount(); i++)
{
qDebug() << "Row: " << i;
//遍历每一行的每一列
for (int j = 0; j < m_model.columnCount(); j++)
{
//获取索引
QModelIndex index = m_model.index(i, j, QModelIndex());
//通过索引闹到数据项
QString text = index.data().toString();
qDebug() << text;
}
}
}
Widget::~Widget()
{
}
运行:
可见,视图中确有实现与用户交互的委托类的存在,且该类将用户修改后的数据写入编辑框中,同时还会写入模型中。
下面通过自定义继承自标准委托类QStyledItemDelegate的委托类来观察委托类的执行过程。自定义委托类需要重写QStyledItemDelegate类中的函数有:
//用户编辑视图上的数据时调用此函数创建编辑组件
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//通过索引从模型中拿数据并设置到编辑框
void setEditorData(QWidget *editor, const QModelIndex &index) const;
//将编辑框上的数据写到模型中
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const;
//更新编辑组件大小,用户在编辑数时可能原先大小的编辑框不够使用
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
另外委托中还有两个关键信号:
//新数据提交时的信号函数
void commitData(QWidget *editor);
//编辑器组件关闭时的信号函数
void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint = NoHint);
代码实现:
//MyQStyledItemDelegate.h
#ifndef MYQSTYLEDITEMDELEGATE_H
#define MYQSTYLEDITEMDELEGATE_H
#include <QStyledItemDelegate>
class MyQStyledItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit MyQStyledItemDelegate(QObject *parent = 0);
//用户编辑视图上的数据时调用此函数创建编辑组件
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//通过索引从模型中拿数据并设置到编辑框
void setEditorData(QWidget *editor, const QModelIndex &index) const;
//将编辑框上的数据写到模型中
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const;
//更新编辑组件大小,用户在编辑数时可能原先大小的编辑框不够使用
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
public slots:
//编辑器组件关闭时的槽函数
void onCloseEditor(QWidget* editor);
//新数据提交时的槽函数
void onCommitData(QWidget* editor);
};
#endif // MYQSTYLEDITEMDELEGATE_H
//MyQStyledItemDelegate.cpp,重写委托中关键的函数,只是实现打印以验证程序执行流程
#include "MyQStyledItemDelegate.h"
#include <QDebug>
MyQStyledItemDelegate::MyQStyledItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
connect(this, SIGNAL(closeEditor(QWidget*)), this, SLOT(onCloseEditor(QWidget*)));
connect(this, SIGNAL(commitData(QWidget*)), this, SLOT(onCommitData(QWidget*)));
}
//用户编辑视图上的数据时调用此函数创建编辑组件
QWidget *MyQStyledItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
qDebug() << "createEditor";
return QStyledItemDelegate::createEditor(parent, option, index);
}
//通过索引从模型中拿数据并设置到编辑框
void MyQStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
qDebug() << "setEditorData";
QStyledItemDelegate::setEditorData(editor,index);
}
//将编辑框上的数据写到模型中
void MyQStyledItemDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
qDebug() << "setModelData";
QStyledItemDelegate::setModelData(editor, model, index);
}
//更新编辑组件大小,用户在编辑数时可能原先大小的编辑框不够使用
void MyQStyledItemDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
qDebug() << "updateEditorGeometry";
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
//编辑器组件关闭时的槽函数
void MyQStyledItemDelegate::onCloseEditor(QWidget* editor)
{
qDebug() << "slot: onCloseEditor";
}
//新数据提交时的槽函数
void MyQStyledItemDelegate::onCommitData(QWidget* editor)
{
qDebug() << "slot: onCommitData";
}
//Widget.h,
class Widget : public QWidget
{
Q_OBJECT
QTableView m_view;
QStandardItemModel m_model;
QPushButton m_btn;
MyQStyledItemDelegate m_delegate; //窗口组件中新增自定义的成员对象MyQStyledItemDelegate
void initView();
void initModel();
private slots:
void onBtnClicked();
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//Widget.cpp
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(300, 100);
m_view.setItemDelegate(&m_delegate); //设置视图的委托对象为我们自定义的MyQStyledItemDelegate
}
运行:
由运行结果可得:
双击编辑框时调用createEditor()、updateEditorGeometry()、setEditorData();
编辑完毕按下回车时调用onCommitData()槽函数、setModelData()、setEditorData()、onCloseEditor槽函数()。显然符合注释中所写的函数的意义。
2. 自定义委托类实现自定义用于交互的编辑组件
通过前面知道视图中的委托为视图提供了数据编辑的上下文,在用户需要编辑视图上的数据(进而写入模型)时,需要为用于提供一个输入的编辑框,委托就是这么一个通过前面知道视图中的委托为视图提供了数据编辑的上下文,在用户需要编辑视图上的数据(进而写入模型)时,需要为用于提供一个输入的编辑框,委托就是这么一个
视图使用默认的QStyledItemDelegate委托类:
//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QPushButton>
#include <QModelIndex>
#include "MyQStyledItemDelegate.h"
class Widget : public QWidget
{
Q_OBJECT
QTableView m_view;
QStandardItemModel m_model;
QPushButton m_btn;
void initView();
void initModel();
private slots:
void onBtnClicked();
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) : QWidget(parent), m_btn(this)
{
initView();
initModel();
m_view.setModel(&m_model);
//设置每个模型数据项占据显示框的大小
for(int i=0; i<m_model.columnCount(); i++)
{
m_view.setColumnWidth(i, 125);
}
m_btn.move(10, 120);
m_btn.resize(400, 30);
m_btn.setText("check");
connect(&m_btn, SIGNAL(clicked()), this, SLOT(onBtnClicked()));
}
void Widget::initModel()
{
//填充模型
QStandardItem* root = m_model.invisibleRootItem();
QStandardItem* item_a1 = new QStandardItem();
QStandardItem* item_b1 = new QStandardItem();
QStandardItem* item_c1 = new QStandardItem();
QStandardItem* item_a2 = new QStandardItem();
QStandardItem* item_b2 = new QStandardItem();
QStandardItem* item_c2 = new QStandardItem();
QStringList h;
item_a1->setData("c++", Qt::DisplayRole);
item_b1->setData(QChar('A'), Qt::DisplayRole);
item_c1->setData(false, Qt::DisplayRole);
item_a2->setData("ruby", Qt::DisplayRole);
item_b2->setData(QChar('B'), Qt::DisplayRole);
item_c2->setData(true, Qt::DisplayRole);
//设置表格的标题
h.append("Language");
h.append("Level");
h.append("Script");
m_model.setHorizontalHeaderLabels(h);
root->setChild(0, 0, item_a1);
root->setChild(0, 1, item_b1);
root->setChild(0, 2, item_c1);
root->setChild(1, 0, item_a2);
root->setChild(1, 1, item_b2);
root->setChild(1, 2, item_c2);
}
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(400, 100);
}
void Widget::onBtnClicked()
{
qDebug() << "Model Data:";
for (int i = 0; i < m_model.rowCount(); i++)
{
qDebug() << "Row: " << i;
for (int j = 0; j < m_model.columnCount(); j++)
{
QModelIndex index = m_model.index(i, j, QModelIndex());
QString text = index.data().toString();
qDebug() << text;
}
//qDebug() << endl;
}
}
Widget::~Widget()
{
}
运行:
默认的委托,响应用户输入时,对bool类型的数据项采用下拉框,字符/字符串类型采用的是输入框,现在重写委托,让bool类型数据项采用复选框,而字符型采用下拉选项:
//m_model.setHorizontalHeaderLabels.h
#ifndef MYQSTYLEDITEMDELEGATE_H
#define MYQSTYLEDITEMDELEGATE_H
#include <QStyledItemDelegate>
#include <QModelIndex>
class MyQStyledItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
mutable QModelIndex m_index; //记录当前操作的数据项的索引
public:
explicit MyQStyledItemDelegate(QObject *parent = 0);
//用户编辑视图上的数据时调用此函数创建编辑组件
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//通过索引从模型中拿数据并设置到编辑框
void setEditorData(QWidget *editor, const QModelIndex &index) const;
//将编辑框上的数据写到模型中
void setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const;
//更新编辑组件大小,用户在编辑数时可能原先大小的编辑框不够使用
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
//编辑框绘制函数
void paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const;
public slots:
//编辑器组件关闭时的槽函数
void onCloseEditor(QWidget* editor);
//新数据提交时的槽函数
void onCommitData(QWidget* editor);
};
#endif // MYQSTYLEDITEMDELEGATE_H
//MyQStyledItemDelegate.cpp
#include "MyQStyledItemDelegate.h"
#include <QDebug>
#include <QCheckBox>
#include <QComboBox>
MyQStyledItemDelegate::MyQStyledItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
connect(this, SIGNAL(closeEditor(QWidget*)), this, SLOT(onCloseEditor(QWidget*)));
//connect(this, SIGNAL(commitData(QWidget*)), this, SLOT(onCommitData(QWidget*)));
}
//用户编辑视图上的数据时调用此函数创建编辑组件
QWidget *MyQStyledItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QWidget* ret = NULL;
m_index = index;
//若是bool类型,改变交互编辑组件为复选框
if (index.data().type() == QVariant::Bool)
{
QCheckBox* cb = new QCheckBox(parent);
cb->setText("Check to True");
ret = cb;
}
//若是字符型,改变交互编辑组件为下拉选项框
else if (index.data().type() == QVariant::Char)
{
QComboBox* cb = new QComboBox(parent);
cb->addItem("A");
cb->addItem("B");
cb->addItem("C");
cb->addItem("D");
ret = cb;
}
else //其他类型的数据项默认处理
ret = QStyledItemDelegate::createEditor(parent, option, index);
return ret;
}
//通过索引从模型中拿数据并设置到编辑框
void MyQStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
//将模型中的数据项转为bool类型设置到视图
if (index.data().type() == QVariant::Bool)
{
QCheckBox* cb = dynamic_cast<QCheckBox*>(editor);
if (cb != NULL)
{
cb->setChecked(index.data().toBool());
}
}
//将模型中的数据项设置到编辑框
else if (index.data().type() == QVariant::Char)
{
QComboBox* cb = dynamic_cast<QComboBox*>(editor);
if (cb != NULL)
{
for (int i = 0; i < cb->count(); i++)
{
if (cb->itemText(i) == index.data().toString())
{
cb->setCurrentIndex(i);
break;
}
}
}
}
else
QStyledItemDelegate::setEditorData(editor,index);
}
//将编辑框上的数据写到模型中
void MyQStyledItemDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
if (index.data().type() == QVariant::Bool)
{
QCheckBox* cb = dynamic_cast<QCheckBox*>(editor);
if (cb != NULL)
{
model->setData(index, cb->isChecked(), Qt::DisplayRole);
}
}
else if (index.data().type() == QVariant::Char)
{
QComboBox* cb = dynamic_cast<QComboBox*>(editor);
if (cb != NULL)
{
model->setData(index, cb->currentText().at(0), Qt::DisplayRole);
}
}
else
QStyledItemDelegate::setModelData(editor, model, index);
}
void MyQStyledItemDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if( m_index != index )
{
QStyledItemDelegate::paint(painter, option, index);
}
}
//更新编辑组件大小,用户在编辑数时可能原先大小的编辑框不够使用
void MyQStyledItemDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
//编辑器组件关闭时的槽函数
void MyQStyledItemDelegate::onCloseEditor(QWidget* editor)
{
qDebug() << "slot: onCloseEditor";
m_index = QModelIndex(); //清空m_index]
}
//新数据提交时的槽函数
void MyQStyledItemDelegate::onCommitData(QWidget* editor)
{
qDebug() << "slot: onCommitData";
}
//Widget.h
class Widget : public QWidget
{
Q_OBJECT
QTableView m_view;
QStandardItemModel m_model;
QPushButton m_btn;
MyQStyledItemDelegate m_delegate; //只增加这一行
void initView();
void initModel();
private slots:
void onBtnClicked();
public:
Widget(QWidget *parent = 0);
~Widget();
};
//Widget.cpp
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(400, 100);
m_view.setItemDelegate(&m_delegate); //只增加这一行
}
运行: