QT5.14.2自带Examples:Analog Clock Window

Analog Clock Window 示例

Analog Clock Window 示例展示了如何在一个自定义的window上绘制内容。
这个例子使用了QPainter的位移、缩放、旋转特性,这些功能都是对计算机图形学算法的封装,相关的矩阵对用户来说都是透明的,使用起来非常方便。

概述

本示例展示了如何使用QPainter在QWindows绘制一个时钟。时钟的大小会随着窗口大小的变化而自动匹配。本示例是基于另一个GUI 示例基础之上完成的。所以这里我们一共会讲两个示例程序。Analog Clock Window Example Raster Window Example ,如下图所示(注意颜色的匹配):
QT5.14.2自带Examples:Analog Clock Window_第1张图片
简单来说Raster Window 示例展示的是使用QPainter在QWindow是进行最基础的静态的绘制。而Analog Clock Window 示例主要展示的是图元的绘制,以及动态的更新。
如上图所示,蓝色部分都是一样的,是两个项目的公用部分。可以看出,需要了解Analog Clock Window示例,需要先了解Raster Window示例。那下面我们先介绍一下Raster Window 示例。

Raster Window 示例

该示例展示如何使用QPainter在QWindows上进行简单的绘制。

RasterWindow 声明

//土豪式头文件加入法。也可以细一点。
//还有一种更土豪的写法,包含Qt Core, Qt GUI 和 Qt Widgets模块,但需要记得在pro文件中加入支持,例如:QT += widgets
#include 

class RasterWindow : public QWindow
{
     
    Q_OBJECT
public:
    explicit RasterWindow(QWindow *parent = 0);
	//renderNow绘制流程的一个步骤
    virtual void render(QPainter *painter);

public slots:
	//在本示例中没有用到,放到Analog Clock Window 再讲
    void renderLater();
    //执行绘制流程
    void renderNow();

protected:
    //配合renderLater()使用,本例中也没有用到。
    bool event(QEvent *event) override;
	
    void resizeEvent(QResizeEvent *event) override;
    void exposeEvent(QExposeEvent *event) override;

private:
//QBackingStore是QT5新加入的类。有了他,Qt就能像OpenGL一样在窗口上进行灵活的绘制。
//用于管理窗口的back buffer(前后缓冲,交替显示,以保证呈现的是整幅画面,而不是绘制过程)
//绘制中的缓冲就是back buffer,显示中的就是front buffer。枚帧进行一次swap
//QBackingStore使用的是栅格化方式(RasterSurface)
//栅格这个词在地图纹理中经常出现,可以百度一下细节。简单来说就是把图片按色块组织起来。
//栅格图又称位图,放大会失真(例如:地图纹理)。
//矢量图基于数据绘制,缩放后会根据数据重新绘制,不会失真(例如:字体,对的字体也是图)
    QBackingStore *m_backingStore;
};

RasterWindow 实现

#include "rasterwindow.h"
//在构造函数中创建backingstore,并将它需要管理的对象实例传给它。
RasterWindow::RasterWindow(QWindow *parent)
    : QWindow(parent)
    , m_backingStore(new QBackingStore(this))
{
     
	//(int posx, int posy, int w, int h)
    setGeometry(100, 100, 300, 200);
}

 //负责处理更新事件。
bool RasterWindow::event(QEvent *event)
{
     
    if (event->type() == QEvent::UpdateRequest) {
     
        renderNow();
        return true;
    }
    return QWindow::event(event);
}
//有些情况我们并不需要立即绘制。
void RasterWindow::renderLater()
{
     
	//QWindow::requestUpdate(),告诉系统,窗口需要更新,让系统自己决定何时更新。
	//会产生一个QEvent::UpdateRequest事件
    requestUpdate();
}

void RasterWindow::resizeEvent(QResizeEvent *resizeEvent)
{
     
	//确保窗口的back buffer大小与窗口一致
    m_backingStore->resize(resizeEvent->size());
}
//当窗口的显示状态发生变化的时候,更新绘制。
void RasterWindow::exposeEvent(QExposeEvent *)
{
     
	//只有在没有遮挡的情况下才需要重新绘制。
    if (isExposed())
        renderNow();
}

void RasterWindow::renderNow()
{
     
	//如果有遮挡则直接返回
    if (!isExposed())
        return;
	//(int posx, int posy, int w, int h)
    QRect rect(0, 0, width(), height());
    m_backingStore->beginPaint(rect);
	//QPainter是一个画家,QPaintDevice是画布。
    QPaintDevice *device = m_backingStore->paintDevice();
    QPainter painter(device);
	//通过改变fillRect的值可以绘制不同的区域。
	//fillRect(50, 50, width()-100, height()-100, QGradient::NightFade);外围的50不会被绘制。
    painter.fillRect(0, 0, width(), height(), QGradient::NightFade);
    //软件设计者的结构设计,RasterWindow只绘制背景,以及创建并设置一个QPainter对象
    //具体业务的绘制,放到其他函数。这里render使用这里创建好的Painter绘制了一个文字
    render(&painter);
    painter.end();

    m_backingStore->endPaint();
    m_backingStore->flush(rect);
}
//render被renderNow调用,可以被子类重载,实现模板方法设计模式
//模板方法设计模式:在一个固定的流程里(renderNow是稳定的部分,render是变化的部分)。
//RasterWindow作为主类,并不知道子类需要绘制什么内容,所以使用虚函数render,让子类重载
void RasterWindow::render(QPainter *painter)
{
     
    painter->drawText(QRectF(0, 0, width(), height()),
    			Qt::AlignCenter, QStringLiteral("QWindow"));//QStringLiteral更高效的QString宏
}

