1、题外话
QT自带的示例里面有一个例子名叫:freeze xxx;
这个例子的做法,就是让两个table使用同一份model数据,QTableView有viewport(),把另外一个table塞入viewport(),然后在设置一下尺寸,冻结几列就显示几列,相当于上面是小QTableView,下面是完整的QTableView,然后拖动滚动条就是滚动,下面的就被上面挡住,相当于冻结了。
但是这个做法有一个很大的缺陷,就是关于同步的问题,比如你点击前面冻结的几列,相当于点击的是小QTableView,你得同步到下面的QTableView,还有其他许多同步问题,最后被我否决了,自己想了另外一个方法。闲话不多说了,下面进入今天的主题
2、主题:
整体思想:就是自定义一个滚动条,根据表格的宽度,计算滚动条的size,通过拖动滚动条,对冻结列之后的列进行隐藏,实现冻结的效果。比如整个表格有10列,显示页面可以显示6列,那么滚动条就可以拖动10-6=4列,如果需要冻结前面3列,那么滚动条从开头往后拖1格,就隐藏第4列,如果向后拖3格,就隐藏3列,反过来向左边拖动,就逐步显示被隐藏的列,这样可以达到和excel冻结窗口一样的效果。等会上效果图。
当然仅仅是这样是不够的,因为表格尺寸会变化,所以尺寸变化的时候,需要重新设置滚动条的size和当前滚动条的位置。这是一个难点。接下来先看自定义的滚动条。
2.1CustomHorizontalScrollBar.h
#include <QTableView>
#include <QScrol1Bar>
class CustomHorizontalScrol1Bar :public QScro11Bar
{
Q_OBJECT
public:
CustomHorizontalScro11Bar(QWidget *parent= nul1ptr);
CustomHorizonta1Scro11Bar();
void initCustomScrol1bar(const int &freezecols, int min,int max); virtual QSize sizeHint() const;
int getFreezeColno() { return m_freezeCols;}
private slots: I
void onValueChanged(int value);
signals:
void scrol1barValueChanged(bool bscrollright,int begincol,int changecolno);
private:
int m_freezeCols;
int m_oldposition;
};
2.2 CustomHorizontalScrollBar.cpp
//自定义滚动条
CustomHorizontalScro11Bar::CustomHorizonta1Scro11Bar(QWidget *parent/*= nullptr*/):QScro11Bar(parent)
{
this->setOrientation(Qt::Horizontal);
this->setSingleStep(10);
connect(this, SIGNAL (valueChanged(int)),this,SLOT(onValueChanged(int)));
}
CustomHorizonta1Scro11Bar::~CustomHorizonta1Scro11Bar()
{
}
void CustomHorizonta1Scro11Bar::initCustomScrol1bar( const int &freezecols,int min, int max)
{
m_freezeCols = freezecols;
m_oldposition=0;
this->setSliderPosition(0);
this->setRange(min,max);
}
QSize CustomHorizonta1Scro11Bar::sizeHint() const
{
return QSize(15,15);
}
void CustomHorizonta1Scro11Bar::onValueChanged( int value)
{
//左滑
if (m_oldposition > value){
emit scrol1barValueChanged(false, m_freezeCols + m_oldposition - 1,m_oldposition - value);
} else if (m_oldposition < value) {
//右滑
emit scrol1barValueChanged(true,m_freezeCols + m_oldposition,value - m_oldposition);
}
m_oldposition = value;
}
2.3自定义滚动条的使用及冻结窗口的实现
假设放QTableView和滚动条的类名是TestWidget,以下代码是TestWidget类中的
void TestWidget::initui()
{
QTableView *m_ptableView = new QTableView(this);
m_ptableView->setModel(xxxx);
CustomHorizontalScrollBar *m_horizontalScrollbar = new CustomHorizontalScrollBar;
//创建自定义滚动条
//至于位置,可以放在QTableView的viewport()或者直接放在QTableView下方。
initHorizontalScrol1bar(3); //冻结3列
//连接信号,滚动条滑动的时候,去做隐藏显示处理。
QObject::connect(m_horizontalScrollbar,SIGNAL(scrollbarValueChanged(bool,int,int)),this,SLOT(onHorizontalValueChanged(bool,int,int)));
}
void TestWidget::onHorizontalValueChanged( bool bscrollright,int begincol,int changecolsn)
{
//往右边滑动隐藏
if (bscrol1right){
for (int i=0;i < changecolsno;++i){
ui.tableView->hideColumm(begincol+i);
}
} else {
//往左边滑动显示
for (int i =0;i< changecolsno;++i){
ui.tableView->showColumm(begincol - i);
}
}
}
//初始化滚动条,包括设置冻结列数等
void TestWidget::initHorizontalScrol1bar(const int &freezecols)
{
if (m_horizontalScrollbar == nul1ptr) {
return;
}
if(m_ptableView->mode1()== nul1ptr) {
m_horizontalScrol1bar->initCustomScrol1bar(freezecols, 0,0);
m_horizontalScrol1bar->setVisible(false);
return;
}
int layoutwidth = m_ptableView->viewport()->width();
int totalcols = m_ptableView->model()->columnCount();
int tableminwidth=0;
for (int col = 0;col <totalcols-1;++col){
tableminwidth += m_ptableView->colummWidth(co1);
}
tableminwidth += TableCol_Width;
if (tableminwidth <= 1ayoutwidth || freezecols == totalcols) {
m_horizontalScrollbar->initCustomScrol1bar(freezeco1s,0,0);
m_horizontalScrollbar->setVisible(false);
return;
}
//假设初始设定QTableView的列宽为110;计算初始滚动条格数
int max = qFloor((tableminwidth - layoutwidth)/110.0)+1;
m_horizontalScrollbar->initCustomScrol1bar(freezeco1s,0,max);
m_horizontalScrollbar->setVisible(true);
}
//widget尺寸变化的时候,调整对应的滚动条显示,比如宽了,那么滚动条滚动的格数就会减少,反之增多等
void TestWidget::resizeEvent(QResizeEvent *e)
{
QWidget::resizeEvent(e);
setScrol1barStatus();
}
//设置在视图尺寸变化的时候,设置滚动条状态
void TestWidget::setScrol1barStatus()
{
if (m_horizontalScrollbar ==nul1ptr | m_ptableView->model() == nul1ptr) {
return;
}
int freezecol = m_horizontalScrol1bar->getFreezeColno();
if (freezecol == m_ptableView->mode1()->colummCount()) {
m_horizontalScrol1bar->setVisible(false);
return;
}
int oldmax = m_horizontalScrol1bar->maximum();
int newmax = calculateScrol1barMax(freezeco1); //计算滚动条最大滚动格数
int currentpos = m_horizontalScrol1bar->value();
if (newmax == oldmax) {
return;
}
if (newmax == 0) {
m_horizontalScrol1bar->setValue(0);
m_horizontalScrol1bar->setRange(0,0);
m_horizontalScrol1bar->setVisible(false);
}else {
m_horizontalScrol1bar->setRange(0,newmax);
if (newmax < oldmax && currentpos > newmax) {
m_horizontalScrol1bar->setValue(newmax);
}
m_horizontalScrol1bar->setVisible(true);
}
int TestWidget::calculateScro11barMax(const int &freezecol)
{
if (m_ptableView->model() == nullptr || m_ptableView->viewport()==nu11ptr){
return 0;
}
int layoutwidth = ui.tableView->viewport()->width();
int totalcols = m_ptableView->model()->colummCount();
int max=0;
int visiblecol =0;
int tableminwidth = calculateTableMinWidth(layoutwidth, totalcols, max,visiblecol);
if(max ==0) {
//有隐藏列
if (visiblecol < totalcols -1){
if (layoutwidth < tableminwidth) {
max = totalcols - visiblecol;
} else {
//隐藏了多少列
for(int j = totalcols - 1 - visiblecol; j> 0;--j) {
ui.tableView->showColumm(freezecol -1 + j);
int tmpmax = 0;
int tmpvisiblecol = 0;
int tmptableminwidth = calculateTableMinWidth(layoutwidth, totalcols, tmpmax, tmpvisiblecol);
if (1ayoutwidth < tmptableminwidth){
max = totalcols - tmpvisiblecol;
break;
}
}
}
}else {
//没有隐藏列,全部显示了
if(1ayoutwidth < tableminwidth){
max = totalcolg - visiblecol;
}
}
return max;
}
int TestWidget::calculateTablelinWidth(const int &layoutwidth,const int &totalcols, int &max, int &visiblecol)
{
int tableminwidth=0;
max=0;
visibleco1=0;
for (int col=0;col <totalcols-1;++co1){
if (m_ptableView->colummWidth(co1)==0){
continue;
}
tableminwidth += ui.tableView->colummWidth(co1);
if (layoutwidth< tableminwidth){
max = totalcols - visiblecol;//总列数-当前能够显示的列数,就是流动条break;
}
++visiblecol;//计算当前在可视范围内,表格能够显示的列数
}
tableminwidth += 110;
return tableminwidth;
}
3、效果
以上实现有问题可以发私信或评论,实现上述之后的效果如下
https://live.youkuaiyun.com/v/224759
4、进阶
后续还想完善的,方向就是表格列宽用户操作的时候,比如单列拉宽,缩窄等,要同步更新滚动条,这个就给大家做思考吧。有时间再补充