Qt imitation Excel spreadsheet component - Freeze support columns, freeze rows, content adaptation and merging cells

Original link: Qt high imitation Excel spreadsheet component - Support frozen columns, freeze rows, content adaptation and merging cells

I. Overview

Recently I saw a relatively cool effect table, table columns freeze function. Often use excel people should have used this feature, when we want to change some important information has been fixed at the time of the interface, you have to use frozen rows or columns of the freeze function.

I also had done a similar function to freeze columns, and Qt's source code has a similar demo.

Qt more familiar to people who should know, Qt installation package can install a lot of Qt for our use cases, are very good, it is worth learning. Personally, I also often go to learn something of them, suggest that you have nothing more than a look.

Qt comes with a program instance, the name of the project to do frozencolumn, this feature demonstrates how to achieve the freeze function column, the idea is very good. So, I also learn from the idea to do several complex controls, we are using this idea to achieve results, follow the fed

Like the title said, this article we not only meet the freeze column feature, in addition, the freezing line, high content adaptive row, cell merging these we have to do.

Second, the results show

The picture below shows the freeze rows, columns freeze effect. gif Figure a bit long, you can spend a little time to read, to confirm that they want results, then read on.

Third, the realization of ideas

1. Freeze rows, columns frozen

Qt in the demo frozencolumnis how dry

Since Qt has helped us want the function to freeze columns, for so long the first to analyze this demo it.

Implement column-freeze, that is to say when dragging the horizontal scroll bar, the first column is always displayed on the window. How to achieve this effect? Qt solution to the very simple, we just need to view two added together, the upper view displays only the first column, the lower view is a full display, then drag the time we just need to drag the view underlying normal It can be.

是不是很简单呢。Qt封装的控件,接口都很齐全,我们只需要使用connect把相关的变化绑定起来即可。

setModel(model);
frozenTableView = new QTableView(this);

init();

//connect the headers and scrollbars of both tableviews together
connect(horizontalHeader(),&QHeaderView::sectionResized, this,
      &FreezeTableWidget::updateSectionWidth);
connect(verticalHeader(),&QHeaderView::sectionResized, this,
      &FreezeTableWidget::updateSectionHeight);

connect(frozenTableView->verticalScrollBar(), &QAbstractSlider::valueChanged,
      verticalScrollBar(), &QAbstractSlider::setValue);
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
      frozenTableView->verticalScrollBar(), &QAbstractSlider::setValue);

上述代码是从Qt5.7.1_vs2013版本中复制出来的。

看到了吧,就是这么简单。

下面就是我们自己封装冻结列、冻结行的讲解,思路参考Qt的。

我们自己的高仿Excel表格

既然Qt都这么干了,我们还有什么理由不这么干呢?

话不多说,直接开干,既然要冻结列和行,那我们至少还需要在添加2个上层视图,以固定列和行。

第一个版本的结构就是这样的,多了2个上层视图。

QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;

等程序做好后,发现一个问题,上层2个用来冻结列和冻结行的视图,永远只有一个是工作正常的,2个上层视图叠加的区域总是出现问题。

要么水平滚动正常,要么垂直滚动正常

思考了很久,为什么呢?也想了很多办法去解决这个问题,最后还是决定,在添加一个视图到行和列视图重叠的区域,因为这么做最简单。

至于为什么,大家可以自己想想,这里我就不做结束了,语言不是特别好描述,感觉自己也描述不清楚,囧。。。。

最后呢,我们的上层视图列表会像下面这样,从名字应该也可以看到他们分别是干什么的。

QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;
QTableView * m_pFrozenColumnView;

然后就是构造函数了,负责同步他们之间的状态

//没有布局 因此必须把父窗口带上
m_pFrozenLeftTopView = new QTableView(this);
m_pFrozenColumnView = new QTableView(this);
m_pFrozenRowView = new QTableView(this);

init();

connect(horizontalHeader(), &QHeaderView::sectionResized, this,
    &FreezeTableView::updateSectionWidth);
connect(verticalHeader(), &QHeaderView::sectionResized, this,
    &FreezeTableView::updateSectionHeight);

//垂直视图垂直滚动条  -> 垂直滚动条
connect(m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::valueChanged,
    verticalScrollBar(), &QAbstractSlider::setValue);
