QPainter - 使用一个时钟项目从头开始看QPainter

简介: QPainter - 使用一个时钟项目从头开始看QPainter

QPainter - 使用一个时钟项目从头开始看QPainter


之前一直在说绘制,但是没有从头详细的去了解绘制这块的写法,因此我们来使用一个时钟的项目来学习一下绘制


先上个图:


绘制的原理


clockpainter

#ifndef PAINTERTEST_CLOCKPAINTER_H
#define PAINTERTEST_CLOCKPAINTER_H

#include <QTimer>
#include <QWidget>
#include <QColor>

class ClockPainter : public QWidget
{
    Q_OBJECT
public:
    explicit ClockPainter(QWidget *parent = nullptr);
    ~ClockPainter() override;

protected:
    void paintEvent(QPaintEvent *event) override;

private:

};


#endif //PAINTERTEST_CLOCKPAINTER_H

#include "clockpainter.h"
#include <QPainter>
ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
{

}
ClockPainter::~ClockPainter()
{

}
void ClockPainter::paintEvent(QPaintEvent *event)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height);

    QPainter painter(this);
    // 抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制原点移动到界面的中心点
    painter.translate(width / 2, height / 2);
    // 用(sx, sy)对坐标系进行缩放。(计算的比例使得缩放在一个合适的范围)
    painter.scale(side / 200.0, side / 200.0);
    // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。
    // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数
    painter.save();
    QPen pen;
    pen.setColor(Qt::red);
    pen.setWidth(5);
    painter.setPen(pen);
    // 顺时针旋转坐标系。给定的角度参数以度为单位。
    painter.translate(50, 50);
    // 顺时针旋转坐标系。给定的角度参数以度为单位。
    painter.rotate(90);
    // 绘制直线
    painter.drawLine(QPoint(0, 0), QPoint(0, 50));
    // 恢复当前painter状态(从堆栈中弹出已保存的状态)。
    painter.restore();

    QWidget::paintEvent(event);
}


从上面的例子我们可以看到QT整体的坐标系是怎么样的, (0,0)在左上角,+x轴是向左,+y轴是向下。


我们可以通过变换去改变这些位置。这个需要对线性代数有基本的了解,具体的可以参考下面这篇:

QT+ OpenGL 变换_turbolove的博客-CSDN博客


你可以尝试修改对应的顺序看看效果


备注:painter操作的是坐标系,这个我自己看感觉出来的


按照上面的思路,我们加一个计时器,并且使得每秒旋转6度,这样的话,我们的这个线一分钟可以转一圈


绘制时分秒指针


我们需要删除现有的绘制函数,然后添加新的函数来绘制对应的指针

void ClockPainter::drawHands(QPainter &painter)
{
    // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。
    // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数
    int secondHandR = radius_ - 20;
    int minuteHandR = radius_ - 40;
    int HourHandR = radius_ - 50;

    QTime current_time =QTime::currentTime();
    int hour = current_time.hour();     //当前的小时
    int minute = current_time.minute(); //当前的分
    int second = current_time.second(); //当前的秒

    QPolygon pts;
    painter.save();
    pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR);
    painter.setBrush(Qt::red);
    painter.setPen(Qt::NoPen);
    painter.rotate(hour * 30 + float(minute / 60.0) * 30.0);
    painter.drawPolygon(pts);
    painter.restore();

    painter.save();
    pts.setPoints(4, -2, 0, 0, 7, 2, 0, 0, -minuteHandR);
    painter.setBrush(Qt::blue);
    painter.setPen(Qt::NoPen);
    painter.rotate(minute * 6 + float(second / 60.0) * 6.0);
    painter.drawPolygon(pts);
    painter.restore();

    painter.save();
    pts.setPoints(4, -1, 0, 0, 9, 1, 0, 0, -secondHandR);
    painter.setBrush(Qt::black);
    painter.setPen(Qt::NoPen);
    painter.rotate(second * 6);
    painter.drawPolygon(pts);
    painter.restore();
}

解释一下主要的函数


QPolygon pts; 定义的是多边形的数据结构;


pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR); 第一个是点的个数,后面依次是对应点的xy值,使用这个可以绘制出自己想要的指针样式;


painter.setBrush(Qt::blue); 这里是设置的填充色, setPen设置的是边框颜色。


painter.rotate 上面介绍过了,这里是获取的旋转角度。


绘制背景


