Qt——模型视图中的委托

1.模型视图中的委托

  • 委托(Delegate)是视图中处理用户输入的部件
  • 视图可以设置委托对象用于处理用户输入
  • 委托对象负责创建和显示用户输入上下文(编辑框的创建和显示)

2.QAbstractItemDelegate用于定义委托的功能接口

QItemDelegates标准委托功能的实现

3.委托中的编辑器

  • 委托能够提供编辑时需要的上下文环境
  • 不同委托提供的编辑器类型不同(文本框、单选框等)
  • 编辑器用从模型获取数据,并将编辑结果返回模型

4.委托中的关键函数

  • createEditor需要编辑数据时,创建编辑器组件
  • updateEditorGeometry更新编辑器组件的大小
  • setEditorData通过索引从模型中获取数据
  • setModelData将编辑后的新数据返回模型

5.委托中的关键信号

  • void closeEditor(QWidget* editor) 编辑器组件关闭信号
  • void commitData(QWidget* editor) 新数据提交信号

Widget.h

复制代码
#define WIDGET_H

#include <QWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QPushButton>
#include "SubStyledItemDelegate.h"

class Widget : public QWidget
{
    Q_OBJECT
    QTableView m_view;
    QStandardItemModel m_model;
    QPushButton m_testBtn;
    SubStyledItemDelegate m_delegate;

    void initView();
    void initModel();

private slots:
    void onTestBtnClicked();

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;
};
#endif // WIDGET_H

Widget.cpp

复制代码
#include "Widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    initView();
    initModel();

    m_view.setModel(&m_model);
    m_testBtn.setParent(this);
    m_testBtn.move(10, 120);
    m_testBtn.resize(300, 30);
    m_testBtn.setText("Test");
    connect(&m_testBtn, SIGNAL(clicked()), this, SLOT(onTestBtnClicked()));
}
void Widget::initView()
{
    m_view.setParent(this);
    m_view.move(10, 10);
    m_view.resize(300, 100);
    m_view.setItemDelegate(&m_delegate);
}
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::onTestBtnClicked()
{
    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(Qt::DisplayRole).toString();
            qDebug() << text;
        }
        qDebug() << endl;
    }
    qDebug() << "Current View Delegate: " << m_view.itemDelegate();
}
Widget::~Widget() = default;

SubStyledItemDelegate.h

复制代码
#ifndef SUBSTYLEDITEMDELEGATE_H
#define SUBSTYLEDITEMDELEGATE_H

#include <QObject>
#include <QStyledItemDelegate>

class SubStyledItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
private slots:
    void onCloseEditor(QWidget* editor);
    void onCommitData(QWidget* editor);
public:
    explicit SubStyledItemDelegate(QObject* parent = 0);
    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;
};

#endif // SUBSTYLEDITEMDELEGATE_H

SubStyledItemDelegate.cpp

复制代码
#include "SubStyledItemDelegate.h"
#include <QDebug>

SubStyledItemDelegate::SubStyledItemDelegate(QObject* parent) :
    QStyledItemDelegate(parent)
{
    connect(this, SIGNAL(closeEditor(QWidget*)), this, SLOT(onCloseEditor(QWidget*)));
    connect(this, SIGNAL(commitData(QWidget*)), this, SLOT(onCommitData(QWidget*)));
}
QWidget* SubStyledItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    qDebug() << "SubStyledItemDelegate::createEditor";
    return QStyledItemDelegate::createEditor(parent, option, index);
}
void SubStyledItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    qDebug() << "SubStyledItemDelegate::updateEditorGeometry";
    QStyledItemDelegate::updateEditorGeometry(editor, option, index);
}
void SubStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    qDebug() << "SubStyledItemDelegate::setEditorData";
    QStyledItemDelegate::setEditorData(editor, index);
}
void SubStyledItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    qDebug() << "SubStyledItemDelegate::setModelData";
    QStyledItemDelegate::setModelData(editor, model, index);
}
void SubStyledItemDelegate::onCloseEditor(QWidget* editor)
{
    qDebug() << "SubStyledItemDelegate::onCloseEditor";
}
void SubStyledItemDelegate::onCommitData(QWidget* editor)
{
    qDebug() << "SubStyledItemDelegate::onCommitData";
}

运行结果:当编辑框中内容被更改后:

SubStyledItemDelegate::createEditor

SubStyledItemDelegate::updateEditorGeometry

SubStyledItemDelegate::setEditorData

SubStyledItemDelegate::onCommitData

SubStyledItemDelegate::setModelData

SubStyledItemDelegate::setEditorData

SubStyledItemDelegate::onCloseEditor

