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

news2024/12/24 21:41:41

文章目录

  • 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();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/857439.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.正则表达式

3.1什么是正则表达式 ●正则表达式( Regular Expression) 是用于匹配字符串中字符组合的模式。在JavaScript中&#xff0c; 正则表达式也是对象 ●通常用来查找、替换那些符合正则表达式的文本&#xff0c;许多语言都支持正则表达式 ●正则表达式在JavaScript中的使用场景: ➢…

平板触控笔买哪种好?便宜又好用的电容笔推荐

或许很多人会觉得&#xff0c;苹果原装的电容笔性能强悍&#xff0c;是无可替代的&#xff0c;但我觉得&#xff0c;还是要看我们的预算。苹果Pencil对绘画要求不高的用户来说&#xff0c;价格太高了&#xff0c;如果我们只是用来写东西的话&#xff0c;我们甚至可以选择平替电…

Windows MYSQL社区版8.1下载安装(MSI)

一、下载 官网链接&#xff08;MySQL :: Download MySQL Community Server&#xff09; 选择版本&#xff1a; 直接下载&#xff1a; 二、安装配置 双击下载好的mysql-8.1.0-winx64.msi&#xff1b;打开安装向导&#xff1b;进入安装页面后选择Custom自定义安装&#xff0c;点…

清除浮动(clearfix)是什么,如何实现?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 清除浮动是什么&#xff1f;⭐ 清除浮动的方法⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些…

【C++】职工管理系统

1.需求分析以及案例展示 2.创建项目 3.创建管理类 4.菜单功能 5.退出功能 6.创建职工类 #pragma once #include <iostream> #include <string> using namespace std;//职工抽象基类 class Worker { public://显示个人信息virtual void showInfo() 0;//获取岗位名称…

揭秘浏览器键入URI到页面显示的过程: 浏览器键入URI,到页面显示,中间发生什么?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

差分升级在物联网水表上的实现与应用(学习)

摘要 当越来越多的物联网水表加入抄表系统后&#xff0c;实现了水表数据的信息化&#xff0c;并且当水表终端需要技术更新时&#xff0c;通过网络方式来升级产品可以高效修复设备面临的问题&#xff0c;减少用户损失&#xff0c;降低维护成本&#xff0c;但同时也对有限的网络…

IDEA 设置字体大小无效

设置字体大小&#xff0c;一般都是从file>settings>editor>font>Size里设置&#xff0c;一般都有效。 但是&#xff0c;如果是更换了主体&#xff0c;则需要从主体颜色菜单那里这是&#xff0c;你看这个页面&#xff0c;上面黄色三角也提示你了&#xff0c;要去颜色…

这所211热度很高!连续3年分数上涨!

一、学校及专业介绍 中国地质大学&#xff08;武汉&#xff09;&#xff08;China University of Geosciences, Wuhan&#xff09;&#xff0c;简称地大。位于武汉市&#xff0c;是中华人民共和国教育部直属的全国重点大学&#xff0c;由教育部和湖北省人民政府共建&#xff0…

告别if else!试试这款轻量级流程引擎吧,跟SpringBoot绝配!

之前同事用了一款轻量级的规则引擎脚本AviatorScript&#xff0c;我也跟着用了起来&#xff0c;真的挺香&#xff0c;能少写很多代码。这期就给大家介绍一下这款规则引擎。 简介 AviatorScript是一门高性能、轻量级寄宿于 JVM &#xff08;包括 Android 平台&#xff09;之上的…

java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver的解决办法

springcloudAlibaba项目连接mysql时&#xff08;mysql版本8.0.31&#xff0c;Springboot2.2.2,spring cloud Hoxton.SR1,spring cloud alibaba 2.1.0.RELEASE&#xff09;&#xff0c;驱动名称报红&#xff0c;配置如下&#xff1a; 原因&#xff1a;引入的jdbc驱动包和使用的m…

uniapp实现自定义上传图片

目录 1、布局样式2、使用uniappAPI&#xff08;uni.chooseImage&#xff09;3、解决bug和添加功能3.1 bug13.2 bug23.3 bug33.4 点击图片进行预览3.5 删除图片 4、云函数上传到云存储 1、布局样式 在正式自定义上传前我们先将静态页面搭建好。 样式代码&#xff1a; <temp…

【面试题】这道面试题真的很变态吗?

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 最近帮公司招聘&#xff0c;主要负责一面&#xff0c;所以基本上问的基础多一点。但是我在问这样一道面试题的时候&#xff0c;很少有人答对。不少人觉得…

福布斯发布2023年云计算100强榜单,OpenAI排名第一

&#x1f989; AI新闻 &#x1f680; 福布斯发布2023年云计算100强榜单&#xff0c;OpenAI排名第一 摘要&#xff1a;福布斯发布《2023年云计算100强榜单》&#xff0c;OpenAI排名第一。榜单关注全球高成长性的云计算公司&#xff0c;特别注重AI领域的发展。今年有16家新上榜…

基于自适应曲线阈值和非局部稀疏正则化的压缩感知图像复原研究【自适应曲线阈值去除加性稳态白/有色高斯噪声】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

自带flash的浏览器,免安装

一. 内容简介 自带flash的浏览器&#xff0c;免安装 二. 软件环境 2.1 FlashBrowser_v1.0.5 2.2 安装包链接 链接&#xff1a;https://pan.baidu.com/s/1O8_uHBkDMAXnj0KCfj_Urw?pwd1234 提取码&#xff1a;1234 三.主要流程 3.1 下载安装包 3.2 运行 点击CefFlashBro…

怎么裁剪视频大小尺寸?简单的裁剪方法分享

怎么裁剪视频的画面大小尺寸呢&#xff1f;有时当我们下载下来一段视频&#xff0c;由于视频的画面大小比例不同&#xff0c;会有很多的黑边&#xff0c;我们不管是观看还是进行二次编辑都非常影响体验&#xff0c;而调整视频画面比例以适应观众的设备或平台&#xff0c;比如将…

dotNet 之数据库sqlite

Sqlite3是个特别好的本地数据库&#xff0c;体积小&#xff0c;无需安装&#xff0c;是写小控制台程序最佳数据库。NET Core是同样也是.NET 未来的方向。 **硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 D…

iview 日期 datetimerange

问题&#xff1a;每次点击编辑按钮进入到编辑页面&#xff0c;活动时间明明有值&#xff0c;却还是提示请选择活动时间。 原因&#xff1a;值没绑定上 解决办法&#xff1a;v-model 修改为 :value <Form-item label"活动时间" prop"timeRange"><d…

谷粒商城第九天-对商品服务中所涉及到的表的思考

目录 一、总述 二、spu、sku、规格参数、销售属性之间的关系理解 三、相关表设计 1. 属性表 2. 基本属性分组表 3. 分组-基本属性关系表 ​4. spu基本属性值表 5. spu详情信息表 6. spu评论表 7. 商品sku值表 8. sku详情表 9. sku图片表 10. 分类表 11. 品牌表 …