Custom Qt timeline control

This is a vertical timeline that is very simple to display but cannot display formatted text. If you want to display formatted text, you can replace the position where the text is displayed on the right with a QLabel control to display formatted text.

If you want to change the text line spacing, according to the answers I have searched on the Internet, you can only use QLabel with the qss style sheet (that is, the styleSheet property of the control). According to this control, you can learn how to measure and draw Qt multi-line text size. The method of using this control is new MTimeLine, and then use the append function to add items, or the removeAt function to delete items.

This code is tested and passed on VS2015 and Qt5.9.

The following is the rendering:

 

Above code, header file:

class MTimeLinePrivate : public QWidget
{
    Q_OBJECT

public:
    MTimeLinePrivate(QWidget* parent = 0);
    void append(const QDate& date, const QString& str);
    void removeAt(int index);

private:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent*) override;
    void showEvent(QShowEvent*) override;
    QSize calcContentSize();
    QSize calcDateStringSize(const QFontMetrics& fm) const;
    void paintDateBkShape(QPainter* painter, const QRect& rect, int whereisy);
    void paintTextBkShape(QPainter* painter, const QRect& rect, int whereisy);

    struct EventInfo
    {
        QDate date;
        QString content;
    };

private:
    static const QMargins contentm; /* 控件内容的边距 */
    static const QMargins datep; /* 日期文本的边距 */
    static const QMargins datem; /* 日期文本背景矩形的外边距 */
    static const QMargins textp; /* 事件文本的边距 */
    static const QMargins textm; /* 事件文本背景矩形的外边距 */
    QVector<EventInfo> events;
};

class MTimeLine : public QWidget
{
    Q_OBJECT

public:
    MTimeLine(QWidget* parent = 0);
    void append(const QDate& date, const QString& str);
    void removeAt(int index);

private:
    MTimeLinePrivate* tlReal;
};

CPP file:

const QMargins MTimeLinePrivate::contentm(10, 6, 10, 6);
const QMargins MTimeLinePrivate::datep(10, 10, 10, 10);
const QMargins MTimeLinePrivate::datem(0, 6, 14, 6);
const QMargins MTimeLinePrivate::textp(12, 12, 12, 12);
const QMargins MTimeLinePrivate::textm(14, 6, 0, 6);

MTimeLinePrivate::MTimeLinePrivate(QWidget* parent) :
    QWidget(parent)
{
}

void MTimeLinePrivate::append(const QDate& date, const QString& str)
{
    events.push_back({ date, str });
    if (isVisible())
    {
        QSize mini = calcContentSize();
        setMinimumSize(mini);
        update();
    }
}

void MTimeLinePrivate::removeAt(int index)
{
    events.remove(index);
    if (isVisible())
    {
        QSize mini = calcContentSize();
        setMinimumSize(mini);
        update();
    }
}

void MTimeLinePrivate::showEvent(QShowEvent*)
{
    QSize mini = calcContentSize();
    setMinimumSize(mini);
}

void MTimeLinePrivate::resizeEvent(QResizeEvent*)
{
    QSize mini = calcContentSize();
    setMinimumSize(mini);
}

QSize MTimeLinePrivate::calcContentSize()
{
    QFontMetrics fm = fontMetrics();
    QSize datesz = calcDateStringSize(fm);
    int ally = contentm.top();
    int datex = contentm.left();
    int midx = contentm.left() + datem.left() + datep.left() + datesz.width() + datep.right() + datem.right();
    int textx = midx;
    for (const auto &item : events)
    {
        QRect dateOuterRect;
        dateOuterRect.setX(datex + datem.left());
        dateOuterRect.setY(ally + datem.top());
        dateOuterRect.setWidth(datep.left() + datesz.width() + datep.right());
        dateOuterRect.setHeight(datep.top() + datesz.height() + datep.bottom());
        QRect dateRect;
        dateRect.setX(dateOuterRect.x() + datep.left());
        dateRect.setY(dateOuterRect.y() + datep.top());
        dateRect.setSize(datesz);

        int charx = textx + textp.left() + textm.left();
        int chary = ally + textm.top() + textp.top();
        int charw = width() - charx - (textp.right() + textm.right() + contentm.right());
        QRect textRect = fm.boundingRect(charx, chary, charw, 12000, Qt::TextWordWrap, item.content);
        QRect textOuterRect;
        textOuterRect.setX(textx + textm.left());
        textOuterRect.setY(ally + textm.top());
        textOuterRect.setWidth(textp.left() + textRect.width() + textp.right());
        textOuterRect.setHeight(textp.top() + textRect.height() + textp.bottom());

        int midh1 = datem.top() + dateOuterRect.height() + datem.bottom(); /* 左侧高度 */
        int midh2 = textm.top() + textOuterRect.height() + textm.bottom(); /* 右侧高度 */
        int midy2 = ally + qMax(midh1, midh2);

        ally = midy2;
    }
    return QSize(2 * midx, ally + contentm.bottom());
}

