QTableView实现excel冻结窗口功能

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、进阶

后续还想完善的,方向就是表格列宽用户操作的时候,比如单列拉宽,缩窄等,要同步更新滚动条,这个就给大家做思考吧。有时间再补充

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值