简述

在经典的 MVC 模型中,view用于向用户展示 model 的数据。但是,Qt提供的不是 MVC 三层架构,而是一个 model/view 设计。这种设计并没有包含一个完整而独立的组件用于管理用户的交互。在这种结构中,为了获得对用户输入控制的灵活性,这种交互工作交给了delegate,也就是“委托”,去完成。简单来说,就像它们的名字一样,view 将用户输入委托给 delegate 处理,而自己不去处理这种输入。这些组件提供一种输入能力,并且能够在某些 view 中提供这种交互情形下的渲染,比如在 table 中通过双击单元格即可编辑内容等。对这种控制委托的标准接口被定义在 QAbstractItemDelegate 类中。

Qt提供的标准组件使用 QItemDelegate 提供编辑功能的支持。这种默认的实现被用在 QListView,QTableView 和 QTreeView 之中。view 实用的delegate可以通过 itemDelegate() 函数获得。setItemDelegate() 函数则可以为一个标准组件设置自定义的 delegate。

delegate 可以用于渲染内容,这是通过 paint() 和 sizeHint() 函数来完成的。这个例子中,我们继承了QItemDelegate,重写了里面的createEditor(),setModelData()和paint()三个函数。给view 组件提供绘制和编辑的功能,来实现在TableView中添加Combobox、Spainbox、CheckBox控件。



效果

QTableView 修改item qtableview自定义_TableView

 


代码之路

一个自定义的delegate可以直接提供一个编辑器,而不是使用内置的编辑器工厂(editor item factory)。如果你需要这种功能,那么需要重新实现一下几个函数:

  • createEditor(): 返回修改数据的组件;
  • setEditorData(): 为editor提供编辑的原始数据;
  • updateEditorGeometry(): 保证editor显示在 item view 的合适位置以及大小;
  • setModelData(): 根据editor 的数据更新model的数据。

下面来看代码:

controldelegate.h文件

#define CONTROLDELEGATE_H

#include <QObject>
#include <QSpinBox>
#include <QCheckBox>
#include <QComboBox>
#include <QItemDelegate>
#include <QApplication>
#include <QStyledItemDelegate>
#include <QStyleOption>
#include <QMouseEvent>
#include <QPainter>

class SpinboxDelegate : public QItemDelegate
{
        Q_OBJECT

public:
    SpinboxDelegate(int column);
    //重写QItemDelegate里的函数
    //const修饰引用传递,"引用传递"仅借用一下参数的别名,不需要产生临时对象,所以可以提高效率.使用const可以确保引用的参数不被修改
    //const加在最后,函数的数据成员mColumn不可被修改
    virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    //void setEditorData(QWidget *editor, const QModelIndex &index) const ;       //为editor提供编辑的原始数据
    virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const ;
    virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    //void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;     //保证editor显示在 item view 的合适位置以及大小

private slots:
    void commitAndCloseEditor();

private:
    int mColumn;
};

class CheckBoxDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    CheckBoxDelegate(QObject *parent = 0);
protected:
    void paint(QPainter* painter,const QStyleOptionViewItem& option,const QModelIndex& index) const;
    bool editorEvent(QEvent *event,QAbstractItemModel *model,const QStyleOptionViewItem &option,const QModelIndex &index);
};

class ComboboxDelegate : public QItemDelegate
{
    Q_OBJECT

public:
    ComboboxDelegate();
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;

private slots:
    void commitAndCloseEditor();
};

#endif // CONTROLDELEGATE_H

controldelegate.cpp文件

#include "controldelegate.h"

//spinbox控件部分
SpinboxDelegate::SpinboxDelegate(int column)
{
        mColumn = column;
}

//返回修改数据的组件,为指定的列或者行创建部件
QWidget *SpinboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,const QModelIndex &index) const
{
    if(index.column() == 3)
    {
        QSpinBox *editor = new QSpinBox(parent);
        editor->setRange(0 , 1000);
        connect(editor,SIGNAL(editingFinished()),SLOT(commitAndCloseEditor()));
        return editor;
    }
    else if( index.column() == 4)
    {
        QSpinBox *editor = new QSpinBox(parent);
        editor->setRange(0,1000);
        connect(editor,SIGNAL(editingFinished()),SLOT(commitAndCloseEditor()));
        return editor;
    }
    else
    {
        return QItemDelegate::createEditor(parent,option,index);
    }
}