这里本来应该先绘制背景的,我们调用的时候也是绘制的背景先。但是我在写代码的时候是先写的指针看效果,然后给对应的指针加背景的,我这里是按照这个顺序来写的博客。


我们新增函数来绘制背景

void ClockPainter::drawBackground(QPainter &painter)
{
    // 3.绘制外部的黑色的圈
    int r = radius_ * 0.9;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();
    // 2.绘制外部的颜色
    r = radius_*0.88;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::white);//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();

    // 1.绘制指针背景色
    r = radius_*0.1;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(200, 200, 200));//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();
}

这里我们绘制一个圆和2个圆环,圆是给指针中心做背景的,第一个圆环覆盖所有的外框,第二个圆环显示时间值,思路是这样,但是绘制的时候需要反着绘制,先绘制大的,然后使用小的覆盖大的


绘制刻度线


接下来我们来绘制刻度线,刻度线没啥,就直接循环绘制即可

void ClockPainter::drawScale(QPainter &painter)
{
    double radius = radius_*0.88;
    painter.save();
    int h = 0;
    for(int i = 0; i < 60; i++)
    {
        if(i % 5 == 0)
        {
            QPen pen;
            pen.setWidthF(1.5);
            painter.setPen(pen);
            painter.drawLine(0,  - radius + 8, 0, -radius);

            QFontMetrics fm(painter.font());
            QString text;
            h == 0? text = "12":text = QString::number(h);
            h++;
            int width = fm.width(text);
            int height = fm.height();
            painter.drawText(-width / 2.0,  - radius + 8 + height, text);
        }
        else
        {
            QPen pen;
            pen.setWidthF(0.8);
            painter.setPen(pen);
            painter.drawLine(0,  - radius + 3, 0, -radius);
        }

        painter.rotate(6);
    }
    painter.restore();
}

我这个是效果简单的,但是万丈高楼平地起,如果你想写复杂的,需要先学会简单的。


完整代码

#ifndef PAINTERTEST_CLOCKPAINTER_H
#define PAINTERTEST_CLOCKPAINTER_H

#include <QTimer>
#include <QWidget>
#include <QColor>

class ClockPainter : public QWidget
{
    Q_OBJECT
public:
    explicit ClockPainter(QWidget *parent = nullptr);
    ~ClockPainter() override;

protected:
    void paintEvent(QPaintEvent *event) override;
    void drawHands(QPainter &painter);
    void drawBackground(QPainter &painter);
    void drawScale(QPainter &painter);

private:
    int radius_{100};
    QTimer timer_;

};


#endif //PAINTERTEST_CLOCKPAINTER_H



#include "clockpainter.h"
#include <QPainter>
#include <QTimer>
#include <QTime>

int degree = 0;
ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
{
    connect(&timer_, &QTimer::timeout, [&](){
        update();
    });
    timer_.start(1000);
}

ClockPainter::~ClockPainter()
{

}

void ClockPainter::paintEvent(QPaintEvent *event)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height);

    QPainter painter(this);
    // 抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制原点移动到界面的中心点
    painter.translate(width / 2, height / 2);
    // 用(sx, sy)对坐标系进行缩放。(计算的比例使得缩放在一个合适的范围)
    painter.scale(side / 200.0, side / 200.0);
    drawBackground(painter);
    drawScale(painter);
    drawHands(painter);
}

void ClockPainter::drawHands(QPainter &painter)
{
    // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。
    // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数
    int secondHandR = radius_ - 20;
    int minuteHandR = radius_ - 40;
    int HourHandR = radius_ - 50;

    QTime current_time =QTime::currentTime();
    int hour = current_time.hour();     //当前的小时
    int minute = current_time.minute(); //当前的分
    int second = current_time.second(); //当前的秒

    QPolygon pts;
    painter.save();
    pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR);
    painter.setBrush(Qt::red);
    painter.setPen(Qt::NoPen);
    painter.rotate(hour * 30 + float(minute / 60.0) * 30.0);
    painter.drawPolygon(pts);
    painter.restore();

    painter.save();
    pts.setPoints(4, -2, 0, 0, 7, 2, 0, 0, -minuteHandR);
    painter.setBrush(Qt::blue);
    painter.setPen(Qt::NoPen);
    painter.rotate(minute * 6 + float(second / 60.0) * 6.0);
    painter.drawPolygon(pts);
    painter.restore();

    painter.save();
    pts.setPoints(4, -1, 0, 0, 9, 1, 0, 0, -secondHandR);
    painter.setBrush(Qt::black);
    painter.setPen(Qt::NoPen);
    painter.rotate(second * 6);
    painter.drawPolygon(pts);
    painter.restore();
}