当Test按钮被点击时

Model Data

Row: 0

"A"

"B"

Row: 1

"C"

"D"

Current View Delegate: QStyledItemDelegate(0x2da60c0)

6.委托的本质

  • 为视图提供数据编辑的上下文环境
  • 产生界面元素的工厂类
  • 能够使用和设置模型中的数据

7.自定义委托时需要重写函数

  • createEditor需要编辑数据时,创建编辑器组件
  • updateEditorGeometry更新编辑器组件的大小和位置
  • setEditorData通过索引从模型中获取数据
  • setModelData将编辑后的新数据返回模型

CustomizedItemModel.h

复制代码
#ifndef CUSTOMIZEDITEMMODEL_H
#define CUSTOMIZEDITEMMODEL_H

#include <QItemDelegate>
#include <QObject>
#include <QModelIndex>

class CustomizedItemModel : public QItemDelegate
{
    Q_OBJECT

protected slots:
    //void onCloseEditor(QWidget*);
public:
    CustomizedItemModel(QObject* parent = 0);
    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;
};

#endif // CUSTOMIZEDITEMMODEL_H

CustomizedItemModel.cpp

复制代码
#include "CustomizedItemModel.h"
#include <QCheckBox>
#include <QComboBox>
#include <QApplication>

CustomizedItemModel::CustomizedItemModel(QObject* parent) :
    QItemDelegate(parent)
{
    //connect(this, SIGNAL(closeEditor(QWidget*)), this, SLOT(onCloseEditor(QWidget*)));
}
QWidget* CustomizedItemModel::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QWidget* ret = NULL; //用于存储要返回的编辑器控件
    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 = QItemDelegate::createEditor(parent, option, index);
    }
    return ret;
}
void CustomizedItemModel::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect); //包含了当前单元格在视图中的坐标和尺寸
}
void CustomizedItemModel::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 CustomizedItemModel::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 CustomizedItemModel::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    // if (index.data().type() == QVariant::Bool)
    // {
    //     bool value = index.data().toBool();

    //     QStyleOptionButton checkBoxOption;
    //     checkBoxOption.rect = option.rect;  // 使用单元格区域
    //     checkBoxOption.state = QStyle::State_Enabled;
    //     checkBoxOption.state |= value ? QStyle::State_On : QStyle::State_Off;

    //     QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxOption, painter);
    // }
    // else
    // {
    //     QItemDelegate::paint(painter, option, index);
    // }
    QItemDelegate::paint(painter, option, index);
}

Widget.h

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTableView>
#include <QStandardItemModel>
#include <QPushButton>
#include "CustomizedItemModel.h"

class Widget : public QWidget
{
    Q_OBJECT
    QTableView m_view;
    QStandardItemModel m_model;
    CustomizedItemModel m_delegate;

    void initView();
    void initModel();

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget() override;
};
#endif // WIDGET_H

Widget.cpp

复制代码
#include "Widget.h"
#include <QStringList>
#include <QChar>

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    initView();
    initModel();

    m_view.setModel(&m_model);

    for(int i=0; i<m_model.columnCount(); i++)
    {
        m_view.setColumnWidth(i, 125);
    }
}
void Widget::initView()
{
    m_view.setParent(this);
    m_view.move(10, 10);
    m_view.resize(500, 300);
    m_view.setItemDelegate(&m_delegate);
}
void Widget::initModel()
{
    QStandardItem* root = m_model.invisibleRootItem();

    QStringList hl;
    QStandardItem* itemA1 = new QStandardItem();
    QStandardItem* itemB1 = new QStandardItem();
    QStandardItem* itemC1 = new QStandardItem();
    QStandardItem* itemA2 = new QStandardItem();
    QStandardItem* itemB2 = new QStandardItem();
    QStandardItem* itemC2 = new QStandardItem();

    hl.append("Language");
    hl.append("Level");
    hl.append("script");

    m_model.setHorizontalHeaderLabels(hl);
    itemA1->setData("nnn", Qt::DisplayRole);
    itemB1->setData(QChar('A'), Qt::DisplayRole);
    itemC1->setData(false, Qt::DisplayRole);

    itemA2->setData("hhh", Qt::DisplayRole);
    itemB2->setData(QChar('B'), Qt::DisplayRole);
    itemC2->setData(true, Qt::DisplayRole);
    root->setChild(0, 0, itemA1);
    root->setChild(0, 1, itemB1);
    root->setChild(0, 2, itemC1);
    root->setChild(1, 0, itemA2);
    root->setChild(1, 1, itemB2);
    root->setChild(1, 2, itemC2);
}
Widget::~Widget() = default;