//根据editor 的数据更新model的数据
void SpinboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->interpretText();
    int value = spinBox->value();

    model->setData(index, value, Qt::EditRole);
}

//显示格式控制
void SpinboxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.column() == 3) {
        int warehouseAmount = index.model()->data(index,Qt::DisplayRole).toInt();
        QString text = QString("%1").arg(warehouseAmount, 3, 10, QChar(' '));

        QStyleOptionViewItem myOption = option;
        //设置显示在item的中间
        myOption.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter;

        drawDisplay(painter, myOption, myOption.rect, text);
        drawFocus(painter, myOption, myOption.rect);
    }
    else if(index.column() == 4) {
        int amount = index.model()->data(index,Qt::DisplayRole).toInt();
        QString text = QString("%1").arg(amount, 3, 10, QChar(' '));

        QStyleOptionViewItem myOption = option;
        myOption.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter;

        drawDisplay(painter, myOption, myOption.rect, text);
        drawFocus(painter, myOption, myOption.rect);
    }
}

void SpinboxDelegate::commitAndCloseEditor()
{
    QSpinBox *editor = qobject_cast<QSpinBox*>(sender());
    emit commitData(editor);                    //当编辑器小部件完成数据编辑并希望将其写入模型时,必须发出此信号
    emit closeEditor(editor);                   //当用户使用指定的编辑器完成对项目的编辑时,将发出此信号
}

//checkbox部分
static QRect CheckBoxRect(const QStyleOptionViewItem &viewItemStyleOptions)/*const*/
{
    //绘制按钮所需要的参数
    QStyleOptionButton checkBoxStyleOption;
    //按照给定的风格参数 返回元素子区域
    QRect checkBoxRect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkBoxStyleOption);
    //返回QCheckBox坐标
    QPoint checkBoxPoint(viewItemStyleOptions.rect.x() + viewItemStyleOptions.rect.width() / 2 - checkBoxRect.width() / 2,
                         viewItemStyleOptions.rect.y() + viewItemStyleOptions.rect.height() / 2 - checkBoxRect.height() / 2);
    //返回QCheckBox几何形状
    return QRect(checkBoxPoint, checkBoxRect.size());
}

CheckBoxDelegate::CheckBoxDelegate(QObject *parent):
    QStyledItemDelegate(parent)
{

}

void CheckBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,const QModelIndex& index)const
{
    bool checked = index.model()->data(index, Qt::DisplayRole).toBool();

    if(index.column() == 5) {
        //按钮的风格选项
        QStyleOptionButton checkBoxStyleOption;
        //|=为复合赋值语句,a|=b等价于a=a|b,对a和b进行位或运算
        checkBoxStyleOption.state |= QStyle::State_Enabled;

        //根据值判断是否选中
        checkBoxStyleOption.state |= checked? QStyle::State_On : QStyle::State_Off;

        //返回QCheckBox几何形状
        checkBoxStyleOption.rect = CheckBoxRect(option);
        //绘制QCheckBox
        QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxStyleOption, painter);
    }
    else {
        //否则调用默认委托
        QStyledItemDelegate::paint(painter, option, index);
    }
}

bool CheckBoxDelegate::editorEvent(QEvent *event,
                                QAbstractItemModel *model,
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index) {
    if(index.column() == 5){
    if((event->type() == QEvent::MouseButtonRelease) ||
            (event->type() == QEvent::MouseButtonDblClick)){
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
        if(mouseEvent->button() != Qt::LeftButton ||
                !CheckBoxRect(option).contains(mouseEvent->pos())){
            return true;
        }
        if(event->type() == QEvent::MouseButtonDblClick){
            return true;
        }
    }else if(event->type() == QEvent::KeyPress){
        if(static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space &&
                static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select){
            return false;
        }
    }else{
        return false;
    }

    bool checked = index.model()->data(index, Qt::DisplayRole).toBool();
    return model->setData(index, !checked, Qt::EditRole);
    }else{
        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
}

//combobox控件部分
ComboboxDelegate::ComboboxDelegate()
{

}

QWidget *ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QComboBox *editor = new QComboBox(parent);
    if(index.column() == 2){
        editor->addItem(QString::fromLocal8Bit("boy"));
        editor->addItem(QString::fromLocal8Bit("girl"));
        editor->setCurrentIndex(0);

        connect(editor,SIGNAL(editingFinished()),SLOT(commitAndCloseEditor()));
        return editor;
    }
    else
    {
        QItemDelegate::createEditor(parent,option,index);
    }
}

void ComboboxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.column() == 2)
    {
        QString text = index.model()->data(index , Qt::DisplayRole).toString();

        QStyleOptionViewItem myOption = option;
        myOption.displayAlignment = Qt::AlignHCenter | Qt::AlignVCenter;

        drawDisplay(painter,myOption,myOption.rect,text);
        drawFocus(painter,myOption,myOption.rect);
    }
    else
    {
        QItemDelegate::paint(painter,option,index);
    }
}

void ComboboxDelegate::commitAndCloseEditor()
{
    QComboBox *editor = qobject_cast<QComboBox*>(sender());
    emit commitData(editor);
    emit closeEditor(editor);
}

mainwindow.h文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QStandardItemModel>
#include "controldelegate.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    void tableViewInit();

private slots:
    void contextMenuEvent(QContextMenuEvent *event);

    //插入行操作
    void sltInsertAction();

    //删除指定行操作
    void sltDeleteAction();

private:
    Ui::MainWindow *ui;

    QMenu *OperationMenu;                   //操作菜单
    QAction *DeleteAction;                  //删除动作
    QAction *InsertAction;                  //插入动作

    QStandardItemModel *tableModel;
};

#endif // MAINWINDOW_H

mainwindow.cpp文件

#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    tableViewInit();

    OperationMenu = new QMenu();
    DeleteAction = new QAction("删除");
    InsertAction = new QAction("插入");
    OperationMenu->addAction(DeleteAction);
    OperationMenu->addAction(InsertAction);

    connect(InsertAction, SIGNAL(triggered(bool)), this, SLOT(sltInsertAction()));
    connect(DeleteAction, SIGNAL(triggered(bool)), this, SLOT(sltDeleteAction()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::contextMenuEvent(QContextMenuEvent *event) //重写上下文事件
{
    //方法一
    if(ui->tableView->hasFocus())
    {
        OperationMenu->move(cursor().pos());
        OperationMenu->show();
    }
    //方法二
//    {
//        ui->tableView->addAction(InsertAction);
//        ui->tableView->addAction(DeleteAction);
//        ui->tableView->(Qt::ActionsContextMenu);
//    }
}

void MainWindow::tableViewInit()
{
    tableModel = new QStandardItemModel;
    ui->tableView->setModel(tableModel);

    tableModel->setHorizontalHeaderItem(0, new QStandardItem("学号"));
    tableModel->setHorizontalHeaderItem(1, new QStandardItem("姓名"));
    tableModel->setHorizontalHeaderItem(2, new QStandardItem("性别"));
    tableModel->setHorizontalHeaderItem(3, new QStandardItem("语文"));
    tableModel->setHorizontalHeaderItem(4, new QStandardItem("数学"));
    tableModel->setHorizontalHeaderItem(5, new QStandardItem("测试通过"));

    ui->tableView->setModel(tableModel);

    //tableView委托设置每一列里的控件
    ui->tableView->setItemDelegateForColumn(2, new ComboboxDelegate());
    ui->tableView->setItemDelegateForColumn(3, new SpinboxDelegate(3));
    ui->tableView->setItemDelegateForColumn(4, new SpinboxDelegate(4));
    ui->tableView->setItemDelegateForColumn(5, new CheckBoxDelegate());

    ui->tableView->setEditTriggers(QAbstractItemView::DoubleClicked);
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
}

//插入行操作
void MainWindow::sltInsertAction()
{
    QList<QStandardItem*> item;
    item.append(new QStandardItem());
    item.append(new QStandardItem());
    item.append(new QStandardItem());
    item.append(new QStandardItem());
    item.append(new QStandardItem());
    item.append(new QStandardItem());
    //设置列数的对齐方式
    item.at(0)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item.at(1)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item.at(2)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item.at(3)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item.at(4)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    tableModel->insertRow(tableModel->rowCount(), item);
}

//删除指定行操作
void MainWindow::sltDeleteAction()
{
    int curreantRow = ui->tableView->currentIndex().row();                    //获取当前行数
    tableModel->removeRow(curreantRow);
}