void ClockPainter::drawBackground(QPainter &painter)
{
    // 这里我们绘制一个圆和2个圆环,圆是给指针中心做背景的,第一个圆环覆盖所有的外框,第二个圆环显示时间值,思路是这样,但是绘制的时候需要反着绘制,先绘制大的,然后使用小的覆盖大的
    // 3.绘制外部的黑色的圈
    int r = radius_ * 0.9;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();
    // 2.绘制外部的颜色
    r = radius_*0.88;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::white);//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();

    // 1.绘制指针背景色
    r = radius_*0.1;
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(200, 200, 200));//外围
    painter.drawEllipse(-r, -r, r * 2, r * 2);
    painter.restore();
}

void ClockPainter::drawScale(QPainter &painter)
{
    double radius = radius_*0.88;
    painter.save();
    int h = 0;
    for(int i = 0; i < 60; i++)
    {
        if(i % 5 == 0)
        {
            QPen pen;
            pen.setWidthF(1.5);
            painter.setPen(pen);
            painter.drawLine(0,  - radius + 8, 0, -radius);

            QFontMetrics fm(painter.font());
            QString text;
            h == 0? text = "12":text = QString::number(h);
            h++;
            int width = fm.width(text);
            int height = fm.height();
            painter.drawText(-width / 2.0,  - radius + 8 + height, text);
        }
        else
        {
            QPen pen;
            pen.setWidthF(0.8);
            painter.setPen(pen);
            painter.drawLine(0,  - radius + 3, 0, -radius);
        }

        painter.rotate(6);
    }
    painter.restore();
}
目录
相关文章
|
6月前
QPainter - 八卦时钟
QPainter - 八卦时钟
43 0
|
5月前
|
编解码 并行计算 算法
MPI分形图像高精度绘制程序和PC端Mandelbrot-Julia分形集预览程序
这篇文章描述了一个使用2010年技术的集群程序,该程序基于Linux + MPI + C++或Windows + .NET + C#,用于并行计算生成高分辨率BMP图像,特别是Mandelbrot和Julia集。在8台节点上,程序实现了7.31的稳定加速比,并在更大规模任务中有望提升。它支持MPI并行计算、任务日志、不同阶数的分形集生成、批处理、多线程以及优化的颜色处理等功能。创新点包括颜色表的正弦控制、动态调整运算精度、复杂颜色生成、优化的颜色更新和并发机制等。程序产生的图像样本显示了其多样性和质量。作者提供源代码,并提到设计思路可应用于类似图像生成任务。
|
2月前
|
存储 Java C++
QT源码拾贝0-5(qimage和qpainter)
这篇文章介绍了在Qt源码中qimage和qpainter的使用,包括线程池的使用、智能指针的存储、std::exchange函数的应用、获取类对象的方法以及QChar字节操作。
QT源码拾贝0-5(qimage和qpainter)
|
API
28 QT - QPainter
28 QT - QPainter
46 0
QT(QPainter画圆弧)
QT(QPainter画圆弧)
243 0
|
Python
Julia:如何用 Plots 画多个子图
Plots 可以画出很多丰富的图。从画线、点、阴影填充都可以,但是在 Julia 上面,与 Python 上的 Matplotlib 的写法有很大的不同,这篇文章就是写一些基本的或者常用的用法,包括如何用 For 循环去画多个子图。
174 0
Julia:如何用 Plots 画多个子图
|
Kotlin
从自定义时钟⏰了解draw流程
今天继续说绘制三部曲之最后一曲——draw。
160 0
从自定义时钟⏰了解draw流程
|
异构计算 Windows
Substance 3D Painter因TDR问题渲染崩溃
解决因TDR问题导致渲染报错问题
1311 0
Substance 3D Painter因TDR问题渲染崩溃
|
网络协议
SAP GUI里Screen Painter的工作原理
SAP GUI里Screen Painter的工作原理
120 0
SAP GUI里Screen Painter的工作原理
Qt QPainter::end: Painter ended whith 2 saced states
在使用Qt QPainter 的时候,有时会遇到“QPainter::end: Painter ended whith 2 saced states”
223 0