Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Implementing Radar and Car Dashboard with QPainter in C++ Qt

Notes 1

Event Handling for Drawing

The QPaintEvent class in Qt handles drawing operations when widgets need repainting. This occurs during initial display, resizing, exposure after being obscured, or through explicit calls to update() or repaint().

Custom drawing is implemented by overriding paintEvent(QPaintEvent*) in QWidget subclasses:

void MyWidget::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    // Drawing logic here
}

Painter Basics

Initialization and Settings

QPainter facilitates rendering on screens. It supports lines, shapes, text, and images.

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);

Pen and Brush Configuration

Confiugre stroke and fill properties:

QPen pen;
pen.setColor(Qt::blue);
pen.setWidth(5);
painter.setPen(pen);

painter.setBrush(Qt::yellow);

Basic Shapes

Drawing text:

painter.drawText(rect(), Qt::AlignCenter, "Qt Example");
painter.drawText(600, 550, "Custom Position");

Drawing lines:

painter.drawLine(10, 200, 300, 20);
painter.drawLine(QLine(10, 300, 400, 30));
painter.drawLine(QPoint(10, 400), QPoint(300, 590));

Drawing rectangles:

painter.drawRect(QRect(500, 30, 200, 100));

Drawing ellipses:

painter.drawEllipse(QRect(500, 30, 200, 100));
painter.drawEllipse(QPoint(400, 300), 200, 100);
painter.drawEllipse(rect().center(), 200, 100);
painter.drawEllipse(QPoint(400, 500), 80, 80);

Drawing arcs:

QRect rectangle(650, 250, 100, 100);
int startAngle = 0 * 16;
int spanAngle = 270 * 16;
painter.drawArc(rectangle, startAngle, spanAngle);

Drawing pies:

int startAngle = 0 * 16;
int spanAngle = 120 * 16;
painter.drawPie(QRect(300, 200, 200, 200), startAngle, spanAngle);

Gradient Effects

Linear Gradients

Create gradients from one color to another along a line:

QLinearGradient linearGradient(0, 0, 100, 100);
linearGradient.setColorAt(0.0, Qt::red);
linearGradient.setColorAt(1.0, Qt::blue);
QBrush brush(linearGradient);
painter.setBrush(brush);
painter.drawRect(this->rect());

Radial Gradeints

Define color transitions from center outward:

QRadialGradient radialGradient(50, 50, 50);
radialGradient.setColorAt(0.0, Qt::yellow);
radialGradient.setColorAt(1.0, Qt::black);
QBrush brush(radialGradient);
painter.setBrush(brush);
painter.drawRect(this->rect());

Conical Gradients

Generate color rotations around a point:

QConicalGradient conicalGradient(100, 100, 0);
conicalGradient.setColorAt(0.0, Qt::red);
conicalGradient.setColorAt(0.5, Qt::blue);
conicalGradient.setColorAt(1.0, Qt::red);
QBrush brush(conicalGradient);
painter.setBrush(brush);
painter.drawRect(this->rect());

Simulated Radar Scanner

Coordinate Transformation

Shift the origin of the painter's coordinate system to the widget's center:

painter.translate(rect().center());

Radar Interface Implementation

Draws a black background, concentric circles, axes, and a conical gradient pie:

void Widget::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    painter.setBrush(Qt::black);
    painter.drawRect(rect());

    QPen pen(Qt::green, 4);
    painter.setPen(pen);
    painter.translate(rect().center());

    painter.setBrush(Qt::NoBrush);
    for(int i = 50; i <= 300; i += 50)
        painter.drawEllipse(QPoint(0, 0), i, i);

    painter.drawLine(QPoint(-300, 0), QPoint(300, 0));
    painter.drawLine(QPoint(0, -300), QPoint(0, 300));

    QConicalGradient conGradient(0, 0, startAngle);
    conGradient.setColorAt(0, QColor(0, 255, 0, 200));
    conGradient.setColorAt(0.1, QColor(0, 255, 0, 100));
    conGradient.setColorAt(0.2, QColor(0, 255, 0, 0));
    conGradient.setColorAt(1, QColor(0, 255, 0, 0));

    painter.setBrush(conGradient);
    painter.setPen(Qt::NoPen);
    painter.drawPie(QRect(-300, -300, 600, 600), startAngle * 16, spanAngle * 16);
}

