在 Qt 中,QTableView 是一个非常强大的表格控件。本文将详细介绍如何在 QTableView 中合并单元格,并解决鼠标点击合并单元格后高亮显示的问题。
1合并单元格函数原型及说明
在 QTableView 中合并单元格需要自定义 QAbstractItemModel 的 span
函数。span
函数定义如下:
virtual QSize QAbstractItemModel::span(const QModelIndex &index) const;
此函数用于返回指定单元格的跨度(合并尺寸),返回的 QSize 表示从该单元格开始的行跨度和列跨度。
自定义表格模型:
首先,我们需要创建一个自定义的表格模型,继承自 QAbstractTableModel 或 QStandardItemModel,然后重载 span
函数。例如:
2具体效果展示
3代码展示
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTableView>
#include "fruitmodel.h"
#include "fruitdelegate.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QTableView *tableView;
FruitModel *model;
FruitDelegate *delegate;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QHeaderView>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
tableView(new QTableView(this)),
model(new FruitModel(this)),
delegate(new FruitDelegate(this)) {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(tableView);
QWidget *container = new QWidget;
container->setLayout(layout);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
setCentralWidget(container);
#else
layout->setContentsMargins(0, 0, 0, 0); // 可选的,去除边距
setLayout(layout);
#endif
tableView->setModel(model);
tableView->setItemDelegate(delegate);
// 添加水果数据
// Fruit fruit1("苹果", QString::fromLocal8Bit("中国"), 10, 20, 5, QIcon("../Data/china.png"));
// Fruit fruit2("苹果", QString::fromLocal8Bit("日本"), 8, 15, 3, QIcon("../Data/japan.png"));
// Fruit fruit3("苹果", QString::fromLocal8Bit("美国"), 12, 25, 6, QIcon("../Data/usa.png"));
// Fruit fruit4("香蕉", QString::fromLocal8Bit("巴西"), 15, 25, 10,QIcon("../Data/brazil.png"));
// Fruit fruit5("香蕉", QString::fromLocal8Bit("菲律宾"), 10, 20, 8, QIcon("../Data/philippines.png"));
// Fruit fruit6("香蕉", QString::fromLocal8Bit("厄瓜多尔"), 12, 18, 7,QIcon("../Data/ecuador.png"));
// Fruit fruit7("西瓜", QString::fromLocal8Bit("中国"), 20, 30, 10, QIcon("../Data/china.png"));
// Fruit fruit8("西瓜", QString::fromLocal8Bit("美国"), 18, 28, 12, QIcon("../Data/usa.png"));
Fruit fruit1("苹果", "中国", 10, 20, 5, QIcon("../Data/china.png"));
Fruit fruit2("苹果", "日本", 8, 15, 3, QIcon("../Data/japan.png"));
Fruit fruit3("苹果", "美国", 12, 25, 6, QIcon("../Data/usa.png"));
Fruit fruit4("香蕉", "巴西", 15, 25, 10,QIcon("../Data/brazil.png"));
Fruit fruit5("香蕉", "菲律宾", 10, 20, 8, QIcon("../Data/philippines.png"));
Fruit fruit6("香蕉", "厄瓜多尔", 12, 18, 7,QIcon("../Data/ecuador.png"));
Fruit fruit7("西瓜", "中国", 20, 30, 10, QIcon("../Data/china.png"));
Fruit fruit8("西瓜", "美国", 18, 28, 12, QIcon("../Data/usa.png"));
model->addFruit(fruit1);
model->addFruit(fruit2);
model->addFruit(fruit3);
model->addFruit(fruit4);
model->addFruit(fruit5);
model->addFruit(fruit6);
model->addFruit(fruit7);
model->addFruit(fruit8);
// 动态分配单元格
QString currentFruit;
int startRow = 0;
for (int row = 0; row < model->rowCount(); ++row) {
QString fruitName = model->data(model->index(row, 0)).toString();
if (fruitName != currentFruit) {
if (!currentFruit.isEmpty()) {
tableView->setSpan(startRow, 0, row - startRow, 1);
}
currentFruit = fruitName;
startRow = row;
}
}
// 最后一种水果的单元格合并
tableView->setSpan(startRow, 0, model->rowCount() - startRow, 1);
// 设置表格自适应
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
#else
tableView->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
tableView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
#endif
}
MainWindow::~MainWindow() {
}
fruitmodel.h
#ifndef FRUITMODEL_H
#define FRUITMODEL_H
#include <QAbstractTableModel>
#include <QList>
#include <QMap>
#include <QIcon>
struct Fruit {
QString name;
QString origin;
int largeQuantity;
int mediumQuantity;
int smallQuantity;
QIcon icon;
Fruit(const QString &name,
const QString &origin,
int largeQuantity,
int mediumQuantity,
int smallQuantity,
const QIcon &icon)
: name(name),
origin(origin),
largeQuantity(largeQuantity),
mediumQuantity(mediumQuantity),
smallQuantity(smallQuantity),
icon(icon) {}
};
class FruitModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit FruitModel(QObject *parent = nullptr);
void addFruit(const Fruit &fruit);
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:
QList<Fruit> fruits;
};
#endif // FRUITMODEL_H
fruitmodel.cpp
#include "fruitmodel.h"
#include <QStringList>
FruitModel::FruitModel(QObject *parent)
: QAbstractTableModel(parent) {
}
void FruitModel::addFruit(const Fruit &fruit) {
beginInsertRows(QModelIndex(), fruits.count(), fruits.count());
fruits.append(fruit);
endInsertRows();
}
int FruitModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return fruits.count();
}
int FruitModel::columnCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return 3; // 水果型号, 产地, 品质数量
}
QVariant FruitModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) {
return QVariant();
}
const Fruit &fruit = fruits.at(index.row());
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0: // 水果型号
return fruit.name;
case 1: // 产地
return fruit.origin;
case 2: // 品质数量
return QString("大: %1 \n中: %2 \n小: %3")
.arg(fruit.largeQuantity)
.arg(fruit.mediumQuantity)
.arg(fruit.smallQuantity);
}
} else if (role == Qt::DecorationRole && index.column() == 1) {
return fruit.icon;
}
return QVariant();
}
QVariant FruitModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole) {
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return "水果型号";
case 1:
return "产地";
case 2:
return "品质数量";
}
}
}
return QVariant();
}
fruitdelegate.h
#ifndef FRUITDELEGATE_H
#define FRUITDELEGATE_H
#include <QStyledItemDelegate>
class FruitDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit FruitDelegate(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 // FRUITDELEGATE_H
fruitdelegate.cpp
#include "fruitdelegate.h"
#include <QPainter>
#include <QApplication>
#include <QTextOption>
FruitDelegate::FruitDelegate(QObject *parent)
: QStyledItemDelegate(parent) {
}
void FruitDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
// 调用基类的paint方法,确保其他列正常绘制
QStyledItemDelegate::paint(painter, option, index);
// 判断是否是第3列(品质数量)
if (index.column() == 2) {
// 获取当前单元格的数据并转换为字符串
QString text = index.data().toString();
// 将字符串按空格分割为多个部分
QStringList lines = text.split(" ");
// 获取单元格的矩形区域
QRect rect = option.rect;
// 保存当前的画笔状态
painter->save();
// 将画笔的原点移动到矩形的左上角
painter->translate(rect.topLeft());
// 逆时针旋转90度
painter->rotate(-90);
// 调整矩形的大小,使其适应旋转后的内容
rect.setSize(rect.size().transposed());
// 设置文本选项,使文本居中对齐
QTextOption textOption;
textOption.setAlignment(Qt::AlignCenter);
// 在旋转后的矩形中绘制文本,将分割的行用换行符连接
painter->drawText(rect, lines.join("\n"), textOption);
// 恢复画笔的状态
painter->restore();
}
}
QSize FruitDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
QSize size = QStyledItemDelegate::sizeHint(option, index);
if (index.column() == 2) { // 品质数量
size.setHeight(option.fontMetrics.height() * 3); // 调整高度以适应垂直文本
}
return size;
}