一、效果
二、基本思想
由於無法直接操作表格的 header
,所以只能採用一個 QTableWidget
和 QTableView
組合來實現一個表格, QTableWidget
用來實現表頭,QTableView
用來加載數據。
(一)自定義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;
}