QT table control qtableview for QT GUI graphic image development, qtablewidget complex header (multi row header) and detailed methods and examples of freezing and fixing specific rows

Time:2021-7-24

We often use tables in the development process. When using QT framework to develop, we use qtableview or qtablewidget to create tables.

The table is divided into table header and table body:

For simple tables, we can set headers to meet our requirements (of course, we can hide headers), but we can’t do much for customized headers. Especially for complex headers, using the built-in header is unlikely to meet the requirements no matter how it is set. For example, I recently received a project with the following requirements:

Let’s analyze the characteristics of this table:

1. The header is not a simple line, but two lines.

2. Consolidation of header cells.

3. There is a gradient dividing line in the middle of some headers, and the dividing line does not fill the table up and down.

If we can solve the above three problems, we can basically make this form. This header is obviously a more complex header. For the API or CSS provided only for QT, none of the above three problems can be solved.

At this time, a teacher may propose a solution: set itemdelegate for the header, rewrite paintevent in itemdelegate, and draw the header yourself. Because we all know that drawing cells by ourselves should be more flexible and meet more needs. However, we usually redraw the cells, not the header cells. So I went to search QT’s help document. I was surprised to find that there was an API for setting itemdelete. I felt that there was a play, so I created an itemdelegate class and set the header as follows

tableWidget->horizontalHorizon()->setItemDelegate(new ItemDelegate());

But the result was disappointing and had no effect. So I turned back to find QT’s introduction to this part and finally found the reason:

The result is obvious. For headerview, itemdelegate cannot be used for redrawing.

Then we have to find another way. After analysis, we have just put forward two schemes:

Solution

  describe advantage shortcoming
Scheme I
  • Hide header
  • The first two lines should be used as headers
  • The content line starts with the third line
  • Set itemdelegate for the table and redraw the headers of the first two rows
A qtablewidget is easier to implement.
  • When the scroll bar appears, the header will move along with the content table, which is not in line with the public habit.
  • The entire original sequence of the content table is changed, and the number of all rows needs to be 2 larger than the original. Rewriting all APIs is not difficult and complex.
Scheme II
  • Use a qtablewidget named M_ Frozontablewgt as the header.
  • Use another qtablewidget as a table for content display.
  • m_ Frozontablewgt hide the header, hide the scroll bar, display only 2 rows of content table, display above the content table, occupy only the header height of the content table, and set itemdelegate to redraw.
  • Content table, display header, height set to M_ The height of the first two rows of frozontablewgt.
The final effect is better and the experience is better.
  • It is troublesome to operate two qtablewidgets.
  • The qtablewidget of the header needs to be locked (fixed).
  • Linkage settings need to be made for two qtablewidgets

To sum up:

The first scheme is relatively simple, but the final experience effect is not very good.

The second scheme is more complex to implement, but the final experience effect is better.

Based on the attitude of achieving customers and self growth, we finally chose the second solution.

The first thing we need to do is to create a class inherited from qtablewidget, named tdmsummarytablewgt.


class TDMSummaryTableWgt : public QTableWidget

Then, in the tdmsummarytablewgt class, you need to declare another qtablewidget for the header, named M_ frozenTableWgt;

private:
  QTableWidget *m_ frozenTableWgt;//  Use tablewidget as header and freeze

This m_ Frozentablewgt is used as the header, and the position is fixed. It does not move with the scroll bar.

At this time, we only need to solve two problems to solve the header:

1. The position of the meter is locked (fixed and locked).

2. Redraw the header.

For the first question, the position of the header is fixed. What aspects should we consider to solve it?

1. From the interface initialization, we should make the header M_ Frozentablewgt has the following features: do not display header, do not display scroll bar, set rowcount to 2 rows and hide all elements after 2 rows, set window level before tdmsummarytablewgt, merge cells, etc.

What we should pay special attention to here is that M_ The number of columns set by frozentablewgt and tdmsummarytablewgt should be exactly the same, and the size and extension scheme of each column should also be exactly the same.

