Qt learning custom control 1: draw a simple to use dashboard
Opening words
I worked as an electrical engineer of new energy bus for several years before, and the platform was OK. All I had been in contact with were electrical principles, harnesses, batteries and various vehicle controllers, etc. all day long, it was basically those tedious and repetitive work, which fortunately created a lot of profits for the enterprise, but I had a great limitation on personal ability improvement and insight.
Later, I used my spare time to look up the information on the Internet and learned a little basic knowledge of Python, VBA, JSP, HTML, Qt and so on. I had a basic understanding of programming.
Later, due to various reasons, the previous platform began to collapse, coupled with their own educational background problems, so after being recommended by friends who do vehicle controller hardware, they entered the current company, slowly began to change their business to start controller software development, and began to self-study Qt.
Due to my poor memory, I forget what I have done and learned easily. I think it's still on my blog, and it's also convenient for me to browse. Don't talk too much, just get to the point.
Method 1: load SVG (if the SVG file cannot be uploaded, the screenshot will be displayed)
Dial screenshots
Pointer screenshots
widget.h:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <qtimer.h> #include <qsvgrenderer.h> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); inline qreal value() {return m_value;} inline void setValue(qreal val) { m_value = val > m_max ? m_max : (val < m_min ? m_min : val); } inline qreal min() {return m_min;} inline qreal max() {return m_max;} inline qreal startAngle() {return m_startAngle;} inline qreal endAngle() {return m_endAngle;} protected: void paintEvent(QPaintEvent *); qreal calcAngle(qreal val); public slots: void changeValue(); private: Ui::Widget *ui; qreal m_value = 0.0; qreal m_min = 0.0; qreal m_max = 240.0; qreal m_startAngle = -120; qreal m_endAngle = 120.0; QTimer *m_timer; QSvgRenderer* boardRender; QSvgRenderer* needleRender; }; #endif // WIDGET_H
widget.cpp:
#include "widget.h" #include "ui_widget.h" #include <qpainter.h> #include <qmath.h> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); resize(300, 300); boardRender = new QSvgRenderer(); boardRender->load(QString("F:/QtPrj/VSG/speedometer.svg")); needleRender = new QSvgRenderer(); needleRender->load(QString("F:/QtPrj/VSG/needel.svg")); m_timer = new QTimer(); m_timer->setTimerType(Qt::PreciseTimer); m_timer->setInterval(50); connect(m_timer, SIGNAL(timeout()), this, SLOT(changeValue())); m_timer->start(); } Widget::~Widget() { delete ui; } void Widget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); qreal size = qMin(width(), height()); painter.save(); boardRender->render(&painter, QRectF((width()-size)/2, (height()-size)/2, size, size)); painter.restore(); painter.save(); QTransform trans; trans.translate(width()/2, height()/2); trans.rotate(calcAngle(m_value), Qt::ZAxis); trans.translate(-width()/2, -height()/2); painter.setWorldTransform(trans); needleRender->render(&painter, QRectF((width()-size)/2, (height()-size)/2, size, size)); painter.restore(); painter.save(); QFont textFont("Microsoft YaHei", static_cast<int>(12*size/200), QFont::Bold); painter.setFont(textFont); QFontMetricsF fm(textFont); QString text = QString::number(static_cast<int>(m_value), 10); qreal pixelWidth = fm.horizontalAdvance(text); qreal pixelHeight = fm.height(); const QRectF rect = QRectF((width()-pixelWidth)/2, height()/2+height()/5, pixelWidth, pixelHeight); painter.setPen(Qt::white); painter.drawText(rect, Qt::AlignCenter, text); painter.restore(); } qreal Widget::calcAngle(qreal val) { return (val * (m_endAngle - m_startAngle) / (m_max - m_min) + m_startAngle); } void Widget::changeValue() { static bool dirChange = false; if (!dirChange) { m_value += 2; if (m_value >= m_max) { m_value = m_max; dirChange = true; } } else { m_value -= 2; if (m_value <= m_min) { m_value = m_min; dirChange = false; } } int size = qMin(width(), height()); update(QRect((width()-size)/2, (height()-size)/2, size, size)); }
Effect map 1:
Method 2: draw directly with code
qmgauge.h:
#ifndef QMGAUGE_H #define QMGAUGE_H #include <qwidget.h> #include <qpainter.h> class QmGauge : public QWidget { Q_OBJECT Q_PROPERTY(qreal m_minimum READ minimum WRITE setMinimum) Q_PROPERTY(qreal m_maximum READ maximum WRITE setMaximum) Q_PROPERTY(qreal m_value READ value WRITE setValue) Q_PROPERTY(qreal m_startAngle READ startAngle WRITE setStartAngle) Q_PROPERTY(qreal m_endAngle READ endAngle WRITE setEndAngle) Q_PROPERTY(int m_majorScaleLines READ majorScaleLines WRITE setMajorScaleLines) Q_PROPERTY(int m_minorScaleLines READ minorScaleLines WRITE setMinorScaleLines) Q_PROPERTY(QString m_unitLabel READ unitLabel WRITE setUnitLabel) public: explicit QmGauge(QWidget *parent = nullptr); ~QmGauge(); inline qreal minimum() {return m_minimum;} inline void setMinimum(qreal val) {m_minimum = val;} inline qreal maximum() {return m_maximum;} inline void setMaximum(qreal val) {m_maximum = val;} inline qreal value() {return m_value;} inline void setValue(qreal val) { m_value = val > m_maximum ? m_maximum : (val < m_minimum ? m_minimum : val);} inline qreal startAngle() {return m_startAngle;} inline void setStartAngle(qreal angle) {m_startAngle = angle;} inline qreal endAngle() {return m_endAngle;} inline void setEndAngle(qreal angle) {m_endAngle = angle;} inline int majorScaleLines() {return m_majorScaleLines;} inline void setMajorScaleLines(int lines) {m_majorScaleLines = lines;} inline int minorScaleLines() {return m_minorScaleLines;} inline void setMinorScaleLines(int lines) {m_minorScaleLines = lines;} inline QString unitLabel() {return m_unitLabel;} inline void setUnitLabel(QString label) {m_unitLabel = label;} public slots: void replot(); protected: void drawPlate(QPainter *painter); void drawScaleLines(QPainter *painter); void drawScaleLabels(QPainter *painter); void drawScalLabelByChar(QPainter *painter); void drawNeedle(QPainter *painter); void drawUnitLabel(QPainter *painter); virtual void paintEvent(QPaintEvent *); private: qreal m_minimum = 0.0; qreal m_maximum = 240.0; qreal m_value = 0.0; qreal m_startAngle = -120.0; qreal m_endAngle = 120.0; int m_majorScaleLines = 13; int m_minorScaleLines = 4; QString m_unitLabel = QString(tr("km/h")); }; #endif // QMGAUGE_H
qmgauge.cpp:
#include "qmgauge.h" #include <QLinearGradient> #include <qmath.h> #include <qdebug.h> /** * @brief QmGauge::QmGauge * @param parent */ QmGauge::QmGauge(QWidget *parent) : QWidget(parent) { //Set minimum size setMinimumSize(QSize(100, 100)); } /** * @brief QmGauge::~QmGauge */ QmGauge::~QmGauge() { } /** * @brief QmGauge::replot */ void QmGauge::replot() { //Update drawing area only, reduce calculation int minWidth = qMin(width(), height()); update((width()-minWidth)/2, (height()-minWidth)/2, minWidth, minWidth); } /** * @brief QmGauge::drawPlate * @param painter */ void QmGauge::drawPlate(QPainter *painter) { //Save drawing device current state painter->save(); painter->setPen(Qt::NoPen); qreal radius = qMin(width(), height())/2; //Draw the dial strand skeleton QLinearGradient lg1 = QLinearGradient(-radius, -radius, radius, radius); lg1.setColorAt(0.0, QColor(240, 240, 240)); lg1.setColorAt(1.0, QColor(200, 200, 200)); lg1.setSpread(QGradient::RepeatSpread); painter->setBrush(lg1); painter->drawEllipse(QPointF(0, 0), radius, radius); QLinearGradient lg2 = QLinearGradient(-radius, -radius, radius, radius); lg2.setColorAt(0.0, QColor(210, 210, 210)); lg2.setColorAt(1.0, QColor(170, 170, 170)); lg2.setSpread(QGradient::RepeatSpread); painter->setBrush(lg2); painter->drawEllipse(QPointF(0, 0), radius-radius/20, radius-radius/20); //Draw Dial Black disc painter->setBrush(QColor(30, 30, 30)); painter->drawEllipse(QPointF(0, 0), radius-radius/10, radius-radius/10); //Restore drawing device to previous state painter->restore(); } /** * @brief QmGauge::drawScaleLines * @param painter */ void QmGauge::drawScaleLines(QPainter *painter) { //Save drawing device current state painter->save(); qreal currentWidth = qMin(width(), height())/2; qreal startPosX = -currentWidth + currentWidth/4; qreal minorEndPosX = startPosX + currentWidth/32; qreal majorEndPosX = startPosX + currentWidth/16; qreal angleInc = (m_endAngle - m_startAngle) / (m_majorScaleLines-1); angleInc /= m_minorScaleLines + 1; //Rotate to the angle corresponding to the minimum value painter->rotate(m_startAngle + 90); qreal minWidth = qMin(minimumWidth(), minimumHeight()); QPen pen; pen.setColor(Qt::white); //Draw tick marks for (int i = 0; i < m_majorScaleLines; i++) { pen.setWidthF(2.0*currentWidth*2/minWidth); //Let lineweight scale with window painter->setPen(pen); painter->drawLine(QPointF(startPosX, 0), QPointF(majorEndPosX, 0)); if (i < m_majorScaleLines-1) { pen.setWidthF(1.0*currentWidth*2/minWidth); painter->setPen(pen); for (int j = 0; j < m_minorScaleLines; j++) { painter->rotate(angleInc); painter->drawLine(QPointF(startPosX, 0), QPointF(minorEndPosX, 0)); } painter->rotate(angleInc); } } //Restore drawing device to previous state painter->restore(); } /** * @brief QmGauge::drawScaleLabels * @param painter * @note The idea of drawing around text labels is as follows: * 1. First, each text label is drawn to the corresponding pixmap memory block; * 2. Then draw pixmap to the interface. */ void QmGauge::drawScaleLabels(QPainter *painter) { //Save drawing device current state painter->save(); //Create a font and let the text size scale with the window QFont labelFont = QFont("Microsoft YaHei", 4*qMin(width(),height())/qMin(minimumWidth(),minimumHeight()), QFont::Bold); /* Create a text measure, noting that if you want the label text to wrap correctly, * You must calculate the pixel height and width of the text maximum, * And draw the outer frame with the maximum value of the height and width */ QFontMetrics tagFontMetrics = QFontMetrics(labelFont); int pixelWidth = tagFontMetrics.horizontalAdvance(QString::number(static_cast<int>(m_maximum), 10)); int pixelHeight = tagFontMetrics.height(); int maxTxtWidth = qMax(pixelWidth, pixelHeight)+1; //Calculate label increment int valueInc = static_cast<int>((m_maximum-m_minimum)/(m_majorScaleLines-1)); // Calculate the start position of drawing label text qreal minWidth = qMin(width(), height())/2; qreal startPosX = -minWidth + minWidth/14; //Calculate the drawing angle increment of label text position qreal angleInc = (m_endAngle - m_startAngle) / (m_majorScaleLines-1); //Initialize drawing device painter->setFont(labelFont); //Set font painter->rotate(m_startAngle + 90); //Rotate to start drawing position painter->setPen(Qt::white);//Set brush color //Start drawing label text for (int i = 0; i < m_majorScaleLines; i++) { //Convert label numbers to text QString label = QString::number(valueInc*i, 10); //Create the label text pixmap, which is drawn to memory QPixmap pixmap(maxTxtWidth, maxTxtWidth); pixmap.fill(Qt::transparent); //Background transparent fill QPainter p(&pixmap); //Create pixmap temporary virtual drawing device QTransform transf; //Create virtual transformation transf.translate(maxTxtWidth/2, maxTxtWidth/2); //Coordinate origin transformation to pixmap Center transf.rotate(-90); //Rotate the coordinate system 90 degrees clockwise, that is, rotate the text 90 degrees transf.translate(-maxTxtWidth/2, -maxTxtWidth/2); //Restore coordinate system origin p.setWorldTransform(transf); //Apply virtual transformations to temporarily created drawing devices p.setPen(Qt::white); p.setFont(labelFont); p.drawText(QRectF(0, 0, maxTxtWidth, maxTxtWidth), Qt::AlignCenter, label); //Draw text to pixmap //Draw pixmap to the interface QRectF targetRect = QRectF(startPosX, -maxTxtWidth/2, maxTxtWidth, maxTxtWidth); QRectF sourceRect; //Be careful not to set the value of this rectangle, otherwise the drawing position will be incorrect painter->drawPixmap(targetRect, pixmap, sourceRect); //Call interface drawing device to draw memory block pixmap //Rotate fixed angle after drawing one label at a time painter->rotate(angleInc); } //Restore drawing device to previous state painter->restore(); } /** * @brief QmGauge::drawScalLabelByChar * @param painter */ void QmGauge::drawScalLabelByChar(QPainter *painter) { painter->save(); QFont labelFont = QFont("Microsoft YaHei", 4*qMin(width(),height())/qMin(minimumWidth(),minimumHeight()), QFont::Bold); QFontMetrics tagFontMetrics = QFontMetrics(labelFont); int pixelWidth = tagFontMetrics.horizontalAdvance(QString("0")); int pixelHeight = tagFontMetrics.height(); int maxTxtWidth = qMax(pixelWidth, pixelHeight); int valueInc = static_cast<int>((m_maximum-m_minimum)/(m_majorScaleLines-1)); qreal minWidth = qMin(width(), height())/2; qreal startPosX = -minWidth + minWidth/11; qreal angleInc = (m_endAngle - m_startAngle) / (m_majorScaleLines-1); painter->setFont(labelFont); painter->rotate(m_startAngle + 90); painter->setPen(Qt::white); qreal anglePerChar = qAbs(qAsin(static_cast<qreal>(maxTxtWidth)/2.0/qAbs(startPosX))*180.0/M_PI); QList<QString> labelList; labelList.clear(); for (int i = 0; i < m_majorScaleLines; i++) { labelList.append(QString::number(valueInc*i, 10)); } QList<int> indexList; indexList.clear(); for(int i = 0; i < labelList.count()-1; i++) { if (labelList.at(i).size() != labelList.at(i+1).size()) { indexList.append(i); } } for (int i = 0; i < labelList.count(); i++) { int charCount = labelList.at(i).size(); painter->rotate(-anglePerChar*(charCount-1)); for (int j = 0; j < charCount; j++) { QPixmap pixmap(maxTxtWidth, maxTxtWidth); pixmap.fill(Qt::transparent); QPainter p(&pixmap); QTransform transf; transf.translate(maxTxtWidth/2, maxTxtWidth/2); transf.rotate(-90); transf.translate(-maxTxtWidth/2, -maxTxtWidth/2); p.setWorldTransform(transf); p.setPen(Qt::white); p.setFont(labelFont); p.drawText(QRectF(0, 0, maxTxtWidth, maxTxtWidth), Qt::AlignCenter, labelList.at(i).at(j)); QRectF targetRect = QRectF(startPosX, -maxTxtWidth/2, maxTxtWidth, maxTxtWidth); QRectF sourceRect; painter->drawPixmap(targetRect, pixmap, sourceRect); if (j < charCount-1) { painter->rotate(anglePerChar); } else { for (int k = 0; k < indexList.count(); k++) { if (indexList.at(k) == i) { painter->rotate(anglePerChar/2.0); indexList.removeAt(k); } } } } painter->rotate(angleInc); } painter->restore(); } /** * @brief QmGauge::drawNeedle * @param painter */ void QmGauge::drawNeedle(QPainter *painter) { int radius; //Save drawing device current state painter->save(); //Calculate the current angle of the pointer qreal currentAngle = -90 + m_startAngle + (m_value - m_minimum) * (m_endAngle - m_startAngle) / (m_maximum- m_minimum); //Draw pointer painter->rotate(currentAngle); radius = qMin(width(), height()) / 32; QRadialGradient rg1(0, 0, radius*11, 0, 0); rg1.setColorAt(0.0, QColor(200, 200, 200)); rg1.setColorAt(1.0, QColor(240, 240, 240)); painter->setPen(Qt::white); painter->setBrush(rg1); const QPointF needledPoints[3] = {QPointF(0.0, radius*0.75), QPointF(0.0, -radius*0.75), QPointF(radius*11, 0)}; painter->drawPolygon(needledPoints, 3); //Restore drawing device to previous state painter->restore(); //Save drawing device current state painter->save(); //Calculate cap radius radius = qMin(width(), height()) / 20; //Draw pointer cap QRadialGradient rg2(0, 0, radius*2); rg2.setColorAt(0.0, Qt::darkGray); rg2.setColorAt(0.5, Qt::white); rg2.setColorAt(1.0, Qt::darkGray); painter->setPen(Qt::NoPen); painter->setBrush(rg2); painter->drawEllipse(QPoint(0, 0), radius, radius); //Restore drawing device to previous state painter->restore(); } /** * @brief QmGauge::drawUnitLabel * @param painter */ void QmGauge::drawUnitLabel(QPainter *painter) { //Save drawing device current state painter->save(); //Create a font and let the text size scale with the window QFont unitFont = QFont("Microsoft YaHei", 4*qMin(width(),height())/qMin(minimumWidth(),minimumHeight()), QFont::Bold); //Draw unit label QFontMetrics unitFontMetrics = QFontMetrics(unitFont); int pixelWidth = unitFontMetrics.horizontalAdvance(m_unitLabel); int pixelHeight = unitFontMetrics.height(); painter->setFont(unitFont); painter->setPen(Qt::white); qreal limit = qMin(width(), height())/2.0/8.0; qreal posY = height()/18.0; if (posY > limit) { posY = limit; } painter->drawText(QRectF(-pixelWidth/2.0, posY, pixelWidth, pixelHeight), Qt::AlignCenter, m_unitLabel); //Create a font and let the text size scale with the window QFont valueFont = QFont("Microsoft YaHei", 6*qMin(width(),height())/qMin(minimumWidth(),minimumHeight()), QFont::Bold); //Draw real time values QFontMetrics valueFontMetrics = QFontMetrics(valueFont); QString valueStr = QString::number(static_cast<int>(m_value), 10); pixelWidth = valueFontMetrics.horizontalAdvance(valueStr); pixelHeight = valueFontMetrics.height(); painter->setFont(valueFont); limit = qMin(width(), height())/2.0/4.0; posY = height()/6.0; if (posY > limit) { posY = limit; } painter->drawText(QRectF(-pixelWidth/2.0, posY, pixelWidth, pixelHeight), Qt::AlignCenter, valueStr); //Restore drawing device to previous state painter->restore(); } /** * @brief QmGauge::paintEvent */ void QmGauge::paintEvent(QPaintEvent *) { //Create interface drawing device QPainter painter(this); //Set anti aliasing painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); //Transform coordinate origin to interface Center painter.translate(QPointF(width()/2, height()/2)); //Call drawing function drawPlate(&painter); drawScaleLabels(&painter); // drawScalLabelByChar(&painter); drawScaleLines(&painter); drawNeedle(&painter); drawUnitLabel(&painter); }
Be careful:
1) The drawScaleLabels function draws a numeric tag string directly on a QPixmap.
2) drawScalLabelByChar draws each character in a numeric label string onto a QPixmap.
The former function will be a little more concise. When the given interface size is updated with 50ms, it will not take up any extra CPU resources. The latter function will take up a certain amount of CPU resources with a large amount of calculation. The display effect is almost the same. The reader can test it himself.
qmwidgetsdemo.h
#ifndef QMWIDGETSDEMO_H #define QMWIDGETSDEMO_H #include <QWidget> #include <QmGauge/qmgauge.h> #include <qgridlayout.h> #include <qtimer.h> class QmWidgetsDemo : public QWidget { Q_OBJECT public: explicit QmWidgetsDemo(QWidget *parent = nullptr); ~QmWidgetsDemo(); public slots: void updateGauge(); private: QGridLayout *gridLayout; QmGauge *gauge; QTimer *m_timer; qreal m_value = 0.0; }; #endif // QMWIDGETSDEMO_H
qmwidgetsdemo.cpp
#include "qmwidgetsdemo.h" QmWidgetsDemo::QmWidgetsDemo(QWidget *parent) : QWidget(parent) { resize(300, 300); gauge = new QmGauge(); gridLayout = new QGridLayout(); gridLayout->addWidget(gauge, 0, 0, 1, 1); setLayout(gridLayout); m_timer = new QTimer(); connect(m_timer, SIGNAL(timeout()), this, SLOT(updateGauge())); m_timer->start(50); } QmWidgetsDemo::~QmWidgetsDemo() { } void QmWidgetsDemo::updateGauge() { static bool changeDir = false; if (!changeDir) { m_value += 2; if (m_value >= 240) { m_value = 240; changeDir = true; } } else { m_value -= 2; if (m_value <= 0) { m_value = 0; changeDir = false; } } gauge->setValue(m_value); gauge->replot(); }
Effect map 2:
The first time in CSDN, I hope to help readers!