一、效果
二、基本思想
由于无法直接操作表格的 header
,所以只能采用一个 QTableWidget
和 QTableView
组合来实现一个表格, QTableWidget
用来实现表头,QTableView
用来加载数据。
注意:一旦设置代理后,选中行之后,背景色无法高亮!!
解决办法:把代理去掉,文字居中的话,采用 item->setTextAlignment(Qt::AlignCenter)
(一)自定义TableView
CustomTableView.h
#ifndef CUSTOMTABLEVIEW_H
#define CUSTOMTABLEVIEW_H
#include <QTableWidget>
#include <QMap>
#define HEADER_ROW_COUNT 2 //表头行数
#define HEADER_ROW_HEIGHT 42 //表头行高
class CustomTableView : public QTableView
{
public:
CustomTableView(int columnCount, std::function<void (QTableWidget *)> addTableHeader, std::function<void (QTableView *)> setColumnsNoEdit, QWidget *parent = Q_NULLPTR);
~CustomTableView();
void setColumnRowCounts(int columnCount, int rowCount); //设置表格内容几行几列
protected:
//重载 resize事件,更新 headerTableWidget 位置
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
//重载 headerTableWidget 移动事件
void scrollTo(const QModelIndex &index, ScrollHint hint) Q_DECL_OVERRIDE;
//冲在虚函数 鼠标移动事件
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE;
private:
QTableWidget *headerTableWidget; //自定义表头,实现表头单元格合并
int columnCount; //表格有几列
int rowCount; //表格内容有几行
private:
//初始化表头(PS:表头的具体几行几列合并,由调用方决定)
void initTableHeader(std::function<void(QTableWidget *headerTableWidget)> addTableHeader);
//初始化显示内容的 TableView(PS:哪些列不可编辑,由调用方决定)
void initTableContent(std::function<void(QTableView *tableView)> setColumnsNoEdit);
void updateHeaderTableGeometry(); //更新表头的位置
};
#endif // CUSTOMTABLEVIEW_H
CustomTableView.cpp
#include "customtableview.h"
#include <QHeaderView>
#include <QScrollBar>
#include "itemdelegatealigncenter.h"
CustomTableView::CustomTableView(int columnCount, std::function<void (QTableWidget *)> addTableHeader,
std::function<void (QTableView *)> setColumnsNoEdit, QWidget *parent) : QTableView(parent)
{
this->columnCount = columnCount;
initTableHeader(addTableHeader);
initTableContent(setColumnsNoEdit);
//添加表格数据
}
CustomTableView::~CustomTableView()
{
delete headerTableWidget;
}
void CustomTableView::setColumnRowCounts(int columnCount, int rowCount)
{
this->columnCount = columnCount;
this->rowCount = rowCount;
}
void CustomTableView::resizeEvent(QResizeEvent *event)
{
QAbstractItemView::resizeEvent(event);
updateHeaderTableGeometry();
}
void CustomTableView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
{
if (index.row() > 0) {
QTableView::scrollTo(index, hint);
}
}
//看不懂这个函数是啥意思
QModelIndex CustomTableView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
QModelIndex currentModelIndex = QTableView::moveCursor(cursorAction, modifiers);
if (cursorAction == QAbstractItemView::MoveUp && currentModelIndex.row() > 0
&& this->visualRect(currentModelIndex).topLeft().y() < headerTableWidget->rowHeight(1)) {
int newValue = this->verticalScrollBar()->value() + this->visualRect(currentModelIndex).topLeft().y()
- headerTableWidget->rowHeight(0) - headerTableWidget->rowHeight(1);
this->verticalScrollBar()->setValue(newValue);
}
return currentModelIndex;
}
void CustomTableView::initTableHeader(std::function<void (QTableWidget *)> addTableHeader)
{
headerTableWidget = new QTableWidget(this);
headerTableWidget->horizontalHeader()->setVisible(false);
headerTableWidget->verticalHeader()->setVisible(false);
// headerTableWidget->setShowGrid(false); //网格线不可见
headerTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); //设置单元格不可编辑
headerTableWidget->horizontalHeader()->setStretchLastSection(true); //最后一个单元格扩展
headerTableWidget->setFocusPolicy(Qt::NoFocus); //解决选中虚框问题
headerTableWidget->setFrameShape(QFrame::NoFrame); //去除边框(QTableView 默认去除边框,若这里不去除,则内容和表头无法对齐)
headerTableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //隐藏垂直滚动条
headerTableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //隐藏水平滚动条
headerTableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); //一次滚动一个像素
headerTableWidget->setItemDelegate(new ItemDelegateAlignCenter(true, this)); //设置绘画代理(若表头有特殊样式要求,可以在这里绘制出来)
this->viewport()->stackUnder(headerTableWidget); //设置窗口层次,表头始终在内容表格上面
headerTableWidget->setColumnCount(this->columnCount); //表头几列
headerTableWidget->setRowCount(HEADER_ROW_COUNT); //表头几行
for (int i=0; i<HEADER_ROW_COUNT; i++) {
headerTableWidget->setRowHeight(0, HEADER_ROW_HEIGHT); //设置行高
}
//隐藏2行后的行(因为 TableWidget 默认会有一些空行存在)
for (int i=HEADER_ROW_COUNT; i<headerTableWidget->rowCount(); i++) {
headerTableWidget->setRowHidden(i, true);
}
//设置表头内容和格式(该方法已经抽象,由调用方决定)
addTableHeader(headerTableWidget);
//表头水平滚动条滚动,表格内容也随之滚动
connect(headerTableWidget->horizontalScrollBar(), SIGNAL(valueChanged(int)),
this->horizontalScrollBar(), SLOT(setValue(int)));
//表格内容水平滚动条滚动,表头也随之滚动
connect(this->horizontalScrollBar(), SIGNAL(valueChanged(int)),
headerTableWidget->horizontalScrollBar(), SLOT(setValue(int)));
updateHeaderTableGeometry(); //更新位置
headerTableWidget->show(); //显示
}
void CustomTableView::initTableContent(std::function<void (QTableView *)> setColumnsNoEdit)
{
this->horizontalHeader()->setVisible(true); //表头可见(留给 headerTableWidget 遮挡,否则就会遮挡表格内容)
//设置表头高度,和 headerTableWidget 总高度一致
int headerHeight = 0;
for (int i=0; i<HEADER_ROW_COUNT; i++) {
headerHeight += headerTableWidget->rowHeight(i);
}
this->horizontalHeader()->setFixedHeight(headerHeight);
this->verticalHeader()->setVisible(false);
// this->setShowGrid(false); //网格线不可见
// this->setEditTriggers(QAbstractItemView::NoEditTriggers);
//设置哪些列不可编辑
setColumnsNoEdit(this);
this->setSelectionMode(QAbstractItemView::SingleSelection); //单选
this->setSelectionBehavior(QAbstractItemView::SelectRows); //选行
this->horizontalHeader()->setStretchLastSection(true); //最后一个单元格扩展
this->setFocusPolicy(Qt::NoFocus); //解决选中虚框问题
// this->setFrameShape(QFrame::NoFrame); //去除边框
this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); //一次滚动一个像素
this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); //一次滚动一个像素
this->setItemDelegate(new ItemDelegateAlignCenter(false, this)); //设置绘画代理(若表格有特殊样式要求,可以在这里绘制出来)
}
void CustomTableView::updateHeaderTableGeometry()
{
headerTableWidget->setGeometry(this->frameWidth(), this->frameWidth(), this->viewport()->width(),
this->horizontalHeader()->height());
}
(二)单元格文字居中代理
ItemDelegateAlignCenter.h
#ifndef ITEMDELEGATEALIGNCENTER_H
#define ITEMDELEGATEALIGNCENTER_H
#include <QStyledItemDelegate>
class ItemDelegateAlignCenter : public QStyledItemDelegate
{
public:
ItemDelegateAlignCenter(bool isHead, QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
private:
bool isHead; //是不是表头
};
#endif // ITEMDELEGATEALIGNCENTER_H
ItemDelegateAlignCenter.cpp
#include "itemdelegatealigncenter.h"
#include <QTextOption>
#include <QPainter>
ItemDelegateAlignCenter::ItemDelegateAlignCenter(bool isHead, QObject *parent) :
isHead(isHead),
QStyledItemDelegate(parent)
{
}
void ItemDelegateAlignCenter::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//若是表头,设置单元格背景色
if (isHead) {
QColor color(148, 169, 205);
painter->fillRect(option.rect, color);
}
//单元格内文字居中
QTextOption op;
op.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
//设置字体
QFont font;
font.setFamily("Microsoft YaHei");
font.setPixelSize(14);
// font.setBold(true);
painter->setFont(font);
//单元格文字居左
// QRect rect;
// rect = QRect(option.rect.x(), option.rect.y(), 100, option.rect.height());
// painter->drawText(rect, index.data(Qt::DisplayRole).toString(), op);
painter->drawText(option.rect, index.data(Qt::DisplayRole).toString(), op);
}
(三)单元格只读代理
ItemDelegateReadOnly.h
#ifndef ITEMDELEGATEREADONLY_H
#define ITEMDELEGATEREADONLY_H
#include <QItemDelegate>
class ItemDelegateReadOnly : public QItemDelegate
{
public:
ItemDelegateReadOnly(QWidget *parent = 0);
protected:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
};
#endif // ITEMDELEGATEREADONLY_H
ItemDelegateReadOnly.cpp
#include "itemdelegatereadonly.h"
#include <QTextOption>
#include <QPainter>
ItemDelegateReadOnly::ItemDelegateReadOnly(QWidget *parent) : QItemDelegate(parent)
{
}
QWidget *ItemDelegateReadOnly::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//单元格只读,不可编辑
Q_UNUSED(parent);
Q_UNUSED(option);
Q_UNUSED(index);
return NULL;
}
void ItemDelegateReadOnly::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//文字居中
QTextOption op;
op.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
//设置字体
QFont font;
font.setFamily("Microsoft YaHei");
font.setPixelSize(14);
// font.setBold(true);
painter->setFont(font);
painter->drawText(option.rect, index.data(Qt::DisplayRole).toString(), op);
}
(四)使用案例
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include "customtableview.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
CustomTableView *tableView;
QStandardItemModel *model;
QItemSelectionModel *selectionModel;
};
#endif // WIDGET_H
Widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QTableWidget>
#include <QPushButton>
#include <QHBoxLayout>
#include "itemdelegatereadonly.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tableView = new CustomTableView(11, [](QTableWidget *headerTable){
//设置header内容
headerTable->setSpan(0, 0, 2, 1);
headerTable->setSpan(0, 1, 2, 1);
headerTable->setSpan(0, 2, 2, 1);
headerTable->setSpan(0, 3, 1, 3);
headerTable->setSpan(0, 6, 1, 4);
headerTable->setSpan(0, 10, 2, 1);
headerTable->setItem(0, 0, new QTableWidgetItem("表头1"));
headerTable->setItem(0, 1, new QTableWidgetItem("表头2"));
headerTable->setItem(0, 2, new QTableWidgetItem("表头3"));
headerTable->setItem(0, 3, new QTableWidgetItem("表头4"));
headerTable->setItem(0, 6, new QTableWidgetItem("表头5"));
headerTable->setItem(0, 10, new QTableWidgetItem("表头6"));
headerTable->setItem(1, 3, new QTableWidgetItem("表头7"));
headerTable->setItem(1, 4, new QTableWidgetItem("表头8"));
headerTable->setItem(1, 5, new QTableWidgetItem("表头9"));
headerTable->setItem(1, 6, new QTableWidgetItem("表头10"));
headerTable->setItem(1, 7, new QTableWidgetItem("表头11"));
headerTable->setItem(1, 8, new QTableWidgetItem("表头12"));
headerTable->setItem(1, 9, new QTableWidgetItem("表头13"));
}, [this](QTableView *contentTable){
ItemDelegateReadOnly *itemDelegate = new ItemDelegateReadOnly(this);
contentTable->setItemDelegateForColumn(0, itemDelegate);
contentTable->setItemDelegateForColumn(1, itemDelegate);
contentTable->setItemDelegateForColumn(2, itemDelegate);
}, this);
model = new QStandardItemModel(this);
model->setRowCount(1);
model->setColumnCount(11);
selectionModel = new QItemSelectionModel(model);
tableView->setModel(model);
tableView->setSelectionModel(selectionModel);
QStandardItem *item;
item = new QStandardItem("1");
model->setItem(0, 0, item);
item = new QStandardItem("1");
model->setItem(0, 1, item);
item = new QStandardItem("1");
model->setItem(0, 2, item);
item = new QStandardItem("3544973");
model->setItem(0, 3, item);
item = new QStandardItem("20699057");
model->setItem(0, 4, item);
item = new QStandardItem("42");
model->setItem(0, 5, item);
item = new QStandardItem("25000");
model->setItem(0, 6, item);
item = new QStandardItem("3000");
model->setItem(0, 7, item);
item = new QStandardItem("-400");
model->setItem(0, 8, item);
item = new QStandardItem("400");
model->setItem(0, 9, item);
item = new QStandardItem("11-11-11");
model->setItem(0, 10, item);
//设置水平布局,表格铺满控件
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(tableView);
}
Widget::~Widget()
{
delete ui;
}