这里采用的是模板方法设计模式,RasterWindow::renderNow不但是完成里一些基础的工作,还负责了整个绘制工作的流程,也就是建立了骨架,并将一些具体的步骤延迟到了子类来实现(晚绑定)。
QT5.14.2自带Examples:Analog Clock Window_第2张图片

回到Analog Clock Window 示例

基于Raster Window 示例中的RasterWindow类,创建AnalogClockWindow类。重载 render 函数,绘制时钟表面。从这里就能看出软件设计者的用心了,render只负责具体业务的绘制,背景和准备工作在RasterWindow::renderNow中完成。我们重载的AnalogClockWindow;:render会被RasterWindow::renderNow函数调用。类图如下所示:
QT5.14.2自带Examples:Analog Clock Window_第3张图片

AnalogClockWindow 类声明

#include 

#include "rasterwindow.h"
class AnalogClockWindow : public RasterWindow
{
     
public:
    AnalogClockWindow();

protected:
    void timerEvent(QTimerEvent *) override;
    void render(QPainter *p) override;

private:
    int m_timerId;
};

AnalogClockWindow 类实现

AnalogClockWindow::AnalogClockWindow()
{
     
    setTitle("Analog Clock");
    resize(200, 200);
	//启动一个定时器,每秒钟发出一个timerEvent。m_timerId为int类型,是该定时器的ID
    m_timerId = startTimer(1000);
}

void AnalogClockWindow::timerEvent(QTimerEvent *event)
{
     
	//m_timerId定时器开启后,每秒会进入一次。
    if (event->timerId() == m_timerId)
    	//renderLater调用QWindow::requestUpdate(),告诉系统,窗口需要更新,但无需立即更新。
        renderLater();
}
//模板方法设计模式中的变化步骤。
void AnalogClockWindow::render(QPainter *p)
{
     
	//三角形的三个点坐标数据,目前原点还在左上角。后面会移动到窗口中心。
	//注意QWindow坐标里Y轴是反的,负数表示朝上。
    static const QPoint hourHand[3] = {
     
        QPoint(7, 8),
        QPoint(-7, 8),
        QPoint(0, -40)
    };
    static const QPoint minuteHand[3] = {
     
        QPoint(7, 8),
        QPoint(-7, 8),
        QPoint(0, -70)
    };

    QColor hourColor(127, 0, 127);
    QColor minuteColor(0, 127, 127, 191);
	//开启抗锯齿。
    p->setRenderHint(QPainter::Antialiasing);
	//所有的点,都进行一次位移。也就是将整个图片的原点从左上角,移动到中心。
    p->translate(width() / 2, height() / 2);
	//根据窗口大小缩放。图形缩放都是对点的操作,背后都有矩阵参与计算。
    int side = qMin(width(), height());
    p->scale(side / 200.0, side / 200.0);
	//不需要边框,只需要填充。
    p->setPen(Qt::NoPen);
    p->setBrush(hourColor);
	
    QTime time = QTime::currentTime();
	
    p->save();//保存当前上下文
    //根据时间值,计算旋转的度数。
    p->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    p->drawConvexPolygon(hourHand, 3);
    p->restore();//回到保存的上下文

    p->setPen(hourColor);
	//小时的刻度线
    for (int i = 0; i < 12; ++i) {
     
        p->drawLine(88, 0, 96, 0);
        p->rotate(30.0);
    }
    
    p->setPen(Qt::NoPen);
    p->setBrush(minuteColor);

    p->save();
    p->rotate(6.0 * (time.minute() + time.second() / 60.0));
    p->drawConvexPolygon(minuteHand, 3);
    p->restore();

    p->setPen(minuteColor);
	//分钟的刻度线
    for (int j = 0; j < 60; ++j) {
     
        if ((j % 5) != 0)
            p->drawLine(92, 0, 96, 0);
        p->rotate(6.0);
    }
}

QT5.14.2自带Examples:Analog Clock Window_第4张图片

加上秒针

在render函数中,按同样的方法设置顶点,颜色。添加旋转代码即可。
QT5.14.2自带Examples:Analog Clock Window_第5张图片

你可能感兴趣的:(QT5.14.2自带Examples:Analog Clock Window)