Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How can I replce Qpainter::drawText with a more effecient way to draw text in a table cell



  • UPDATE:
    After tested with a simpler QWidget subclass, I realized may be it's not a drawText problem.
    I've removed the colorful gifs, but keep the last two gray or white gifs to better demonstrate the problem.

    the original post:
    I try to implement a simple table drawing widget by overriding the QWidget:PaintEvent method.
    Now I find that, before the text and color shows up, the window is about 1 second visible blank.
    This is the gif that shows the blank.
    Using VS2019 Perfomance Profiling Tools, We can see drawText occupies about 30% ~ 50% cpu work. And if I removed all the text
    drawing, the visible blank disappears.
    cb8f3cc3-f9e4-4359-aa38-6d11d5b32433-图片.png

    Here is my code.
    main.cpp

    #include <QtWidgets/QApplication>
    #include "QPlainStyleTable.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        auto _m_pTable = new QPlainStyleTable();
        _m_pTable->SetHeaderColor(Qt::red, Qt::green);
        _m_pTable->SetContentColor(Qt::gray, Qt::yellow, Qt::blue);
        _m_pTable->SetBorderColor(Qt::white);
        _m_pTable->SetFont(QFont("微软雅黑"), QFont("微软雅黑"));
    
        _m_pTable->SetDimension(8, 6);
    
        _m_pTable->SetTitle({ "姓名", "年龄", "性别", "学历", "籍贯" });
        _m_pTable->AddData({ "小红", "28", "女", "大学", "山东" });
        _m_pTable->AddData({ "姓名", "年龄", "性别", "学历", "籍贯" });
        _m_pTable->AddData({ "姓名", "年龄", "性别", "学历", "籍贯" });
        _m_pTable->AddData({ "姓名", "年龄", "性别", "学历", "籍贯" });
        _m_pTable->show();
        return a.exec();
    }
    
    

    QPlainStyleTable.h

    #pragma once
    
    #include <vector>
    #include <string>
    #include <functional>
    #include <map>
    #include <memory>
    
    #include <QWidget>
    #include <QPixmap>
    #include <QColor>
    #include <QFont>
    #include <QMenu>
    
    //返回true即不再可编辑
    using CellEditCallBack = std::function<void(int mouseX, int mouseY)>;
    
    class QPlainStyleTable : public QWidget
    {
        Q_OBJECT
    
    public:
        QPlainStyleTable(QWidget *parent = nullptr);
        ~QPlainStyleTable();
    
        void SetHeaderColor(const QColor& bkColor, const QColor& txtColor);
        void SetContentColor(const QColor& evenBkColor, const QColor& oddBkColor, const QColor& txtColor);
        void SetBorderColor(const QColor& color);
        void SetFont(const QFont& headerFont, const QFont& contentFont);
        void SetDimension(int row, int col);
    
        void SetTitle(const std::vector<std::string>& title);
        void AddData(const std::vector<std::string>& row);
        void ClearContent();
    
        void SetCellEditCallBack(int row, int col, CellEditCallBack callback);
        void SetUnEditable(int row, int col);
        void UpdateContent(int row, int col, const std::string& content);
    protected:
        virtual void paintEvent(QPaintEvent *event) override;
        virtual bool eventFilter(QObject *watched, QEvent *event) override;
    private:
        //缓存背景
        QPixmap bkCache;
        void DrawBk();
        bool BkSizeIsSame();
    
        //行列
        int row;
        int col;
    
        //单元格大小, 在画背景时计算出
        int headerH;
        int tailW;
        int cellH;
        int cellW;
    
        QColor headerBkColor;
        QColor headerTxtColor;
    
        QColor contentTxtColor;
        QColor evenBkColor;
        QColor oddBkColor;
        QColor borderColor;
    
        QFont headerFont;
        QFont contentFont;
    
        // headerText
        std::vector<std::string> title;
    
        // contentText
        std::vector<std::vector<std::string>> contents;
    
        struct EditableCell
        {
            int row;
            int col;
            bool operator<(const EditableCell& rhs) const
            {
                if (row == rhs.row)
                {
                    return col < rhs.col;
                }
                else if (col == rhs.col)
                {
                    return row < rhs.row;
                }
    
                return false;
            }
        };
    
        std::map<EditableCell, CellEditCallBack> callbacks;
        CellEditCallBack IsEditable(int row, int col);
    };
    
    

    QPlainStyleTable.cpp

    
    #include <algorithm>
    
    #include <QEvent>
    #include <QMouseEvent>
    #include <QPainter>
    #include "QPlainStyleTable.h"
    
    
    QPlainStyleTable::QPlainStyleTable(QWidget *parent)
        : QWidget(parent), row(0), col(0), cellW(0), cellH(0)
    {
        installEventFilter(this);
    }
    
    QPlainStyleTable::~QPlainStyleTable()
    {
    
    }
    
    void QPlainStyleTable::SetHeaderColor(const QColor& bkColor, const QColor& txtColor)
    {
        headerBkColor = bkColor;
        headerTxtColor = txtColor;
    }
    
    void QPlainStyleTable::SetContentColor(const QColor& evenBkColor, const QColor& oddBkColor, const QColor& txtColor)
    {
        this->evenBkColor = evenBkColor;
        this->oddBkColor = oddBkColor;
    
        this->contentTxtColor = txtColor;
    }
    
    void QPlainStyleTable::SetBorderColor(const QColor& color)
    {
        this->borderColor = color;
    }
    
    void QPlainStyleTable::SetFont(const QFont& headerFont, const QFont& contentFont)
    {
        this->headerFont = headerFont;
        this->contentFont = contentFont;
    }
    
    void QPlainStyleTable::SetDimension(int row, int col)
    {
        this->row = row;
        this->col = col;
    }
    
    void QPlainStyleTable::SetTitle(const std::vector<std::string>& title)
    {
        this->title = title;
    }
    
    void QPlainStyleTable::AddData(const std::vector<std::string>& row)
    {
        this->contents.push_back(row);
        update();
    }
    
    void QPlainStyleTable::ClearContent()
    {
        this->contents.clear();
        this->callbacks.clear();
        update();
    }
    
    void QPlainStyleTable::SetCellEditCallBack(int row, int col, CellEditCallBack callback)
    {
         callbacks[EditableCell{ row, col}] = callback;
    }
    
    void QPlainStyleTable::paintEvent(QPaintEvent *event)
    {
        if (bkCache.isNull())
        {
            DrawBk();
        }
        else
        {
            if (!BkSizeIsSame())
            {
                DrawBk();
            }
        }
    
        QPainter painter(this);
        painter.drawPixmap(rect(), bkCache);
    
        // DrawContentTxt
        painter.setFont(contentFont);
        painter.setPen(contentTxtColor);
        auto showRows = std::min((int)contents.size(), row);
        for (int idx = 0; idx < showRows; ++idx)
        {
            auto& oneRow = contents[idx];
            auto showCols = std::min((int)oneRow.size(), col);
            for (int idy = 0; idy < showCols; ++idy)
            {
                auto& txt = oneRow[idy];
                //painter.setPen(contentTxtColor);
                painter.setClipping(true);
                painter.drawText(idy * cellW, idx* cellH + headerH, cellW, cellH,
                    Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap
                    , QString::fromLocal8Bit(txt.c_str()));
    
                if (IsEditable(idx, idy))
                {
                    painter.setPen(Qt::red);
                    painter.setRenderHint(QPainter::Antialiasing);
                    painter.drawRoundedRect(idy * cellW, idx * cellH + headerH, cellW, cellH
                        , 2, 2);
                }
            }
        }
    }
    
    bool QPlainStyleTable::eventFilter(QObject *watched, QEvent *event)
    {
        if (watched == this
            && event->type() == QEvent::MouseButtonDblClick)
        {
            auto theEvent = (QMouseEvent*)event;
            auto x = theEvent->x();
            auto y = theEvent->y();
    
            auto clickRow = y < headerH ? -1 : (y - headerH) / cellH;
            auto clickCol = x / cellW;
            auto callback = IsEditable(clickRow, clickCol);
            if (callback)
            {
                auto globalPos = theEvent->globalPos();
                callback(globalPos.x(), globalPos.y());
            }
    
            return true;
        }
    
        return QWidget::eventFilter(watched, event);
    }
    
    void QPlainStyleTable::DrawBk()
    {
        QPixmap bk(width(), height());
    
        auto thisW = bk.width();
        auto thisH = bk.height();
        cellH = thisH / (row + 1);
        cellW = thisW / col;
        headerH = thisH - (cellH * row);
        tailW = thisW - (cellW * col);
    
        QPainter painter(&bk);
        painter.setPen(Qt::NoPen);
    
        // header
        painter.setBrush(headerBkColor);
        painter.drawRect(0, 0, thisW - 1, headerH - 1);
    
        headerFont.setHintingPreference(QFont::PreferNoHinting);
        painter.setFont(headerFont);
        painter.setPen(headerTxtColor);
        painter.setRenderHint(QPainter::TextAntialiasing);
        auto min = std::min(col, (int)title.size());
        for (int idx = 0; idx < min; ++idx)
        {
            auto txt = title[idx];
            painter.drawText(idx * cellW, 0, cellW, headerH
                , Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap
                , QString::fromLocal8Bit(txt.c_str()));
        }
    
        // content
        auto isEven = [](int n) { return n % 2 == 0; };
        for (int idx = 0; idx < row; ++idx)
        {
            int upperY = headerH + idx * cellH;
    
            painter.setPen(Qt::NoPen);
            painter.setBrush(isEven(idx) ? evenBkColor : oddBkColor);
            painter.drawRect(0, upperY, thisW - 1, headerH - 1);
    
            painter.setPen(borderColor);
            painter.setBrush(Qt::NoBrush);
            painter.drawLine(0, upperY - 1, thisW - 1, upperY - 1);
        }
    
        // border
        painter.setPen(borderColor);
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(0, 0, thisW - 1, thisH - 1);
    
        bkCache.swap(bk);
    }
    
    bool QPlainStyleTable::BkSizeIsSame()
    {
        return bkCache.size() == size();
    }
    
    CellEditCallBack QPlainStyleTable::IsEditable(int row, int col)
    {
        for (auto& p: callbacks)
        {
            if (p.first.row == row 
                && p.first.col == col)
            {
                return p.second;
            }
        }
    
        return nullptr;
    }
    
    void QPlainStyleTable::SetUnEditable(int row, int col)
    {
        callbacks.erase(EditableCell{row, col});
    }
    
    void QPlainStyleTable::UpdateContent(int row, int col, const std::string& content)
    {
        bool found{ false };
        for (int idx =0 ; idx < contents.size(); ++idx)
        {
            auto& oneRow = contents[idx];
            for (int idy = 0; idy < oneRow.size(); ++idy)
            {
                if (idx == row && idy == col)
                {
                    found = true;
                    auto& old = oneRow[idy];
                    old = content;
                    break;
                }
            }
        }
    
        if (found)
        {
           update();
        }
    }
    

  • Lifetime Qt Champion

    @xce1 said in Painter::DrawText is very slow:

    QString::fromLocal8Bit(txt.c_str())

    Why?



  • @jsulm
    I use VS2019 IDE, with multibyte charset and need show some chinese characters.

    I just tried QString::(txt.c_str()), still has the problem.


  • Lifetime Qt Champion

    @xce1 There is no need for txt.c_str() at all! txt is already a QString which is UTF-16 and can handle Chinese.



  • @jsulm
    Thanks for your attention, but txt is really a std::string.
    The other parts of the program uses c++ std lib mostly, so here I used std::vector<std::vector<std::string>> contents; to store cell strings.

    I just upload the gif, which shows what I described as drawText is slow.
    Would you help me with that?

    Best regards.


  • Lifetime Qt Champion



  • I've replaced all std::strings to QString, and change VS2019 charset to utf, and only use ASCII chars in string.
    Still there is a visible blank before the custom tables shows.

    Even there is only one column, one row.



  • @xce1
    Your post is hurting my eyes! :( :)



  • I've tested it with a single drawText call in paintEvent, the problem still exists.
    But if I put a label on widget, the problem disappears!
    main.cpp

    #include <QtWidgets/QApplication>
    #include "QPlainStyleTable.h"
    #include "QtClass.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
    //     auto _m_pTable = new QPlainStyleTable();
    //     _m_pTable->SetHeaderColor(Qt::red, Qt::green);
    //     _m_pTable->SetContentColor(Qt::gray, Qt::yellow, Qt::blue);
    //     _m_pTable->SetBorderColor(Qt::white);
    //     //_m_pTable->SetFont(QFont(u8"微软雅黑"), QFont(u8"微软雅黑"));
    // 
    //     _m_pTable->SetDimension(1, 1);
    // 
    //     _m_pTable->SetTitle({ "a", "b", "c", "d", "e" });
    //     _m_pTable->AddData({ "a1", "b1", "c1", "d1", "e1`" });
    //     _m_pTable->AddData({ "a2", "b2", "c2", "d2", "e2" });
    //     _m_pTable->AddData({ "a3", "b3", "c3", "d3", "e3" });
    //     _m_pTable->AddData({ "a4", "b4", "c4", "d4", "e4" });
    //     
    //     _m_pTable->show();
        auto _m_pQClass = new QtClass(nullptr);
        _m_pQClass->setFixedSize(500, 400);
        _m_pQClass->show();
        return a.exec();
    }
    

    QtClass.h

    #pragma once
    
    #include <QWidget>
    
    class QtClass : public QWidget
    {
        Q_OBJECT
    
    public:
        QtClass(QWidget *parent);
        ~QtClass();
    
        void paintEvent(QPaintEvent* e) override;
    };
    
    

    QtClass.cpp with QLabel commented out.

    #include "QtClass.h"
    
    #include <QPainter>
    #include <QLabel>
    
    QtClass::QtClass(QWidget *parent)
        : QWidget(parent)
    {
    //     auto _pLabel = new QLabel(this);
    //     _pLabel->setText("HELLO WORLD!");
    //     _pLabel->move(200, 300);
    }
    
    QtClass::~QtClass()
    {
    }
    
    void QtClass::paintEvent(QPaintEvent* e)
    {
        QPainter painter(this);
        painter.drawText(0, 0, 100, 200, Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap, QString("Hello World!"));
    }
    

    test4.gif

    QtClass.cpp with QLabel .

    #include "QtClass.h"
    
    #include <QPainter>
    #include <QLabel>
    
    QtClass::QtClass(QWidget *parent)
        : QWidget(parent)
    {
        auto _pLabel = new QLabel(this);
        _pLabel->setText("HELLO WORLD!");
        _pLabel->move(200, 300);
    }
    
    QtClass::~QtClass()
    {
    }
    
    void QtClass::paintEvent(QPaintEvent* e)
    {
        QPainter painter(this);
        painter.drawText(0, 0, 100, 200, Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap, QString("Hello World!"));
    }
    

    test5.gif



  • @JonB Sorry about that. I've removed the colorful gifs.

    @jsulm Perhaps It's not drawText problem.
    To better demonstrate the situation, I've upload two new comparable gray gifs with a simpler QWidget subclass.


  • Lifetime Qt Champion

    Hi,

    Something just came to mind: maybe QStaticText might be of interest.



  • @SGaist Hi, thanks. I just tried that, It's still the same.


  • Lifetime Qt Champion

    I've no performance problem with your code on linux and windows7 (both in a VM, 64bit, Qt5.15.x)



  • This post is deleted!


  • @Christian-Ehrlicher

    Hi, Thank you for your attention.

    Would you please take a look at the situation below ?

    @xce1 said in How can I replce Qpainter::drawText with a more effecient way to draw text in a table cell:

    I've tested it with a single drawText call in paintEvent, the problem still exists.
    But if I put a label on widget, the problem disappears!
    main.cpp

    #include <QtWidgets/QApplication>
    #include "QPlainStyleTable.h"
    #include "QtClass.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
    //     auto _m_pTable = new QPlainStyleTable();
    //     _m_pTable->SetHeaderColor(Qt::red, Qt::green);
    //     _m_pTable->SetContentColor(Qt::gray, Qt::yellow, Qt::blue);
    //     _m_pTable->SetBorderColor(Qt::white);
    //     //_m_pTable->SetFont(QFont(u8"微软雅黑"), QFont(u8"微软雅黑"));
    // 
    //     _m_pTable->SetDimension(1, 1);
    // 
    //     _m_pTable->SetTitle({ "a", "b", "c", "d", "e" });
    //     _m_pTable->AddData({ "a1", "b1", "c1", "d1", "e1`" });
    //     _m_pTable->AddData({ "a2", "b2", "c2", "d2", "e2" });
    //     _m_pTable->AddData({ "a3", "b3", "c3", "d3", "e3" });
    //     _m_pTable->AddData({ "a4", "b4", "c4", "d4", "e4" });
    //     
    //     _m_pTable->show();
        auto _m_pQClass = new QtClass(nullptr);
        _m_pQClass->setFixedSize(500, 400);
        _m_pQClass->show();
        return a.exec();
    }
    

    QtClass.h

    #pragma once
    
    #include <QWidget>
    
    class QtClass : public QWidget
    {
        Q_OBJECT
    
    public:
        QtClass(QWidget *parent);
        ~QtClass();
    
        void paintEvent(QPaintEvent* e) override;
    };
    
    

    QtClass.cpp with QLabel commented out.

    #include "QtClass.h"
    
    #include <QPainter>
    #include <QLabel>
    
    QtClass::QtClass(QWidget *parent)
        : QWidget(parent)
    {
    //     auto _pLabel = new QLabel(this);
    //     _pLabel->setText("HELLO WORLD!");
    //     _pLabel->move(200, 300);
    }
    
    QtClass::~QtClass()
    {
    }
    
    void QtClass::paintEvent(QPaintEvent* e)
    {
        QPainter painter(this);
        painter.drawText(0, 0, 100, 200, Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap, QString("Hello World!"));
    }
    

    test4.gif

    QtClass.cpp with QLabel .

    #include "QtClass.h"
    
    #include <QPainter>
    #include <QLabel>
    
    QtClass::QtClass(QWidget *parent)
        : QWidget(parent)
    {
        auto _pLabel = new QLabel(this);
        _pLabel->setText("HELLO WORLD!");
        _pLabel->move(200, 300);
    }
    
    QtClass::~QtClass()
    {
    }
    
    void QtClass::paintEvent(QPaintEvent* e)
    {
        QPainter painter(this);
        painter.drawText(0, 0, 100, 200, Qt::AlignCenter | Qt::TextSingleLine | Qt::TextDontClip | Qt::TextWordWrap, QString("Hello World!"));
    }
    

    test5.gif


  • Lifetime Qt Champion

    @xce1 said in How can I replce Qpainter::drawText with a more effecient way to draw text in a table cell:

    Would you please take a look at the situation below ?

    That's the same code as above.



  • @Christian-Ehrlicher
    Yes. It is. I thought maybe you missed the last post. Sorry about that.

    I used Qt5.14.2 on Win10 VS2019.

    Since no one else has the same problem. I'm about to just put a QLabel or other Qt widget to bypass it.

    Thank you anyway.


  • Moderators

    @xce1 have you tried a release build with O3 ? maybe its just a debug/development issue



  • @J-Hilk I just tried that. It's the same as no debug.
    There is a visible blank background before "Hello World" shows up
    if I didn't add a QLabel in QtClass constructor.
    Thank you!


Log in to reply