一、介绍

Spin Box Delegate示例展示了如何通过重用标准Qt编辑器小部件来为模型/视图框架中的自定义委托创建一个编辑器。

模型/视图框架提供了一个标准委托,默认情况下与标准视图类一起使用。对于大多数情况,通过此委托选择可用的编辑器小部件就足以编辑文本、布尔值和其他简单数据类型。但是,对于特定的数据类型,有时需要通过使用自定义委托以特定的方式显示数据,或者允许用户使用自定义控件编辑数据。

这个例子背后的概念在模型/视图编程概述的委托类一章中有介绍。

二、SpinBoxDelegate类定义

委托的定义如下:

  1. #ifndef DELEGATE_H
  2. #define DELEGATE_H
  3. #include <QStyledItemDelegate>
  4. class SpinBoxDelegate : public QStyledItemDelegate {
  5. Q_OBJECT
  6. public:
  7. SpinBoxDelegate(QObject* parent = nullptr);
  8. QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option,
  9. const QModelIndex& index) const override;
  10. void setEditorData(QWidget* editor, const QModelIndex& index) const override;
  11. void setModelData(QWidget* editor, QAbstractItemModel* model,
  12. const QModelIndex& index) const override;
  13. void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option,
  14. const QModelIndex& index) const override;
  15. };
  16. #endif

委托类只声明创建编辑器小部件、将其显示在视图中的正确位置以及与模型通信所需的那些函数。定制委托还可以通过重新实现paintEvent()函数来提供自己的绘制代码。此外,还可以通过重新实现destroyEditor()函数来重用(并避免删除)编辑器小部件。可重用的小部件可以是在构造函数中创建并在析构函数中删除的可变成员。

三、SpinBoxDelegate类的实现

委托通常是无状态的,构造函数只需要调用基类的构造函数,并将父类QObject作为参数:

  1. SpinBoxDelegate::SpinBoxDelegate(QObject* parent)
  2. : QStyledItemDelegate(parent)
  3. {
  4. }

由于委托是QStyledItemDelegate的一个子类,它从模型中获取的数据将以默认的样式显示,我们不需要提供自定义的paintEvent()

createEditor()函数返回一个编辑器小部件,在本例中是一个QSpinBox,它将模型中的值限制为0到100(含100)之间的整数。

  1. QWidget* SpinBoxDelegate::createEditor(QWidget* parent,
  2. const QStyleOptionViewItem& /* option */,
  3. const QModelIndex& /* index */) const
  4. {
  5. QSpinBox* editor = new QSpinBox(parent);
  6. editor->setFrame(false);
  7. editor->setMinimum(0);
  8. editor->setMaximum(100);
  9. return editor;
  10. }

我们在QSpinBox上安装事件过滤器,以确保它的行为方式与其他委托一致,事件筛选器的实现由基类提供。

setEditorData()函数从模型读取数据,将其转换为整数值,并将其写入编辑器小部件。

  1. void SpinBoxDelegate::setEditorData(QWidget* editor,
  2. const QModelIndex& index) const
  3. {
  4. int value = index.model()->data(index, Qt::EditRole).toInt();
  5. QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
  6. spinBox->setValue(value);
  7. }

因为视图将委托视为普通的QWidget实例,所以在在QSpinBox中设置值之前,我们必须使用静态强制转换。

setModelData()函数读取QSpinBox的内容,并将其写入模型:

  1. void SpinBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
  2. const QModelIndex& index) const
  3. {
  4. QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
  5. spinBox->interpretText();
  6. int value = spinBox->value();
  7. model->setData(index, value, Qt::EditRole);
  8. }

我们调用interpretText()以确保在QSpinBox中获得最新的值。

updateEditorGeometry()函数使用样式选项中提供的信息更新编辑器小部件的几何形状。这是委托在本例中必须执行的最小操作。

  1. void SpinBoxDelegate::updateEditorGeometry(QWidget* editor,
  2. const QStyleOptionViewItem& option,
  3. const QModelIndex& /* index */) const
  4. {
  5. editor->setGeometry(option.rect);
  6. }

如果需要,更复杂的编辑器小部件可能会将“option.rect”中可用的矩形划分为不同的子小部件。

四、main函数

为了在标准视图中演示自定义编辑器小部件的使用,需要建立一个包含一些任意数据的模型和一个显示它的视图。

我们以正常的方式设置应用程序,构造一个标准项模型来保存一些数据,建立一个表视图来使用模型中的数据,并构造一个自定义委托来用于编辑:

  1. int main(int argc, char* argv[])
  2. {
  3. QApplication app(argc, argv);
  4. QStandardItemModel model(4, 2);
  5. QTableView tableView;
  6. tableView.setModel(&model);
  7. SpinBoxDelegate delegate;
  8. tableView.setItemDelegate(&delegate);

表格视图将被告知委托的相关信息,并将使用它来显示每个项。由于委托是QStyledItemDelegate的一个子类,表中的每个单元格都将使用标准绘制操作进行渲染。

出于演示目的,我们在模型中插入一些任意数据:

  1. tableView.horizontalHeader()->setStretchLastSection(true);
  2. for (int row = 0; row < 4; ++row) {
  3. for (int column = 0; column < 2; ++column) {
  4. QModelIndex index = model.index(row, column, QModelIndex());
  5. model.setData(index, QVariant((row + 1) * (column + 1)));
  6. }
  7. }

最后,表格视图显示一个窗口标题,然后我们开始应用程序的事件循环:

  1. tableView.setWindowTitle(QObject::tr("Spin Box Delegate"));
  2. tableView.show();
  3. return app.exec();
  4. }