Dynamic Scanning

Control the scan angle using a timer:

QTimer *timer = new QTimer(this);
timer->setInterval(20);
timer->start();

startAngle = 0;
spanAngle = 70;

connect(timer, &QTimer::timeout, [=]() {
    startAngle += 1;
    if(startAngle >= 360)
        startAngle = 0;
    update();
});

Car Dashboard Design

Initial Layout

Use translation and radial gradients for base design:

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setBrush(Qt::black);
painter.drawRect(rect());
painter.translate(rect().center());

QRadialGradient radialGradient(QPoint(0, 0), 300);
radialGradient.setColorAt(0.0, QColor(255, 0, 0, 50));
radialGradient.setColorAt(1.0, QColor(255, 0, 0, 250));
QBrush brush(radialGradient);
painter.setBrush(brush);
painter.drawEllipse(QPoint(0, 0), 300, 300);
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(Qt::white, 3));
painter.drawEllipse(QPoint(0, 0), 60, 60);

Scale Markings

Use rotation and save/restore to manage coordinate systems:

painter.rotate(135);
painter.drawLine(300 - 20, 0, 300 - 3, 0);
painter.setFont(QFont("宋体", 18));
painter.drawText(300 - 50, 12, QString::number(0));

double angle = 270 * 1.0 / 50;
for(int i = 1; i <= 50; i++) {
    painter.rotate(angle);
    if(i % 10 == 0) {
        painter.drawLine(300 - 20, 0, 300 - 3, 0);
        if(135 + angle * i < 270) {
            painter.rotate(180);
            painter.drawText(-300 + 30, 12, QString::number(i));
            painter.rotate(180);
        } else {
            painter.drawText(300 - 65, 12, QString::number(i));
        }
    } else {
        painter.drawLine(300 - 8, 0, 300 - 3, 0);
    }
}

Pointer and Scan Area

Draw pointer and scan sector:

painter.restore();
painter.save();
painter.rotate(135 + spanAngle);
painter.drawLine(60, 0, 300 - 65, 0);

painter.restore();
painter.save();
QRect rect(-300 + 65, -300 + 65, 600 - 130, 600 - 130);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(255, 51, 183, 200));
painter.drawPie(rect, -135 * 16, -spanAngle * 16);

Animated Pointer

Animate pointer movement:

QTimer *timer = new QTimer(this);
timer->start(15);
spanAngle = 0;
direction = 1;

connect(timer, &QTimer::timeout, [=]() {
    if(direction == 1) {
        spanAngle++;
        if(spanAngle >= 273)
            direction = 0;
    } else {
        spanAngle--;
        if(spanAngle <= 0)
            direction = 1;
    }
    update();
});

Display speed:

painter.restore();
painter.setFont(QFont("宋体", 18));
painter.drawText(QRect(-60, -60, 120, 120), Qt::AlignCenter, QString::number(int(spanAngle / 5.4)));

Optimized Dashboard Implemantation

Member Variables

private:
    QTimer *timer;
    int startAngle;
    int spanAngle;
    int direction;
    int boardSpanAngle;
    double angle;

    void initCanvas(QPainter& painter);
    void drawCurrentSpeed(QPainter& painter);
    void drawScale(QPainter& painter, int radius);
    void drawScaleText(QPainter& painter, int radius);
    void drawSpeedLine(QPainter& painter, int length);
    void drawSpeedPie(QPainter& painter, int radius);
    void drawRedCircle(QPainter& painter, int radius);
    void drawBlackCircle(QPainter& painter, int radius);
    void drawOutShine(QPainter& painter, int radius);
    void drawLogo(QPainter& painter, int radius);

Constructor Initialization

timer = new QTimer(this);
timer->start(1);
connect(timer, SIGNAL(timeout()), this, SLOT(timeOutHandler()));

startAngle = 150;
spanAngle = 0;
direction = 1;
boardSpanAngle = 240;
angle = boardSpanAngle * 1.0 / 60;
setFixedSize(800, 600);

Canvas Setup

void Widget::initCanvas(QPainter &painter) {
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setBrush(Qt::black);
    painter.drawRect(rect());
    QPoint center(width() / 2, height() * 0.6);
    painter.translate(center);
}

