在 Qt 中自定义委托(Delegate)是一种非常强大的功能,允许我们根据需要定制每个单元格的显示内容。本文将介绍如何使用自定义委托来绘制包含背景颜色、文本和图片的自定义 Item。
要求
自定义绘制单元格item
显示要素一 在单元格左侧显示图标
显示要素二 中间绘制文本信息
显示要素三 具体信息 垂直排列
显示要素三 如果显示的内容多则添加 +号文本
效果展示
知识点汇总
自定义消息类注册
假设你的结构体定义如下:
namespace D {
struct B {
int id;
QString name;
};
struct C {
int id;
QString description;
B innerB; // 结构体 C 中包含结构体 B
};
} // namespace D
Q_DECLARE_METATYPE(D::B)
Q_DECLARE_METATYPE(D::C)
Q_DECLARE_METATYPE(QVector<D::B>)
Q_DECLARE_METATYPE(QVector<D::C>)
在源文件中使用
在你的源文件中,需要在 main
函数或者其他初始化函数中调用 qRegisterMetaType
来注册这些类型:
// 注册自定义类型
qRegisterMetaType<D::B>("D::B");
qRegisterMetaType<D::C>("D::C");
qRegisterMetaType<QVector<D::B>>("QVector<D::B>");
qRegisterMetaType<QVector<D::C>>("QVector<D::C>");
const QVector<FruitBox>& fruitBoxes = index.data(Qt::UserRole).value<QVector<FruitBox>>();
else if (role == Qt::UserRole && index.column() == 3) {
return QVariant::fromValue(student.fruitBoxes);
QTableView 中存储和显示自定义数据结构 类似setData
QVariant CharacterModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
const Character &character = characters[index.row()];
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0:
return character.name;
case 1:
return character.level;
case 2:
return character.hobby;
default:
return QVariant();
}
} else if (role == Qt::DecorationRole && index.column() == 3) {
return character.icon;
}
return QVariant();
}
可以用来存储一些你想要获取的数据 用于代理自定义绘制
绘制过程中rect.adjusted 函数的功能和用法
adjusted 函数里面的数字 代表举例左上右下距离rect 区域的距离
图中有四部分显示构成
为了更好地理解如何通过调整 QRect
的位置和大小来实现所需的布局,我们可以通过具体的数字进行说明。
假设单元格的总宽度为 300 像素,文本部分的宽度如下:
- 产地文本(
originText
)的宽度为 50 像素。 - 水果信息文本(
fruitInfo
)的宽度为 100 像素。 - 加号文本(
plusSign
)的宽度为 20 像素。
我们需要将产地文本居中显示在单元格的 1/4 处,水果信息居中显示在单元格的 1/2 处,加号文本居中显示在单元格的 3/4 处。我们先计算这些文本在单元格中的起始 X 坐标。
计算文本起始位置
- 总宽度:300 像素
- 产地文本:显示在 1/ 4处,宽度为 50 像素
- 水果信息文本:显示在 2/4 处,宽度为 100 像素
- 加号文本:显示在 3/4 处,宽度为 20 像素
产地文本位置
- 单元格的 1/4 处的 X 坐标:
300 / 4 = 75
像素 - 产地文本宽度:50 像素
- 产地文本的起始 X 坐标应为:
75 - 50 / 2 = 50
像素
水果信息文本位置
- 单元格的 1/2 处的 X 坐标:
300 / 2 = 150
像素 - 水果信息文本宽度:100 像素
- 水果信息文本的起始 X 坐标应为:
150 - 100 / 2 = 100
像素
加号文本位置
- 单元格的 3/4 处的 X 坐标:
300 * 3 / 4 = 225
像素 - 加号文本宽度:20 像素
- 加号文本的起始 X 坐标应为:
225 - 20 / 2 = 215
像素
调整矩形区域
为了更好地理解,我们将使用具体的数值来说明:
调整 QRect
的位置和大小
-
产地文本区域:
- 起始 X 坐标:50 像素
- 矩形区域宽度:50 像素
- 调整
QRect
:rect.adjusted(50, 0, -250, 0)
,其中-250
是调整后的宽度。
-
水果信息文本区域:
- 起始 X 坐标:100 像素
- 矩形区域宽度:100 像素
- 调整
QRect
:rect.adjusted(100, 0, -200, 0)
,其中-200
是调整后的宽度。
-
加号文本区域:
- 起始 X 坐标:215 像素
- 矩形区域宽度:20 像素
- 调整
QRect
:rect.adjusted(215, 0, -85, 0)
,其中-85
是调整后的宽度。
FruitBoxDelegate.h
#ifndef FRUITBOXDELEGATE_H
#define FRUITBOXDELEGATE_H
#include <QStyledItemDelegate>
#include <QPainter>
#include "StudentTableModel.h"
class FruitBoxDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit FruitBoxDelegate(QObject* parent = nullptr);
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
#endif // FRUITBOXDELEGATE_H
FruitBoxDelegate.cpp
#include "FruitBoxDelegate.h" // 包含 FruitBoxDelegate 头文件
#include <QPainter> // 包含 QPainter 类的定义,用于绘制
#include <QPixmap> // 包含 QPixmap 类的定义,用于处理图片
#include <QApplication> // 包含 QApplication 类的定义
// 构造函数,初始化委托对象
FruitBoxDelegate::FruitBoxDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}
// 重写 paint 方法,用于自定义绘制单元格内容
void FruitBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
painter->save(); // 保存当前绘制状态
// 从模型中获取果箱数据
const QVector<FruitBox>& fruitBoxes = index.data(Qt::UserRole).value<QVector<FruitBox>>();
QRect rect = option.rect; // 获取单元格的矩形区域
painter->setClipRect(rect); // 设置剪切区域,确保绘制不会超出单元格
QFont font = painter->font(); // 获取当前字体
font.setPointSize(font.pointSize() - 2); // 缩小字体
painter->setFont(font); // 应用缩小后的字体
QPixmap flagPixmap; // 定义一个 QPixmap 对象,用于存储国旗图片
if (!fruitBoxes.isEmpty()) { // 检查是否有果箱数据
const FruitBox& firstBox = fruitBoxes[0]; // 获取第一个果箱
QString originText = firstBox.origin; // 获取产地信息
QString fruitInfo = QString("苹: %1 香: %2 西: %3")
.arg(firstBox.appleCount)
.arg(firstBox.bananaCount)
.arg(firstBox.watermelonCount); // 构建水果信息字符串
QString plusSign; // 初始化加号字符串
bool hasMultipleBoxes = fruitBoxes.size() > 1; // 检查是否有多个果箱
if (hasMultipleBoxes) {
plusSign = "(+)"; // 如果有多个果箱,设置加号信息
}
// 加载国旗图片,根据产地选择相应的图片
if (originText == "中国") {
flagPixmap = QPixmap(":/flags/china.png"); // 加载中国国旗图片
} else if (originText == "日本") {
flagPixmap = QPixmap(":/flags/japan.png"); // 加载日本国旗图片
} else if (originText == "美国") {
flagPixmap = QPixmap(":/flags/usa.png"); // 加载美国国旗图片
}
int totalWidth = rect.width(); // 获取单元格的总宽度
int totalHeight = rect.height(); // 获取单元格的总高度
// 调整图片大小,使其占据单元格的 2/10
int flagWidth = totalWidth * 2 / 10; // 计算图片宽度
int flagHeight = totalHeight * 2 / 10; // 计算图片高度
flagPixmap = flagPixmap.scaled(flagWidth, flagHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); // 调整图片大小
// 计算各部分宽度
int originTextWidth = painter->fontMetrics().horizontalAdvance(originText); // 计算产地文本的宽度
int fruitInfoWidth = painter->fontMetrics().horizontalAdvance(fruitInfo); // 计算水果信息文本的宽度
int plusSignWidth = painter->fontMetrics().horizontalAdvance(plusSign); // 计算加号文本的宽度
// 确定各部分的绘制区域
int flagX, originX, fruitInfoX, plusSignX;
flagX = totalWidth / 10 - flagPixmap.width() / 2; // 国旗图片在单元格的 1/10 处居中显示
originX = totalWidth * 4 / 10 - originTextWidth / 2; // 产地文本在单元格的 4/10 处居中显示
fruitInfoX = totalWidth * 7 / 10 - fruitInfoWidth / 2; // 水果信息在单元格的 7/10 处居中显示
if (hasMultipleBoxes) {
plusSignX = totalWidth - plusSignWidth - 2; // 加号文本在单元格的最右处,间隔 2 像素居中显示
} else {
plusSignX = -1; // 不需要加号
}
// 调整绘制区域的矩形
QRect flagRect(flagX, rect.top() + (rect.height() - flagPixmap.height()) / 2, flagPixmap.width(), flagPixmap.height()); // 调整国旗图片的绘制区域
QRect originRect = rect.adjusted(originX, 0, -(totalWidth - originX - originTextWidth), 0); // 调整产地文本的绘制区域
QRect fruitInfoRect = rect.adjusted(fruitInfoX, 0, -(totalWidth - fruitInfoX - fruitInfoWidth), 0); // 调整水果信息文本的绘制区域
QRect plusSignRect = rect.adjusted(plusSignX, 0, -(totalWidth - plusSignX - plusSignWidth), 0); // 调整加号文本的绘制区域
// 绘制国旗图片
painter->drawPixmap(flagRect);
// 绘制产地(居中显示)
painter->drawText(originRect, Qt::AlignCenter, originText);
// 绘制水果信息
painter->drawText(fruitInfoRect, Qt::AlignCenter | Qt::AlignVCenter, fruitInfo);
// 绘制加号(加粗和更大字号)
if (hasMultipleBoxes) {
QFont boldFont = painter->font();
boldFont.setBold(true);
boldFont.setPointSize(boldFont.pointSize() + 2); // 增大字号
painter->setFont(boldFont);
painter->drawText(plusSignRect, Qt::AlignCenter | Qt::AlignVCenter, plusSign);
}
}
painter->restore(); // 恢复绘制状态
}
// 重写 sizeHint 方法,返回单元格的建议大小
QSize FruitBoxDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
Q_UNUSED(option); // 未使用的参数
Q_UNUSED(index); // 未使用的参数
return QSize(500, 80); // 调整大小以适应所有内容,增加宽度到 500
}
StudentTableModel.h
#ifndef STUDENTTABLEMODEL_H
#define STUDENTTABLEMODEL_H
#include <QAbstractTableModel>
#include <QString>
#include <QVector>
#include <QVariant>
struct FruitBox {
QString origin;
int appleCount;
int bananaCount;
int watermelonCount;
};
struct Student {
int id;
QString name;
QString hobby;
QVector<FruitBox> fruitBoxes;
};
Q_DECLARE_METATYPE(FruitBox)
Q_DECLARE_METATYPE(QVector<FruitBox>)
class StudentTableModel : public QAbstractTableModel {
Q_OBJECT
public:
StudentTableModel(QObject* parent = nullptr);
void setStudents(const QVector<Student>& students);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
private:
QVector<Student> m_students;
};
#endif // STUDENTTABLEMODEL_H
StudentTableModel.cpp
#include "StudentTableModel.h"
StudentTableModel::StudentTableModel(QObject* parent)
: QAbstractTableModel(parent) {}
void StudentTableModel::setStudents(const QVector<Student>& students) {
beginResetModel();
m_students = students;
endResetModel();
}
int StudentTableModel::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_students.size();
}
int StudentTableModel::columnCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return 4; // 学号, 姓名, 爱好, 果箱展示
}
QVariant StudentTableModel::data(const QModelIndex& index, int role) const {
if (!index.isValid())
return QVariant();
const Student& student = m_students[index.row()];
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0: return student.id;
case 1: return student.name;
case 2: return student.hobby;
default: return QVariant();
}
} else if (role == Qt::UserRole && index.column() == 3) {
return QVariant::fromValue(student.fruitBoxes);
}
return QVariant();
}
QVariant StudentTableModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0: return QStringLiteral("学号");
case 1: return QStringLiteral("姓名");
case 2: return QStringLiteral("爱好");
case 3: return QStringLiteral("果箱展示");
default: return QVariant();
}
}
return QVariant();
}
main.cpp
#include <QApplication>
#include <QTableView>
#include "StudentTableModel.h"
#include "FruitBoxDelegate.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QVector<Student> students = {
{1, "张三", "篮球", {{ "中国", 3, 5, 6 }}},
{2, "李四", "足球", {{ "中国", 3, 5, 6 }, { "美国", 2, 3, 4 }}}
};
qRegisterMetaType<QVector<FruitBox>>("QVector<FruitBox>");
StudentTableModel* model = new StudentTableModel();
model->setStudents(students);
QTableView* view = new QTableView();
view->setModel(model);
view->setItemDelegateForColumn(3, new FruitBoxDelegate());
view->verticalHeader()->setDefaultSectionSize(80); // 设置默认行高
view->show();
return app.exec();
}
-
疑难解释
-
对于自定义的数据存储和获取
-
const QVector<FruitBox>& fruitBoxes = index.data(Qt::UserRole).value<QVector<FruitBox>>();
:从模型中获取果箱数据。
-
获取单元格的矩形区域:
QRect rect = option.rect;
:获取单元格的矩形区域。painter->setClipRect(rect);
:设置剪切区域,确保绘制不会超出单元格。
-
调整字体大小:
QFont font = painter->font();
:获取当前字体。font.setPointSize(font.pointSize() - 2);
:缩小字体。painter->setFont(font);
:应用缩小后的字体。
-
调整图片大小:
int flagWidth = totalWidth * 2 / 10;
:计算图片宽度,使其占据单元格的 2/10。int flagHeight = totalHeight * 2 / 10;
:计算图片高度。flagPixmap = flagPixmap.scaled(flagWidth, flagHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
:调整图片大小。
-
计算各部分宽度:
- 计算产地文本、水果信息文本和加号文本的宽度。
-
确定各部分的绘制区域:
- 根据文本和图片的位置,调整各部分的绘制区域。
-
绘制国旗图片:
painter->drawPixmap(flagRect);
:绘制国旗图片。
-
绘制文本:
- 使用
painter->drawText
绘制产地文本、水果信息和加号文本。
- 使用
-
恢复绘制状态:
painter->restore();
:恢复绘制状态。
-
重写 sizeHint 方法:
QSize FruitBoxDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
:返回单元格的建议大小,增加宽度到 500。
具体代码可以参考gitee Delegate_Model_View案例
https://gitee.com/myqtproject/my-table-widget.git