Qt High Copy Excel Table Component - Supports frozen columns, frozen rows, content adaptation and merging cells

Keywords: PHP Qt Excel Qt5

Catalog

Original Link: Qt High Copy Excel Table Component - Supports frozen columns, frozen rows, content adaptation and merging cells

1. Overview

Recently I saw a cool table effect, freezing table columns.People who use excel regularly should have used this feature, and when we want to keep important information fixed to the interface, we have to use the ability to freeze rows or columns.

I've done similar frozen columns before, and Qt's source code has a similar demo.

People who are familiar with Qt should know that there are many Qt use cases installed for us in the installation package of Qt, which are very good and worth learning.Personally, I often learn something about it. I recommend that you also have a look at it when you are free.

Qt comes with a case program called frozencolumn, which demonstrates how to implement the frozen column function. The idea is very good.So, I also used this idea for reference and made several complex controls, all of which used this idea to achieve the effect, and then released one after another

As the title says, this article is not only about freezing columns, but also freezing rows, content adaptive row heights, cell merging, which we all need to do.

2. Effect Display

The following figure shows the effect of freezing rows and columns.The gif chart is a bit long, so you can take a little time to finish it, confirm that you want the effect, and continue looking down.

3. Ideas for implementation

1. Freeze Rows, Freeze Columns

What demofrozencolumn does in Qt

Now that Qt has helped us think about freezing columns, let's take a long time to analyze this demo.

Freeze columns, that is, the first column is always displayed in the window when you drag the horizontal scrollbar.How do I do this?The solution given by Qt is simple. We just need to overlay the two views. The upper view only shows the first column, the lower view is the full display, and then when we drag, we just need to drag the lower view normally.

Is it easy?Qt encapsulates controls with a wide range of interfaces, so we only need to use connect s to bind related changes.

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);

The above code was copied from Qt5.7.1_vs2013.

See, it's that simple.

The following is our own explanation of encapsulating frozen columns and frozen rows, ideas refer to Qt.

Our own highly mimicked Excel table

Now that Qt has done this, why don't we do that?

Let's not say much. Just start. Now that you're freezing columns and rows, you need to add at least two upper views to fix them.

The first version is structured like this, with two more top-level views.

QTableView * m_pFrozenLeftTopView;
QTableView * m_pFrozenRowView;

When the program is finished, a problem is found. Only one of the upper two views used to freeze columns and rows will always work properly, and the area where the two views overlap will always have problems.

Either scroll horizontally or vertically

I thought for a long time, why?There are also many ways to solve this problem, and finally decide to add a view to an area where row and column views overlap, because it is the easiest way to do so.

As for why, you can think for yourself, I will not finish here, the language is not very good to describe, I feel that I do not know the description, _...

Finally, our list of top-level views looks like the one below, and you should be able to see what they are doing from their names.

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

And then there's the constructor, responsible for synchronizing the states between them

//There is no layout so the parent window must be brought with it
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);

//Vertical View Vertical Scrollbar - >Vertical Scrollbar
connect(m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::valueChanged,
    verticalScrollBar(), &QAbstractSlider::setValue);
//Vertical Scrollbar - > Vertical View Vertical Scrollbar
connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
    m_pFrozenColumnView->verticalScrollBar(), &QAbstractSlider::setValue);

//Horizontal Scrollbar - > Horizontal View Horizontal Scrollbar
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);

Maintaining the currently selected items on four views is a more demanding operation. I've set up here that only one cell can be selected for each view, then empty the currently selected items on the other three views when the other view cells are selected.

