问题
模型负责组织数据,视图负责显示数据,如何编辑修改数据?
传统的 MVC 设计模式
Qt 中的模型视图设计模式如何处理用户输入?
视图中集成了处理用户输入的功能
视图将用户输入作为内部的子功能而实现
模型视图中的委托
委托 (Delegate) 是视图中处理用户输入的部件
视图可以设置委托对象用于处理用户输入
委托对象负责创建和显示用户上下文
- 如:编辑框的创建和显示
委托中的编辑器
委托能够提供编辑时需要的上下文环境 (编辑器)
不同委托提供的编辑器类型不同 (文本框,单选框,等)
编辑器从模型获取数据,并将编辑结果返回模型
委托中的关键函数
createEditor
- 需要编辑数据时,创建编辑器组件
updateEditorGeometry
- 更新编辑器组件的大小
setEditorData
- 通过索引从模型中获取数据
setModelData
- 将编辑后的新数据返回模型
委托中的关键信号
void closeEditor(QWidget* editor)
- 编辑器组件关闭信号
void commitData(QWidget* editor)
- 新数据提交信号
委托的关键函数和信号
SubStyledItemDelegate.h
#ifndef SUBSTYLEDITEMDELEGATE_H
#define SUBSTYLEDITEMDELEGATE_H
#include <QObject>
#include <QStyledItemDelegate>
class SubStyledItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
private slots:
void OnCloseEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint = NoHint);
void onCommitData(QWidget* editor);
public:
explicit SubStyledItemDelegate(QObject* parent = nullptr);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index);
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void setEditorData(QWidget* editor, const QModelIndex& index) const;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index);
signals:
};
#endif // SUBSTYLEDITEMDELEGATE_H
SubStyledItemDelegate.cpp
#include "SubStyledItemDelegate.h"
#include <QDebug>
SubStyledItemDelegate::SubStyledItemDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
connect(this, &QStyledItemDelegate::closeEditor, this, &SubStyledItemDelegate::OnCloseEditor);
connect(this, &QStyledItemDelegate::commitData, this, &SubStyledItemDelegate::onCommitData);
}
QWidget* SubStyledItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index)
{
qDebug() << "SubStyledItemDelegate::createEditor";
return QStyledItemDelegate::createEditor(parent, option, index);
}
void SubStyledItemDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem &option, const QModelIndex& index) const
{
qDebug() << "SubStyledItemDelegate::updateEditorGeometry";
return QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
void SubStyledItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
qDebug() << "SubStyledItemDelegate::setEditorData";
return QStyledItemDelegate::setEditorData(editor, index);
}
void SubStyledItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index)
{
qDebug() << "SubStyledItemDelegate::setModelData";
return QStyledItemDelegate::setModelData(editor, model, index);
}
void SubStyledItemDelegate::OnCloseEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint)
{
qDebug() << "SubStyledItemDelegate::OnCloseEditor";
}
void SubStyledItemDelegate::onCommitData(QWidget* editor)
{
qDebug() << "SubStyledItemDelegate::onCommitData";
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTableView>
#include <QPushButton>
#include <QStandardItemModel>
#include "SubStyledItemDelegate.h"
class Widget : public QWidget
{
Q_OBJECT
private:
QStandardItemModel m_model;
QTableView m_view;
QPushButton m_testBtn;
SubStyledItemDelegate m_delegate;
void initModel();
void initView();
private slots:
void onTestBtnClicked();
public:
Widget(QWidget *parent = nullptr);
~Widget();
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QStandardItem>
#include <QModelIndex>
#include <QDebug>
Widget::Widget(QWidget* parent)
: QWidget(parent)
{
m_testBtn.setParent(this);
m_testBtn.move(10, 120);
m_testBtn.resize(300, 30);
m_testBtn.setText("test");
initModel();
initView();
m_view.setModel(&m_model);
connect(&m_testBtn, &QPushButton::clicked, this, &Widget::onTestBtnClicked);
}
void Widget::initModel()
{
QStandardItem *root = m_model.invisibleRootItem();
QStandardItem *itemA = new QStandardItem();
QStandardItem *itemB = new QStandardItem();
QStandardItem *itemC = new QStandardItem();
QStandardItem *itemD = new QStandardItem();
itemA->setData("A", Qt::DisplayRole);
itemB->setData("B", Qt::DisplayRole);
itemC->setData("C", Qt::DisplayRole);
itemD->setData("D", Qt::DisplayRole);
root->setChild(0, 0, itemA);
root->setChild(0, 1, itemB);
root->setChild(1, 0, itemC);
root->setChild(1, 1, itemD);
}
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(300, 100);
m_view.setItemDelegate(&m_delegate);
};
void Widget::onTestBtnClicked()
{
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(Qt::DisplayRole).toString();
qDebug() << text;
}
}
qDebug() << m_view.itemDelegate();
}
Widget::~Widget()
{
}
委托的本质
为视图提供编辑的上下文环境
产生界面元素的工厂类
能够使用和设置模型中的数据
问题
如何自定一个委托类?
自定义委托时需要重写的函数
1. 重写 createEditor 成员函数
- 根据索引中值的类型创建编辑器组件
2. 重写 updateEditorGeometry 成员函数
- 根据参数中数据项的信息设置编辑器的位置和大小
3. 重写 setEditorData 成员函数
- 根据参数中的数据索引设置编辑器中的内容
4. 重写 setModelData 成员函数
- 根据参数中的数据索引更改模型中的数据
重写 paint 成员函数 (可选)
- 根据参数中的信息绘制编辑器
自定义委托
CustomizedDelegate.h
#ifndef CustomizedDeletage_H
#define CustomizedDeletage_H
#include <QObject>
#include <QItemDelegate>
#include <QModelIndex>
class CustomizedDeletage : public QItemDelegate
{
Q_OBJECT
protected:
mutable QModelIndex m_index;
protected slots:
void onCloseEditor(QWidget*);
public:
explicit CustomizedDeletage(QObject* parent = nullptr);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
void updateEditorGeometry(QWidget* editor, 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 paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
signals:
};
#endif // CustomizedDeletage_H
CustomizedDelegate.cpp
#include "CustomizedDeletage.h"
#include <QVariant>
#include <QCheckBox>
#include <QComboBox>
CustomizedDeletage::CustomizedDeletage(QObject* parent) : QItemDelegate(parent)
{
connect(this, &QItemDelegate::closeEditor, this, &CustomizedDeletage::onCloseEditor);
}
QWidget* CustomizedDeletage::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QWidget* w = NULL;
m_index = index;
if(index.data().type() == QVariant::Bool)
{
QCheckBox* cb = new QCheckBox(parent);
cb->setText("selece to true");
w = 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");
w = cb;
}
else
{
w = QItemDelegate::createEditor(parent, option, index);
}
return w;
}
void CustomizedDeletage::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}
void CustomizedDeletage::setEditorData(QWidget* editor, const QModelIndex &index) const
{
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
{
QItemDelegate::setEditorData(editor, index);
}
}
void CustomizedDeletage::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(), Qt::DisplayRole);
}
}
else
{
QItemDelegate::setModelData(editor, model, index);
}
}
void CustomizedDeletage::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if(m_index != index)
{
QItemDelegate::paint(painter, option, index);
}
}
void CustomizedDeletage::onCloseEditor(QWidget*)
{
m_index = QModelIndex();
}
Widget.h
#include "CustomizedDeletage.h"
#include <QVariant>
#include <QCheckBox>
#include <QComboBox>
CustomizedDeletage::CustomizedDeletage(QObject* parent) : QItemDelegate(parent)
{
connect(this, &QItemDelegate::closeEditor, this, &CustomizedDeletage::onCloseEditor);
}
QWidget* CustomizedDeletage::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QWidget* w = NULL;
m_index = index;
if(index.data().type() == QVariant::Bool)
{
QCheckBox* cb = new QCheckBox(parent);
cb->setText("selece to true");
w = 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");
w = cb;
}
else
{
w = QItemDelegate::createEditor(parent, option, index);
}
return w;
}
void CustomizedDeletage::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}
void CustomizedDeletage::setEditorData(QWidget* editor, const QModelIndex &index) const
{
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
{
QItemDelegate::setEditorData(editor, index);
}
}
void CustomizedDeletage::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(), Qt::DisplayRole);
}
}
else
{
QItemDelegate::setModelData(editor, model, index);
}
}
void CustomizedDeletage::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if(m_index != index)
{
QItemDelegate::paint(painter, option, index);
}
}
void CustomizedDeletage::onCloseEditor(QWidget*)
{
m_index = QModelIndex();
}
Widget.cpp
#include "Widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
initModel();
initView();
m_view.setModel(&m_model);
for(int i = 0; i < m_model.columnCount(); i++)
{
m_view.setColumnWidth(i, 150);
}
}
void Widget::initModel()
{
QStandardItem *root = m_model.invisibleRootItem();
QStandardItem *itemA1 = new QStandardItem();
QStandardItem *itemA2 = new QStandardItem();
QStandardItem *itemA3 = new QStandardItem();
QStandardItem *itemB1 = new QStandardItem();
QStandardItem *itemB2 = new QStandardItem();
QStandardItem *itemB3 = new QStandardItem();
QStringList list;
list.append("Language");
list.append("Level");
list.append("Script");
m_model.setHorizontalHeaderLabels(list);
itemA1->setData("C++", Qt::DisplayRole);
itemA2->setData(QChar('A'), Qt::DisplayRole);
itemA3->setData(false, Qt::DisplayRole);
itemB1->setData("Python", Qt::DisplayRole);
itemB2->setData(QChar('B'), Qt::DisplayRole);
itemB3->setData(true, Qt::DisplayRole);
root->setChild(0, 0, itemA1);
root->setChild(0, 1, itemA2);
root->setChild(0, 2, itemA3);
root->setChild(1, 0, itemB1);
root->setChild(1, 1, itemB2);
root->setChild(1, 2, itemB3);
}
void Widget::initView()
{
m_view.setParent(this);
m_view.move(10, 10);
m_view.resize(600, 400);
m_view.setItemDelegate(&m_delegate);
};
Widget::~Widget()
{
}
为了防止双击编辑器后图像有覆盖的现象,使用 m_index 变量防止图像覆盖。
问题
自定义委托时重写的函数由谁调用?