五、示例代码

这个简单的例子展示了视图如何使用自定义委托来编辑从模型中获得的数据。

main.cpp

  1. #include "delegate.h"
  2. #include <QApplication>
  3. #include <QHeaderView>
  4. #include <QStandardItemModel>
  5. #include <QTableView>
  6. int main(int argc, char* argv[])
  7. {
  8. QApplication app(argc, argv);
  9. // 设置model
  10. QStandardItemModel model(4, 2);
  11. QTableView tableView;
  12. tableView.setModel(&model);
  13. // 设置delegate
  14. SpinBoxDelegate delegate;
  15. tableView.setItemDelegate(&delegate);
  16. // 去除选中单元格的时候出现的虚线矩形框
  17. tableView.setFocusPolicy(Qt::NoFocus);
  18. // 创建4*2的表格
  19. tableView.horizontalHeader()->setStretchLastSection(true);
  20. for (int row = 0; row < 4; ++row) {
  21. for (int column = 0; column < 2; ++column) {
  22. QModelIndex index = model.index(row, column, QModelIndex());
  23. model.setData(index, QVariant((row + 1) * (column + 1)));
  24. }
  25. }
  26. // 设置窗口名称,进入事件循环
  27. tableView.setWindowTitle(QObject::tr("Spin Box Delegate"));
  28. tableView.show();
  29. return app.exec();
  30. }

delegate.h

  1. #ifndef DELEGATE_H
  2. #define DELEGATE_H
  3. // 允许用户使用旋转框小部件从模型中更改整数值的委托。
  4. #include <QStyledItemDelegate>
  5. class SpinBoxDelegate : public QStyledItemDelegate {
  6. Q_OBJECT
  7. public:
  8. SpinBoxDelegate(QObject* parent = nullptr);
  9. QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option,
  10. const QModelIndex& index) const override;
  11. void setEditorData(QWidget* editor,
  12. const QModelIndex& index) const override;
  13. void setModelData(QWidget* editor, QAbstractItemModel* model,
  14. const QModelIndex& index) const override;
  15. void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option,
  16. const QModelIndex& index) const override;
  17. };
  18. #endif

delegate.cpp

  1. #include "delegate.h"
  2. #include <QSpinBox>
  3. // 委托通常是无状态的, 构造函数只需要调用基类的构造函数并将QObjcet作为参数即可
  4. // - 使用基类提供的重绘事件(paintEvent()), 以确保从模型中获取的数据将按默认的样式显示
  5. // - 使用基类提供的事件过滤器(eventFilter()), 以确保它的行为方式与其它委托一致
  6. SpinBoxDelegate::SpinBoxDelegate(QObject* parent)
  7. : QStyledItemDelegate(parent)
  8. {
  9. }
  10. // createEditor()函数返回一个编辑器小部件, 在本例中是一个QSpinBox。
  11. QWidget* SpinBoxDelegate::createEditor(QWidget* parent,
  12. const QStyleOptionViewItem& /* option */,
  13. const QModelIndex& /* index */) const
  14. {
  15. // QSpinBox在模型中的值限制为0到100(含100)之间的整数
  16. QSpinBox* editor = new QSpinBox(parent);
  17. editor->setFrame(false);
  18. editor->setMinimum(0);
  19. editor->setMaximum(100);
  20. return editor;
  21. }
  22. // setEditorData()函数从模型读取数据, 将其转换为整数值, 并将其写入编辑器小部件。
  23. void SpinBoxDelegate::setEditorData(QWidget* editor,
  24. const QModelIndex& index) const
  25. {
  26. int value = index.model()->data(index, Qt::EditRole).toInt();
  27. // 因为视图将委托视为普通的QWidget实例, 所以在使用QSpinBox之前需要静态强制转换。
  28. QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
  29. spinBox->setValue(value);
  30. }
  31. // setModelData()函数读取QSpinBox的内容, 并将其写入模型:
  32. void SpinBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
  33. const QModelIndex& index) const
  34. {
  35. QSpinBox* spinBox = static_cast<QSpinBox*>(editor);
  36. // interpretText()确保在QSpinBox中获得最新的值。
  37. spinBox->interpretText();
  38. int value = spinBox->value();
  39. model->setData(index, value, Qt::EditRole);
  40. }
  41. // updateEditorGeometry()函数使用样式选项中提供的信息更新编辑器小部件的几何形状。
  42. // 这是委托在本例中必须执行的最小操作。
  43. void SpinBoxDelegate::updateEditorGeometry(QWidget* editor,
  44. const QStyleOptionViewItem& option,
  45. const QModelIndex& /* index */) const
  46. {
  47. editor->setGeometry(option.rect);
  48. }

六、示例结果

image.png