void TDMSummaryTableWgt::initFrozenFrame()
{
  m_frozenTableWgt = new QTableWidget(this);
 
  m_ frozenTableWgt->horizontalHeader()->setVisible(false);// Header not visible
  m_ frozenTableWgt->verticalHeader()->setVisible(false);// Header not visible
  m_ frozenTableWgt->setShowGrid(false);// Gridlines are not visible
  m_ frozenTableWgt->setEditTriggers(QAbstractItemView::NoEditTriggers);// Set cell not editable
  m_ frozenTableWgt->horizontalHeader()->setStretchLastSection(true);// Last cell extension
  m_ frozenTableWgt->setFocusPolicy(Qt::NoFocus);// Solve the problem of selected virtual box
  m_ frozenTableWgt->setFrameShape(QFrame::NoFrame);// Remove border embarrassment
  m_ frozenTableWgt->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);// Hide scroll bar
  m_frozenTableWgt->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//
  m_frozenTableWgt->setHorizontalScrollMode(ScrollPerPixel);
 
  m_ frozenTableWgt->setItemDelegate(new ItemDelegate(0));// Set the painting agent (mainly draw the header in the agent)
 
  viewport()->stackUnder(m_ frozenTableWgt);// Set window hierarchy
 
  m_ frozenTableWgt->setColumnCount(10);// Header10 column
  m_ frozenTableWgt->setRowCount(2);// Header2 line
 
  m_ frozenTableWgt->setRowHeight(0, 42);// The first row sets the height 42px
  m_ frozenTableWgt->setRowHeight(1, 42);// The second row sets the height 42px
 
  for (int row = 2;  row < m_ frozenTableWgt->rowCount(); ++ Row) // hide the row after 2 rows
     m_frozenTableWgt->setRowHidden(row, true);
 
  //===================Set header content=================//
  //Merge cells
  m_ frozenTableWgt->setSpan(0, 0, 2, 1);// Teacher ID
  m_ frozenTableWgt->setSpan(0, 1, 2, 1);// Teacher name
  m_ frozenTableWgt->setSpan(0, 2, 2, 1);// Teacher name
  m_ frozenTableWgt->setSpan(0, 3, 1, 4);// Latest date (August 20)
  m_ frozenTableWgt->setSpan(0, 7, 1, 2);// Previous day (August 19)
  m_ frozenTableWgt->setSpan(0, 9, 2, 1);// operation
 
  m_ Frozentablewgt - > setitem (0, 0, new qtablewidgettitem ("teacher ID");
  m_ Frozentablewgt - > setitem (0, 1, new qtablewidgettitem ("teacher name");
  m_ Frozentablewgt - > setitem (0, 2, new qtablewidgettitem ("teacher name");
  m_ Frozentablewgt - > setitem (0, 3, new qtablewidgettitem ("August 20"));
  m_ Frozentablewgt - > setitem (0, 7, new qtablewidgettitem ("August 19"));
  m_ Frozentablewgt - > setitem (0, 9, new qtablewidgettitem ("operation");
  m_ Frozentablewgt - > setitem (1, 3, new qtablewidgettitem ("renewal rate");
  m_ Frozentablewgt - > setitem (1, 4, new qtablewidget item);
  m_ Frozentablewgt - > setitem (1, 5, new qtablewidgettitem ("continued growth");
  m_ Frozentablewgt - > setitem (1, 6, new qtablewidgettitem ("continued growth rate");
  m_ Frozentablewgt - > setitem (1, 7, new qtablewidgetitem ("continued growth rate");
  m_ Frozentablewgt - > setitem (1, 8, new qtablewidget item);
 
  //Connect the signal slot. For scroll bar linkage
  connect(m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::valueChanged,
      verticalScrollBar(), &QAbstractSlider::setValue);
  connect(verticalScrollBar(), &QAbstractSlider::valueChanged,
      m_frozenTableWgt->verticalScrollBar(), &QAbstractSlider::setValue);
 
  updateFrozenTableGeometry();// Update location
  m_frozenTableWgt->show();
}

2. In addition to the above considerations, we need to consider M_ The linkage between frozentablewgt and tdmsummarytablewgt mainly includes table size change, scroll bar movement, interface translation and so on.

Let’s first write a method to determine M_ Location of frozentablewgt and tdmsumarytablewgt.


void TDMSummaryTableWgt::updateFrozenTableGeometry()
{
  m_frozenTableWgt->setGeometry(frameWidth(),
                 frameWidth(),
                 viewport()->width(),
                 horizontalHeader()->height());
 
}

We need to rewrite the three functions mentioned above to solve the problem. In each method, we need to re execute updatefrozentablegeometry();

protected:
  /**
   *@ brief resizeevent overloads the virtual function resizeevent and updates M_ Location of frozentablewgt
   * @param event
   */
  virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
 
  /**
   *@ brief movecursor overloaded virtual function mouse movement event
   * @param cursorAction
   * @param modifiers
   * @return
   */
  virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) Q_DECL_OVERRIDE;
 
  /**
   *@ brief scrollto tablewidget move event
   * @param index
   * @param hint
   */
  void scrollTo (const QModelIndex & index, ScrollHint hint = EnsureVisible) Q_DECL_OVERRIDE;

For the above three virtual functions, we need to pay special attention to the movecursor method. In this method, we should focus on the scenario of moving the mouse up: only when the mouse moves up and tdmsummarytablewgt has not been displayed in the first row, and the vertex of the visual area should be less than m_ Only the first row of frozentablewgt can continue to move upward:


QModelIndex TDMSummaryTableWgt::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
  QModelIndex current = QTableView::moveCursor(cursorAction, modifiers);
 
  if (cursorAction == MoveUp && current.row() > 0
      && visualRect(current).topLeft().y() < m_frozenTableWgt->rowHeight(1) ){
     const int newValue = verticalScrollBar()->value() + visualRect(current).topLeft().y()
                - m_frozenTableWgt->rowHeight(0) - m_frozenTableWgt->rowHeight(1);
     verticalScrollBar()->setValue(newValue);
  }
  return current;
}

After finishing the above several films, the first problem has been basically solved, that is, M_ Frozentablewgt’s fixed row (frozen) function.

To complete M_ The second problem to be solved is the style redrawing of frozentable wgtde.

To solve this problem, we need to create a new proxy class that inherits from qstyleditemdelegate, which is called itemdelegate. And override the paint method and draw m in the paint method_ frozenTableWgt;

m_ frozenTableWgt->setItemDelegate(new ItemDelegate(0));// Set the painting agent (mainly draw the header in the agent)

 


class ItemDelegate : public QStyledItemDelegate
{
  Q_OBJECT
public:
  ItemDelegate(int type, QObject *parent=0);
 
  void paint(QPainter *painter,
        const QStyleOptionViewItem &option, const QModelIndex &index) const;
 
private:
  
};

In the paint method, the background is drawn according to the background of each cell

int rowIndex = index.row();// Line number
  int colIndex = index.column();// Column number
  If (rowindex = = 0 | rowindex = = 1) // the first two lines are used as headers
  {
    //Background
    QColor color;
 
    If (rowindex = = 0 & & (COLINDEX = = 0 | // teacher ID
               COLINDEX = = 1 | // teacher name
               COLINDEX = = 2 | // course type
               COLINDEX = = 9)) // operation
    {
      color.setRgb(231, 238, 251);
    }
    Else if ((rowindex = = 0 & & COLINDEX = = 3) | // August 20
         (rowindex = = 1 & & (COLINDEX = = 3 | // renewal rate
                  COLINDEX = = 4 | // renewal rate of new students
                  COLINDEX = = 5 | // continued growth
                  COLINDEX = = 6)) // continued growth rate
    {
      color.setRgb(214, 228, 253);
    }
    Else if ((rowindex = = 0 & & COLINDEX = = 7) | // August 19
         (rowindex = = 1 & & (COLINDEX = = 7 | // renewal rate
                  COLINDEX = = 8)) // renewal rate of new students
    {
      color.setRgb(203, 221, 255);
    }
 
    //Draw background
    painter->setPen(color);
    painter->setBrush(QBrush(color));
    painter->drawRect(option.rect);

According to the requirements of each cell, whether to draw the gradient separation line on the right.

//Right side spacer
    if ((rowIndex == 0 && (colIndex == 0 || colIndex == 1) )) {
      int startX = option.rect.right();
      int startY = option.rect.y() + (option.rect.height() - 40) / 2;
      int endX = startX;
      int endY = startY + 40;
      QLinearGradient linearGradient(startX, startY, endX, endY);
      linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
      linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
      linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
      painter->setBrush(linearGradient);
      painter->drawRect(option.rect.right()- 2, startY, 2, 40);
 
    }
    else if (rowIndex == 1 && (colIndex == 3 ||
                  colIndex == 4 ||
                  colIndex == 5 ||
                  colIndex == 7 )) {
 
      int startX = option.rect.right();
      int startY = option.rect.y() + (option.rect.height() - 28) / 2;
      int endX = startX;
      int endY = startY + 28;
      QLinearGradient linearGradient(startX, startY, endX, endY);
      linearGradient.setColorAt(0, QColor(164, 188, 240, 0));
      linearGradient.setColorAt(0.5, QColor(164, 188, 240, 255));
      linearGradient.setColorAt(1, QColor(164, 188, 240, 0));
      painter->setBrush(linearGradient);
      painter->drawRect(option.rect.right()- 2, startY, 2, 28);    
    }

Finally, draw the font of each cell

//Typeface
    painter->setPen(QColor(51, 51, 51));
    QTextOption op;
    op.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
 
    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPixelSize(14);
    font.setBold(true);
    painter->setFont(font);
    painter->drawText(option.rect, index.data(Qt::DisplayRole).toString(), op);

This solves the problem in the header.

The complete example source code of this article can be downloaded here

This article mainly explains the detailed methods and examples of QT table control qtableview, qtablewidget complex header (multi row header) and freezing and fixing specific rows. For more information about QT GUI graphics and image development, please see the relevant links below