Qt 中使用自定义委托来绘制复杂的表格项,包括背景颜色、文本和图片,并且使表格随着窗体大小自动调整列宽QTableView

在 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 坐标。

计算文本起始位置

  1. 总宽度:300 像素
  2. 产地文本:显示在 1/ 4处,宽度为 50 像素
  3. 水果信息文本:显示在 2/4 处,宽度为 100 像素
  4. 加号文本:显示在 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 的位置和大小
  1. 产地文本区域

    • 起始 X 坐标:50 像素
    • 矩形区域宽度:50 像素
    • 调整 QRectrect.adjusted(50, 0, -250, 0),其中 -250 是调整后的宽度。
  2. 水果信息文本区域

    • 起始 X 坐标:100 像素
    • 矩形区域宽度:100 像素
    • 调整 QRectrect.adjusted(100, 0, -200, 0),其中 -200 是调整后的宽度。
  3. 加号文本区域

    • 起始 X 坐标:215 像素
    • 矩形区域宽度:20 像素
    • 调整 QRectrect.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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值