//垂直滚动条  -> 垂直视图垂直滚动条
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
    m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::setValue);

//水平滚动条  -> 水平视图水平滚动条
connect(horizontalScrollBar(), &QAbstractSlider::valueChanged,
    m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::setValue);
connect(m_pFrozenRowView->horizontalScrollBar(), &QAbstractSlider::valueChanged,
    horizontalScrollBar(), &QAbstractSlider::setValue);

connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenColumnView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenRowView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);
connect(m_pFrozenLeftTopView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FreezeTableView::updateSelections);

4个视图上的当前选中项维护是一个比较费劲的操作,我这里设置了每个视图都只能选中一个单元格,然后其他视图单元格被选中的时候,清空其他三个视图上的当前选中项。

当某一个视图被点击时,updateSelections槽就会被处罚。然后根据参数中被选中项,获取点击的单元格行号和列号,依次拿到被点击了的视图,接着清空其他没有被点击的视图当前选中项。

void FreezeTableView::updateSelections(const QItemSelection & selected, const QItemSelection &)
{
    if (selected.isEmpty())
    {
        return;
    }
    QModelIndex index = selected.indexes().at(0);
    int row = index.row();
    int column = index.column();
    if (row < m_iRowFrozen
        && column < m_iColumnFrozen)//左上
    {
        clearSelection();
        m_pFrozenRowView->clearSelection();
        m_pFrozenColumnView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
    }
    else if (row >= m_iRowFrozen
        && column < m_iColumnFrozen)//左下
    {
        clearSelection();
        m_pFrozenRowView->clearSelection();
        m_pFrozenLeftTopView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
    }
    else if (row >= m_iRowFrozen
        && column >= m_iColumnFrozen)//右下
    {
        m_pFrozenColumnView->clearSelection();
        m_pFrozenRowView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
        m_pFrozenLeftTopView->clearSelection();
    }
    else if (row < m_iRowFrozen
        && column >= m_iColumnFrozen)//右上视图点击
    {
        selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
        m_pFrozenColumnView->clearSelection();
        m_pFrozenLeftTopView->clearSelection();
    }
}

大概思路就讲这么多了,其他一些细节的实现大家可以自行完成,有困难可以找我。

2、行高自适应

编辑单元格内容时,行高自适应。

直接调用我封装好的类,ResizeRowHeightEnable接口,参数传递true即可。

代码比较简单,一看应该都可以明白。是用过表格resizeRowToContents这个接口自适应行高。

void ExcTableWidget::ResizeRowHeightEnable(bool enable)
{
    if (enable)
    {
        connect(m_pModel, &QStandardItemModel::itemChanged, m_pVew, [this](QStandardItem * item){
            m_pVew->resizeRowToContents(item->row());
        }, Qt::UniqueConnection);
    }
    else
    {
        m_pModel->disconnect(m_pVew);
    }
}

3、蚂蚁线

蚂蚁线这个工我之前在另一篇文章中有讲过,需要重写一个绘图代理QStyledItemDelegate,然后设置给表格控件。

可以参考Qt之表格控件蚂蚁线这篇文章。

下面是这个绘图代理的头文件,其中有几个public接口,主要是用于设置蚂蚁线的样式,和是否启用蚂蚁线的。

其中比较重要的接口是paint虚函数,这个函数里边对单元格进行了绘制。

很重要:我们的绘制函数一定不要忘记调用原来的paint函数,否则单元格的其他样式都会丢失

class SelectStyle : public QStyledItemDelegate
{
    Q_OBJECT

public:
    SelectStyle(QObject * parent = nullptr) : QStyledItemDelegate(parent), m_bAntLine(false), m_iOffset(0), m_color(0, 132, 255){}
    ~SelectStyle(){}

public:
    void GoStepAntLine(bool);

    void SetLineColor(const QColor & color);
    void SetLineType(bool dash);

protected:
    virtual void paint(QPainter * painter
        , const QStyleOptionViewItem & option
        , const QModelIndex & index) const override;

private:
    void DrawBorderRect(QPainter * painter, const QRect & rect, bool firstColumn) const;
    void DrawDashRect(QPainter * painter, const QRect & rect, bool firstColumn) const;

protected:
    bool m_bAntLine;
    bool m_bDashState;
    int m_iOffset;
    QColor m_color;
};