When a view is clicked, the updateSelections slot is penalized.Then, according to the selected item in the parameter, get the row number and column number of the clicked cell, get the clicked view in turn, and empty the other currently selected items of the view that was not clicked.

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)//Upper Left
    {
        clearSelection();
        m_pFrozenRowView->clearSelection();
        m_pFrozenColumnView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
    }
    else if (row >= m_iRowFrozen
        && column < m_iColumnFrozen)//Lower Left
    {
        clearSelection();
        m_pFrozenRowView->clearSelection();
        m_pFrozenLeftTopView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
    }
    else if (row >= m_iRowFrozen
        && column >= m_iColumnFrozen)//lower right
    {
        m_pFrozenColumnView->clearSelection();
        m_pFrozenRowView->selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
        m_pFrozenLeftTopView->clearSelection();
    }
    else if (row < m_iRowFrozen
        && column >= m_iColumnFrozen)//Top Right View Click
    {
        selectionModel()->select(selected, QItemSelectionModel::Select | QItemSelectionModel::Clear);
        m_pFrozenColumnView->clearSelection();
        m_pFrozenLeftTopView->clearSelection();
    }
}

That's about all the ideas. You can accomplish some other details by yourself. If you have difficulties, you can find me.

2. Row Height Adaptive

Row height adapts when editing cell contents.

Call my encapsulated class directly, ResizeRowHeightEnable interface, and pass true for the parameter.

The code is simple enough to understand at first glance.Is an interface that adapts row heights using the table 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. Ant Line

The Ant Line worker, as I mentioned earlier in another article, needs to rewrite a drawing agent, QStyledItemDelegate, and set it to a table control.

Can be referred to Qt Table Control Ant Line This article.

Below is the header file for this drawing agent, which has several public interfaces, primarily for styling and enabling ant lines.

One of the more important interfaces is the paint virtual function, which draws cells inside.

Important: Our drawing function must not forget to call the original paint function, otherwise other styles of the cell will be lost

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;
};

4. Test Code

1. Add tabular data

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. Set frozen rows and columns

//Test Freeze Columns
tableView->SetFrozen(2, 2);

3. Row Height, Column Width

//Test row height
tableView->SetRowHight(2, 100);

//Test Column Width
tableView->SetColumnWidth(1, 200);

4. Cell Background Color

    //Set cell text color First five columns font to red
    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. Cell Text

//Set cell background color The first five columns of the second row have a red background color
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);

//Set cell text alignment The first five columns of text on the third line are centered
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);

//Set cell text alignment The first five columns of text font on the fourth line
QFont font;
font.setBold(true);//Bold
font.setPixelSize(18);//18 pixels
font.setItalic(true);//Italic
font.setFamily(QString("Microsoft Yahei"));
font.setUnderline(true);//Is there an underline
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. Other related tests

//merge cell
tableView->SetSpan(5, 5, 2, 2);

//Adaptive first row height
tableView->ResizeRowHeight(0);

//Highly adaptive is enabled when large amounts of data are filled and modified
tableView->ResizeRowHeightEnable(true);

//Selection box color and style
tableView->SetFoucsLine(Qt::red, false);
tableView->SetMotionLine(true);

5. Related Articles

  1. Property Browser control QtTreePropertyBrowser compiled into a dynamic library (designer plug-in)

  2. Super Practical Property Browser Control--QtTreePropertyBrowser

  3. Qt Table Control Ant Line

  4. Test program: Qt Implements High Copy excel Table-Executable File (Source Not Open)

Qt comes with a demo program called frozencolumn.

This control relies on this secondary development. Of course, it has been encapsulated into a control by me. Exposure is an excuse. Users don't need to care about the implementation logic of content anymore.This can be achieved by calling only a few interfaces.

Other complex controls will be released in turn:

  1. Qt Implements Table Tree Control - Supports multi-level table headers
  2. Qt implements table control - supports multi-level list headers, multi-level row headers, cell merge, font settings, etc.





If you think the article is good, you might as well reward it. It is not easy to write. Thank you for your support.Your support is my greatest motivation, thank you!!!













Important--Reporting Statement

  1. The articles on this site are all original without special instructions. Copyright is reserved. When you reproduce them, please provide the source of the original by means of links.Also write the original author: Ten to Eight or Twowords

  2. If you want to reproduce it, please reproduce it in its original text. If you modify this text at the time of reproducing, please inform us in advance, and decline the purpose of helping the reproducer by modifying this text at the time of reproducing.

Posted by Smackie on Wed, 31 Jul 2019 11:39:08 -0700