效果图
方案一
- 参考QT示例项目中代码,简单思路为使用俩个QtaleView A,B进行处理,A表格覆盖在B表格之上,A表格只显示第一行,其余行进行隐藏处理。关键在于俩个表格要使用同一份模型,和光标同步。
// 设置和父table同步的光标选择单元格(必要),修改统一数据
frozenTableView->setSelectionModel(selectionModel());
// 将tableview放到frozenTableView视图下,进行覆盖
viewport()->stackUnder(frozenTableView);
在表格进行变化时,通过检测resizeEvent函数来进行处理。
- .h文件
class MyTableView : public QTableView
{
Q_OBJECT
public:
MyTableView(QLabel *label, QSortFilterProxyModel *sortModel, QWidget *parent = 0);
~MyTableView() { delete frozenTableView; }
protected:
/// @brief 在QTableView的大小调整时进行自定义的处理操作,更新表格的内容,调整表格的列宽和行高
/// @param event
void resizeEvent(QResizeEvent *event) override;
/// @brief 可以自定义滚动的行为
/// @param index 表示需要滚动到可见区域的模型索引。
/// @param hint 表示滚动的提示,有多个选项可用,其中EnsureVisible是默认选项,表示确保索引可见。
void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override;
protected:
QLabel *messageLabel;
private:
QTableView *frozenTableView;
void init();
/// 变换表格长度
void updateFrozenTableGeometry();
private slots:
void updateSectionWidth(int logicalIndex, int oldSize, int newSize);
/// @brief 拖拽表头时,实时变换长度
/// @param logicalIndex
/// @param oldSize
/// @param newSize
void updateSectionHeight(int logicalIndex, int oldSize, int newSize);
};
- .cpp文件
MyTableView::MyTableView(QLabel *label, QSortFilterProxyModel *sortModel, QWidget *parent)
: QTableView(parent), messageLabel(label)
{
frozenTableView = new QTableView(this);
// 设置代理模型
setModel(sortModel);
frozenTableView->setModel(sortModel);
init();
connect(horizontalHeader(), &QHeaderView::sectionResized, this, &MyTableView::updateSectionWidth);
connect(verticalHeader(), &QHeaderView::sectionResized, this, &MyTableView::updateSectionHeight);
connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged, verticalScrollBar(),
&QAbstractSlider::setValue);
connect(verticalScrollBar(), &QAbstractSlider::valueChanged, frozenTableView->verticalScrollBar(),
&QAbstractSlider::setValue);
};
void MyTableView::resizeEvent(QResizeEvent *event)
{
QTableView::resizeEvent(event);
updateFrozenTableGeometry();
}
void MyTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
{
if (index.column() > 0)
QTableView::scrollTo(index, hint);
}
void MyTableView::init()
{
//**TableView**//
// 升序
sortByColumn(ElecWizardTableModel::ParamClassify, Qt::AscendingOrder);
// 设置委托
setItemDelegate(new ElecComboxDelegate(this));
// 水平表头允许拖拽调整宽度
horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
// 垂直表头不可见
verticalHeader()->setVisible(false);
// 点击表头允许排序
setSortingEnabled(true);
// 隐藏排序后出现的箭头
horizontalHeader()->setSortIndicatorShown(false);
// 任意键都可以来编辑表格中的单元格
setEditTriggers(QTableView::AllEditTriggers);
// 将tableview放到frozenTableView视图下,进行覆盖
viewport()->stackUnder(frozenTableView);
// // 最后一列自动填充列宽
// horizontalHeader()->setStretchLastSection(true);
// // 以像素为单位滚动
// setHorizontalScrollMode(ScrollPerPixel);
// setVerticalScrollMode(ScrollPerPixel);
// end
//**frozenTableView**//
// 升序
frozenTableView->sortByColumn(ElecWizardTableModel::ParamClassify, Qt::AscendingOrder);
// 设置无焦点
frozenTableView->setFocusPolicy(Qt::NoFocus);
// 点击表头允许排序
frozenTableView->setSortingEnabled(true);
// 隐藏排序后出现的箭头
frozenTableView->horizontalHeader()->setSortIndicatorShown(false);
// 隐藏垂直表头
frozenTableView->verticalHeader()->hide();
// frozenTableView水平表头的各个段宽度无法被拖动调整大小
frozenTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
// 设置和父table同步的光标选择单元格(必要),修改统一数据
frozenTableView->setSelectionModel(selectionModel());
// 隐藏frozenTableView第一列以外的数据
for (int col = 1; col < ElecWizardTableModel::Column::end; ++col)
frozenTableView->setColumnHidden(col, true);
// frozenTableView使用与tableview第一列相同的宽高
frozenTableView->setColumnWidth(0, columnWidth(0));
frozenTableView->setRowHeight(0, rowHeight(0));
// frozenTableView不显示滑动条
frozenTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
frozenTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
frozenTableView->show();
// // 以像素为单位滚动
// frozenTableView->setVerticalScrollMode(ScrollPerPixel);
// end
updateFrozenTableGeometry();
}
void MyTableView::updateFrozenTableGeometry()
{
// 获取单个单元格的高度
QModelIndex index = model()->index(0, 0);
QRect rect = visualRect(index);
// 行数*行高+表头高+行数(边线1px)
int height = model()->rowCount() * rect.height() + horizontalHeader()->height() + model()->rowCount();
// 当前视图下的高度
int height2 = viewport()->height() + horizontalHeader()->height();
// 当表格未满时使用height,防止第一列出现多余的框
int resultHeight = height2 < height ? height2 : height;
// 设置第一列的几何大小
frozenTableView->setGeometry(verticalHeader()->width() + frameWidth(), frameWidth() - 1, columnWidth(0),
resultHeight);
}
void MyTableView::updateSectionHeight(int logicalIndex, int oldSize, int newSize)
{
frozenTableView->setRowHeight(logicalIndex, newSize);
}
void MyTableView::updateSectionWidth(int logicalIndex, int oldSize, int newSize)
{
if (logicalIndex == 0) {
frozenTableView->setColumnWidth(0, newSize);
updateFrozenTableGeometry();
}
}
方案二
-
可以通过自定义QScrollBar滑动条,加入到布局中,隐藏原有表格的滑动条,通过自定义的滑动条与表格的联动算法从而实现冻结列的效果。
-
自定义滑动条代码如下
class CustomHorizontalScrollBar : public QScrollBar
{
Q_OBJECT
public:
explicit CustomHorizontalScrollBar(QWidget* parent = nullptr);
~CustomHorizontalScrollBar();
void initCustomScrollbar(const int& freezecols);
int getFreezeColno() const { return m_freezeCols; }
private slots:
void onValueChanged(int value);
signals:
void scrollbarValueChanged(bool bscrollright, const int& begincol, const int& changecolno);
private:
int m_freezeCols;
int m_oldposition;
};
// 自定义滚动条
CustomHorizontalScrollBar::CustomHorizontalScrollBar(QWidget* parent /*= nullptr*/)
: QScrollBar(parent), m_freezeCols(0), m_oldposition(0)
{
this->setOrientation(Qt::Horizontal);
this->setSingleStep(10);
connect(this, SIGNAL(valueChanged(int)), this, SLOT(onValueChanged(int)));
}
void CustomHorizontalScrollBar::initCustomScrollbar(const int& freezecols)
{
m_freezeCols = freezecols;
m_oldposition = 0;
this->setValue(0);
}
void CustomHorizontalScrollBar::onValueChanged(int value)
{
// 左滑
if (m_oldposition > value) {
emit scrollbarValueChanged(false, m_freezeCols + m_oldposition - 1, m_oldposition - value);
} else if (m_oldposition < value) {
// 右滑
emit scrollbarValueChanged(true, m_freezeCols + m_oldposition, value - m_oldposition);
}
m_oldposition = value;
}
自定义滑动条中要维护一个固定列数和记录上一次位置的变量,通过滑动条的valueChanged
信号触发自定义信号onValueChanged
信号,从而连接到widget上表格的槽函数onHorizontalValueChanged
,最终通过其联动处理实现固定行滑动的效果。
//与自定义的信号连接
connect(mCustomHorizontalScrollBar, &CustomHorizontalScrollBar::scrollbarValueChanged, this,
&myWidget::onHorizontalValueChanged);
/**
* @brief myWidget::onHorizontalValueChanged
* 水平滚动条值改变时的槽函数
* @param bscrollright 是否向右滚动
* @param begincol 开始显示的列索引
* @param changecolsno 修改的列数
*/
void myWidget::onHorizontalValueChanged(bool bscrollright, const int& begincol, const int& changecolsno)
{
// 检查是否正在更新宽度或滚动
if (m_isUpdateWidthOrScroll) {
return;
}
QList<int> visiblecolumnsNo; // 表头显示的列;0表示第一列,与表格保持一致
// QList<TableSetItem> tablelist = tableSetPropertyMap[currentTableName];
// // 遍历所有的表头属性,找出需要显示的列
// for (int i = 0; i < tablelist.size(); ++i) {
// if (tablelist[i].bDisplay) {
// visiblecolumnsNo.append(i);
// }
// }
int candisplaycolumns = visiblecolumnsNo.size(); // 表格表头的属性【显示】的列数
// 如果没有要显示的列或者开始列的索引不合法,直接返回
if (candisplaycolumns <= 0 || begincol < 0) {
return;
}
if (bscrollright) {
// 如果开始列加上要修改的列数超过了可以显示的列数,直接返回
if (candisplaycolumns <= begincol + changecolsno - 1) {
return;
}
// 隐藏指定的列
for (int i = 0; i < changecolsno; ++i) {
ui.tableView->hideColumn(visiblecolumnsNo[begincol + i]);
}
} else {
// 如果开始列小于等于要修改的列数的索引或者开始列的索引不符合要求,直接返回
if (candisplaycolumns <= begincol || begincol < changecolsno - 1) {
return;
}
// 显示指定的列
for (int i = 0; i < changecolsno; ++i) {
ui.tableView->showColumn(visiblecolumnsNo[begincol - i]);
}
}
}
筛选表格功能
- QSortFilterProxyModel是Qt框架中的一个代理模型类,用于对其他模型数据进行排序和过滤。
class CustomProxyModel : public QSortFilterProxyModel
{
public:
CustomProxyModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent),
m_strFilterString(""),
m_FilterColumnStr(""),
m_FilterCol(0)
{
}
void setFilterString(const QString &strFilter)
{
m_strFilterString = strFilter;
invalidateFilter();
}
void setFilterColumn(const QString &colname, const int &colno)
{
m_FilterColumnStr = colname;
m_FilterCol = colno;
invalidateFilter();
}
public:
/// @brief 对源模型进行排序和过滤操作
/// @param source_row 源模型中的行号
/// @param source_parent 源模型中行的父索引。
/// @return
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
QString strValue = index.sibling(index.row(), m_FilterCol).data().toString();
bool falg = strValue.contains(m_strFilterString, Qt::CaseInsensitive);
return falg;
}
private:
QString m_strFilterString; // 输入框文本
QString m_FilterColumnStr; // 下拉框文本
int m_FilterCol; // 下拉框序号
};
- setFilterColumn与setFilterString俩个函数分别是下拉框与文本框调用的函数,当其被调用时,触发函数中的invalidateFilter函数,就是调用filterAcceptsRow进行筛选。