四、测试代码

1、添加表格数据

QFile file(":/grades.txt");
if (file.open(QFile::ReadOnly)) {
    QString line = file.readLine(200);
    QStringList list = line.simplified().split(',');
    tableView->SetHeaderLabels(list);

    QStringList lines; 
    while (file.canReadLine()) {
        line = file.readLine(200);
        lines.append(line);
    }
    file.close();

    int i = 1;
    int row = 0;
    while (i-- > 0)
    {
        for each (const QString & line in lines)
        {
            if (!line.startsWith('#') && line.contains(',')) {
                list = line.simplified().split(',');
                for (int col = 0; col < list.length(); ++col){
                    tableView->SetItemData(row, col, list.at(col));
                }
                ++row;
            }
        }
    }
}

2、设置冻结行、列

//测试冻结列
tableView->SetFrozen(2, 2);

3、行高、列宽

//测试行高
tableView->SetRowHight(2, 100);

//测试列宽
tableView->SetColumnWidth(1, 200);

4、单元格背景色

    //设置单元格文本颜色   第一行前五列字体为红色
    tableView->SetItemForegroundColor(0, 0, Qt::red);
    tableView->SetItemForegroundColor(0, 1, Qt::red);
    tableView->SetItemForegroundColor(0, 2, Qt::red);
    tableView->SetItemForegroundColor(0, 3, Qt::red);
    tableView->SetItemForegroundColor(0, 4, Qt::red);

5、单元格文字

//设置单元格背景色  第二行前五列背景色为红色
tableView->SetItemBackgroundColor(1, 0, Qt::red);
tableView->SetItemBackgroundColor(1, 1, Qt::red);
tableView->SetItemBackgroundColor(1, 2, Qt::red);
tableView->SetItemBackgroundColor(1, 3, Qt::red);
tableView->SetItemBackgroundColor(1, 4, Qt::red);

//设置单元格文本对齐方式 第三行前五列文字居中
tableView->SetTextAlignment(2, 0, Qt::AlignCenter);
tableView->SetTextAlignment(2, 1, Qt::AlignCenter);
tableView->SetTextAlignment(2, 2, Qt::AlignCenter);
tableView->SetTextAlignment(2, 3, Qt::AlignCenter);
tableView->SetTextAlignment(2, 4, Qt::AlignCenter);

//设置单元格文本对齐方式 第四行前五列文字字体
QFont font;
font.setBold(true);//加粗
font.setPixelSize(18);//18像素
font.setItalic(true);//斜体
font.setFamily(QString("Microsoft Yahei"));
font.setUnderline(true);//是否有下划线
font.setStrikeOut(true);
tableView->SetItemFont(3, 0, font);
tableView->SetItemFont(3, 1, font);
tableView->SetItemFont(3, 2, font);
tableView->SetItemFont(3, 3, font);
tableView->SetItemFont(3, 4, font);

6、其他相关测试

//合并单元格
tableView->SetSpan(5, 5, 2, 2);

//自适应第一行高度
tableView->ResizeRowHeight(0);

//高度自适应  尽量放在大量数据填充完 修改数据阶段启用
tableView->ResizeRowHeightEnable(true);

//选择框颜色和样式
tableView->SetFoucsLine(Qt::red, false);
tableView->SetMotionLine(true);

五、相关文章

  1. 属性浏览器控件QtTreePropertyBrowser编译成动态库(设计师插件)

  2. 超级实用的属性浏览器控件--QtTreePropertyBrowser

  3. Qt之表格控件蚂蚁线

  4. 测试程序:Qt实现高仿excel表格-可执行文件(源码不开放)

Qt自带的demo中有一个demo程序,源码工程叫做frozencolumn。

这个控件就是依赖于这个二次开发的,当然了已经被我封装成了一个控件,对外暴露的都是借口,用户不在需要关心内容的实现逻辑了。只需要调用几个接口,就可以达到这样的效果。

后续还会依次放出其他复杂控件:

  1. Qt实现表格树控件-支持多级表头
  2. Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等





如果您觉得文章不错,不妨给个 打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!














很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


Guess you like

Origin www.cnblogs.com/swarmbees/p/11155074.html