Scale Drawing

void Widget::drawScale(QPainter &painter, int radius) {
    painter.save();
    painter.rotate(startAngle);

    for(int i = 0; i <= 60; i++) {
        if(i % 5 == 0) {
            painter.setPen(QPen(Qt::white, 4));
            if(i >= 40)
                painter.setPen(QPen(Qt::red, 4));
            painter.drawLine(radius - 20, 0, radius - 3, 0);
        } else {
            painter.setPen(QPen(Qt::white, 3.5));
            if(i >= 40)
                painter.setPen(QPen(Qt::red, 4));
            painter.drawLine(radius - 8, 0, radius - 3, 0);
        }
        painter.rotate(angle);
    }
}

Scale Labels

void Widget::drawScaleText(QPainter &painter, int radius) {
    painter.restore();
    painter.save();
    int R = radius - 42;

    painter.setPen(Qt::white);
    QFont font("Arial", 13);
    font.setBold(true);
    painter.setFont(font);

    for(int i = 0; i <= 60; i++) {
        if(i % 5 == 0) {
            painter.save();
            int delx = qCos(qDegreesToRadians(210 - angle * i)) * R;
            int dely = qSin(qDegreesToRadians(210 - angle * i)) * R;
            painter.translate(QPoint(delx, -dely));
            painter.rotate(-120 + angle * i);
            painter.drawText(QRect(-30, -30, 60, 60), Qt::AlignCenter, QString::number(i * 4));
            painter.restore();
        }
    }
}

Speed Sector

void Widget::drawSpeedPie(QPainter &painter, int radius) {
    painter.restore();
    QRect rect(-radius, -radius, radius * 2, radius * 2);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(255, 0, 0, 80));
    painter.drawPie(rect, -startAngle * 16, -spanAngle * 16);
}

Red Gradient Circle

void Widget::drawRedCircle(QPainter &painter, int radius) {
    QRadialGradient radGradient(0, 0, radius);
    radGradient.setColorAt(0, QColor(255, 0, 0, 250));
    radGradient.setColorAt(1, QColor(0, 0, 0, 50));
    painter.setBrush(radGradient);
    painter.setPen(Qt::NoPen);
    painter.drawEllipse(QPoint(0, 0), radius, radius);
}

Timer Handler

void Widget::timeOutHandler() {
    if(direction == 1) {
        spanAngle++;
        if(spanAngle >= boardSpanAngle + 1)
            direction = 0;
    } else {
        spanAngle--;
        if(spanAngle <= 0)
            direction = 1;
    }
    update();
}

Black Circle

void Widget::drawBlackCircle(QPainter &painter, int radius) {
    painter.setBrush(Qt::black);
    painter.setPen(Qt::NoPen);
    painter.drawEllipse(QPoint(0, 0), radius, radius);
}

Speed Display

void Widget::drawCurrentSpeed(QPainter &painter) {
    painter.setPen(Qt::white);
    QFont font("Arial", 26);
    font.setBold(true);
    painter.setFont(font);
    painter.drawText(QRect(-60, -60, 120, 80), Qt::AlignCenter, QString::number(spanAngle));
    QFont font2("Arial", 12);
    font2.setBold(true);
    painter.setFont(font2);
    painter.drawText(QRect(-60, -60, 120, 180), Qt::AlignCenter, "Km/h");
}

Outer Glow

void Widget::drawOutShine(QPainter &painter, int radius) {
    QRect rect(-radius, -radius, radius * 2, radius * 2);
    painter.setPen(Qt::NoPen);
    QRadialGradient radGradient(0, 0, radius);
    radGradient.setColorAt(0, QColor(255, 0, 0, 0));
    radGradient.setColorAt(0.91, QColor(255, 0, 0, 0));
    radGradient.setColorAt(0.96, QColor(255, 0, 0, 120));
    radGradient.setColorAt(1, QColor(255, 0, 0, 255));
    painter.setBrush(radGradient);
    painter.drawPie(rect, -startAngle * 16, -60 * angle * 16);
}

Logo Display

void Widget::drawLogo(QPainter &painter, int radius) {
    QRect rect(-100, radius * 0.38, 200, 60);
    painter.drawPixmap(rect, QPixmap(":/icon.png"));
}

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.