void MTimeLinePrivate::paintDateBkShape(QPainter* painter, const QRect& rect, int whereisy)
{
    const QPoint triangle[] =
    {
        { 0, -6 },
        { 7, 0 },
        { 0, 6 },
    };
    painter->setPen(Qt::NoPen);
    painter->setBrush(QColor(0x34, 0x98, 0xdb));
    painter->drawRoundedRect(rect, 6, 6);
    painter->save();
    painter->translate(rect.right(), whereisy);
    painter->drawPolygon(triangle, 3);
    painter->restore();
}

void MTimeLinePrivate::paintTextBkShape(QPainter* painter, const QRect& rect, int whereisy)
{
    const QPoint triangle[] =
    {
        { 0, -6 },
        { -7, 0 },
        { 0, 6 },
    };
    painter->setPen(Qt::NoPen);
    painter->setBrush(QColor(0xec, 0xf0, 0xf1));
    painter->drawRoundedRect(rect, 6, 6);
    painter->save();
    painter->translate(rect.x(), whereisy);
    painter->drawPolygon(triangle, 3);
    painter->restore();
}

QSize MTimeLinePrivate::calcDateStringSize(const QFontMetrics& fm) const
{
    QDate today = QDate::currentDate();
    QSize datesz = fm.size(0, today.toString(u8"yyyy年MM月dd日"));
    return datesz;
}

void MTimeLinePrivate::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(0, 0, width(), height(), Qt::white); /* 白色背景 */
    QFontMetrics fm = painter.fontMetrics();
    QSize datesz = calcDateStringSize(fm);
    int ally = contentm.top();
    int datex = contentm.left();
    int midx = contentm.left() + datem.left() + datep.left() + datesz.width() + datep.right() + datem.right();
    int textx = midx;
    bool odd = true; /* 奇偶数,用来区分绘制点的样式 */
    for (const auto &item : events)
    {
        /* 绘制左侧日期 */
        QRect dateOuterRect;
        dateOuterRect.setX(datex + datem.left());
        dateOuterRect.setY(ally + datem.top());
        dateOuterRect.setWidth(datep.left() + datesz.width() + datep.right());
        dateOuterRect.setHeight(datep.top() + datesz.height() + datep.bottom());
        const int doty = dateOuterRect.center().y();
        paintDateBkShape(&painter, dateOuterRect, doty);
        QRect dateRect;
        dateRect.setX(dateOuterRect.x() + datep.left());
        dateRect.setY(dateOuterRect.y() + datep.top());
        dateRect.setSize(datesz);
        painter.setPen(Qt::black);
        painter.drawText(dateRect, 0, item.date.toString(u8"yyyy年MM月dd日"));
        /* 绘制右侧字符串 */
        int charx = textx + textp.left() + textm.left();
        int chary = ally + textm.top() + textp.top();
        int charw = width() - charx - (textp.right() + textm.right() + contentm.right());
        QRect textRect = fm.boundingRect(charx, chary, charw, 12000, Qt::TextWordWrap, item.content);
        QRect textOuterRect;
        textOuterRect.setX(textx + textm.left());
        textOuterRect.setY(ally + textm.top());
        textOuterRect.setWidth(textp.left() + textRect.width() + textp.right());
        textOuterRect.setHeight(textp.top() + textRect.height() + textp.bottom());
        paintTextBkShape(&painter, textOuterRect, doty);
        painter.setPen(Qt::black);
        painter.drawText(textRect, Qt::TextWordWrap, item.content);
        /* 绘制中间的线条和点 */
        int midh1 = datem.top() + dateOuterRect.height() + datem.bottom(); /* 左侧高度 */
        int midh2 = textm.top() + textOuterRect.height() + textm.bottom(); /* 右侧高度 */
        int midy2 = ally + qMax(midh1, midh2);
        painter.setPen(QPen(QColor(0, 170, 255), 2));
        painter.drawLine(QPoint(midx, ally), QPoint(midx, midy2));
        painter.setPen(QPen(QColor(0, 170, 255), 8, Qt::SolidLine, odd ? Qt::SquareCap : Qt::RoundCap));
        painter.drawPoint(midx, doty);

        odd = !odd;
        ally = midy2;
    }
}

/

MTimeLine::MTimeLine(QWidget* parent) :
    QWidget(parent)
{
    QGridLayout* lay = new QGridLayout(this);
    lay->setContentsMargins(0, 0, 0, 0);
    QScrollArea* area = new QScrollArea(this);
    tlReal = new MTimeLinePrivate(this);
    area->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    area->setWidget(tlReal);
    area->setWidgetResizable(true);
    lay->addWidget(area);
    setLayout(lay);
}

void MTimeLine::append(const QDate& date, const QString& str)
{
    tlReal->append(date, str);
}

void MTimeLine::removeAt(int index)
{
    tlReal->removeAt(index);
}

The benefits of this article, free to receive Qt development learning materials package, technical video, including (Qt actual combat project, C++ language foundation, C++ design mode, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/m0_73443478/article/details/132474582