Qt5官方demo解析集9——Analog Clock Window Example

本系列所有文章可以在这里查看http://blog.csdn.net/cloud_castle/article/category/2123873


这个例子可能是我们Clock系列的最后一个例程了,它又有什么特别之处呢?我们来看看吧~

The Analog Clock Window example shows how to draw the contents of a custom window.


This example demonstrates how the transformation and scaling features of QPainter can be used to make drawing easier.

我个人是不太喜欢翻译这两段英文,因为如果有翻译的话大家可能就不会去看这两段话了,可是我又不能保证把原文的意思都清晰地翻译出来,请原谅我一生放荡不羁语死早。。。


好了,回正题,这个例子其实挺有意思的,它使用了一个我们并不太常见的QWindow。了解它可能会使我们进一步了解Qt的窗口机制。来看源码~

这个例子用到了我们之前都没见过的.pri文件,这是个什么东西?让我们把.pro和.pri放在一起看:

analogclock.pro:

include(../rasterwindow/rasterwindow.pri)

# work-around for QTBUG-13496
CONFIG += no_batch

SOURCES += \
    main.cpp

target.path = $$[QT_INSTALL_EXAMPLES]/gui/analogclock
INSTALLS += target


rasterwindow.pri:

INCLUDEPATH += $$PWD
SOURCES += $$PWD/rasterwindow.cpp
HEADERS += $$PWD/rasterwindow.h
好像是第一次讲Qt的pro文件,其实我们很有必要理清这个小小的pro文件到底带给了我们什么东西,我们一句一句看:

首先,pri是什么,简单来说,可以理解为(project include),即包含工程。在做实际项目的时候,大多数情况下我们用到的类可能并不是自己写的,而是以打包(文件夹)的形式拿过来,并且,所有的文件都放在一个目录似乎也难以维护。那么我们创建一个pri文件,并在pro中include它就好了,而include(../rasterwindow/rasterwindow.pri)将被展开成pri文件中的内容。(../是什么?一个"."是当前目录,两个"."是上级目录)另外,Qt中还有.prf和.prl文件,具体可参考<浅谈 qmake 之 pro、pri、prf、prl文件>


那么$$PWD又是什么呢?在qmake工程文件中,我们可以利用$$var来取一个变量var的值,这里的PWD就是当前目录的绝对路径了。

OK,现在我们把.pri文件中的内容在.pro中展开,第二句我们又糊涂了,CONFIG += no_batch似乎是为了解决QTBUG-13496这个Bug存在的。那如果我们想知道这个Bug是什么,帖这段网址去看看https://bugreports.qt-project.org/browse/QTBUG-13496。Qt官方对这个Bug是这么描述的:nmake gives a wrong source file to the compiler

然后在下面一般会有合适的解决方案。


$$[QT_INSTALL_EXAMPLES],这里的QT_INSTALL_EXAMPLES是Qt配置选项的值,它不是一个变量,是你装好Qt以后就定好的一个值,这里是你Qt官方demo的顶层绝对路径。有关qmake的各项参数,具体可以参考<qmake 乱乱乱谈(一)>


既然谈到了pro文件,Qt += XXX不得不谈,既然要编译程序,无非就是预处理、编译和链接,编译预处理要展开我们的宏,展开包含的头文件,链接时呢,我们需要链接器能找到我们的库。

Qt在pro文件中实际上默认省略了两句话 CONFIG += qt 和 QT += core。第一句话告诉qmake,你可以到$QTDIR/include目录下去找头文件。当然,因为这个目录下都是文件夹,qmake找不到实际的头文件。然后它又告诉qmake,“我们的库文件路径在$QTDIR/lib里面~”。

第二句话QT += core就细分了,它告诉qmake,头文件路径可以向下一层,即$QTDIR/include/QtCore。链接需要的库呢,指定了一个QtCore4.lib。并定义宏QT_CORE_LIB

我们可以在QtCore文件夹里找到我们熟悉的QString,这就是我们在编写小程序用到QString却不需要#include<QString>的原因。


好,我们现在来理一下这个思路。QT += XXX 实际上是为我们指明了一个路径。如果我们没有在pro文件中添加 QT += network ,然后#include<QTcpSocket>出现什么情况?有个波浪线出来了吧,提示是,“QTcpSocket:没有这个文件或目录”。这就是因为在Qt在include目录下找不到名为QTcpSocket的文件或者目录。

那好说啊,我们这样#include<QNetwork/QTcpSocket>不就能找到了吗?是的,可以看到波浪线没了,说明这个头文件确实能被找到,但是别忘了QT += XXX还提供了我们在链接时需要用到的库。如果在单步编译这个Qt程序的话,可以看到编译过了,程序会在链接时报错。


好了,随便一讲讲了这么多,先来看RasterWindow类的实现。rasterwindow.h:

#ifndef RASTERWINDOW_H
#define RASTERWINDOW_H

//! [1]
#include <QtGui>

class RasterWindow : public QWindow
{
    Q_OBJECT
public:
    explicit RasterWindow(QWindow *parent = 0);

    virtual void render(QPainter *painter);  // 定义了一个需要子类继承的虚Render<span style="font-family: Arial, Helvetica, sans-serif;">(QPainter *painter)</span><span style="font-family: Arial, Helvetica, sans-serif;">函数用来进行绘图</span>

public slots:
    void renderLater();
    void renderNow();

protected:
    bool event(QEvent *event);             // 重写了三个事件

    void resizeEvent(QResizeEvent *event);
    void exposeEvent(QExposeEvent *event);

private:
    QBackingStore *m_backingStore;
    bool m_update_pending;                // 作为窗口更新的标志位
};
//! [1]
#endif // RASTERWINDOW_H

与往常不同的是,这个RasterWindow继承的是QWindow类,而不是我们熟悉的QWidget,它们有什么区别呢?大家都知道,QWidget及其众多的子类在Qt5中已经从QtGui模块中移除,成为了独立的QtWidgets,但是这个QWindow依然是属于QtGui模块的。也就是说,使用QWindow,我们不需要包含QtWidgets模块。

而QPainter也是被QtGui所包含的,因此我们仅包含QtGui就可以创建一个简单的窗口了。


QBackingStore类可以简单的理解为专为QWindow绘图所提供的类,同样属于QtGui。因此一般来说这个类是当我们想要使用QPainter但又不想使用OpenGL、QWidget、QGraphicsView带来额外开销的时候来使用的。


rasterwindow.cpp:

#include "rasterwindow.h"

//! [1]
RasterWindow::RasterWindow(QWindow *parent)
    : QWindow(parent)
    , m_update_pending(false)
{
    m_backingStore = new QBackingStore(this);
    create();                                // 通过平台资源创建一个窗口

    setGeometry(100, 100, 300, 200);  // 显示位置

}
//! [1]


//! [7]
bool RasterWindow::event(QEvent *event)
{
    if (event->type() == QEvent::UpdateRequest) {  // 当窗口需要重绘时触发
        m_update_pending = false;
        renderNow();
        return true;
    }
    return QWindow::event(event);
}
//! [7]

//! [6]
void RasterWindow::renderLater()  // 这个函数是被外部调用的
{
    if (!m_update_pending) {
        m_update_pending = true;
        QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); // 手动派发事件
    }
}
//! [6]


//! [5]
void RasterWindow::resizeEvent(QResizeEvent *resizeEvent)  // 仅当窗口isExposed时进行重绘
{
    m_backingStore->resize(resizeEvent->size());
    if (isExposed())
        renderNow();
}
//! [5]

//! [2]
void RasterWindow::exposeEvent(QExposeEvent *)  
{
    if (isExposed()) {
        renderNow();
    }
}
//! [2]


//! [3]
void RasterWindow::renderNow()
{
    if (!isExposed())
        return;

    QRect rect(0, 0, width(), height());
    m_backingStore->beginPaint(rect);  // 确定绘制区域

    QPaintDevice *device = m_backingStore->paintDevice();  // QBackingStore->QPaintDevice->QPainter
    QPainter painter(device);

    painter.fillRect(0, 0, width(), height(), Qt::white);  // 绘制了一个白色背景 
    render(&painter);                                

    m_backingStore->endPaint();
    m_backingStore->flush(rect);                      // 记得结束和刷新
}
//! [3]

//! [4]
void RasterWindow::render(QPainter *painter)  // 实现了自己的虚函数,因为被重写因此我们看不到这行QWindow
{
    painter->drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, QStringLiteral("QWindow"));
}
//! [4]

注意到倒数第三行的QStringLiteral,为什么不用QString?我们来看Manual怎么说的:

If you have code looking like:

if (node.hasAttribute("http-contents-length")) //...


One temporary QString will be created to be passed as the hasAttribute function parameter. This can be quite expensive, as it involves a memory allocation and the copy and the conversion of the data into QString's internal encoding.

This can be avoided by doing

if (node.hasAttribute(QStringLiteral("http-contents-length"))) //...


Then the QString's internal data will be generated at compile time and no conversion or allocation will occur at runtime
Using QStringLiteral instead of a double quoted ascii literal can significantly speed up creation of QString's from data known at compile time.

也就是说,如果直接“...”这种形式的常量字符,Qt将为它创建一个临时的QString对象,这需要申请内存,转换成QString的编码等等许多工作,而这对于一个常量而言代价是很大的。因此Qt提供了QStringLiteral的包装方式,它在编译期间就创建了这个QString的内部数据(也就是utf16字符串),从而在运行期不再需要申请内存和数据转换,并且相比前者也减少了编译时间。


最后来看main.cpp:
#include <QtGui>

#include "rasterwindow.h"

//! [5]
class AnalogClockWindow : public RasterWindow
{
public:
    AnalogClockWindow();

protected:
    void timerEvent(QTimerEvent *);  // 通过timerEvent,我们能够使用更多定时器的功能
    void render(QPainter *p);

private:
    int m_timerId;  // 这个数据成员用来存储定时器ID
};
//! [5]


//! [6]
AnalogClockWindow::AnalogClockWindow()
{
    setTitle("Analog Clock");
    resize(200, 200);

    m_timerId = startTimer(1000);  // 存储这个定时器ID
}
//! [6]

//! [7]
void AnalogClockWindow::timerEvent(QTimerEvent *event) // 当timeout时事件被触发
{
    if (event->timerId() == m_timerId)  // 确定事件来自这个定时器。尽管这里有点多余,但它是个好习惯。
        renderLater();                  // 提交update事件而不是强制重绘通常是种更好的选择
}
//! [7]

//! [1] //! [14]
void AnalogClockWindow::render(QPainter *p)  //<span style="font-family: 'Microsoft YaHei'; font-size: 20px; line-height: 30px;"> </span>绘图代码参见Analog Clock Example
{
//! [14]
//! [8]
    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);
//! [8]

//! [9]
    p->setRenderHint(QPainter::Antialiasing);
//! [9] //! [10]
    p->translate(width() / 2, height() / 2);

    int side = qMin(width(), height());
    p->scale(side / 200.0, side / 200.0);
//! [1] //! [10]

//! [11]
    p->setPen(Qt::NoPen);
    p->setBrush(hourColor);
//! [11]

//! [2]
    QTime time = QTime::currentTime();

    p->save();
    p->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    p->drawConvexPolygon(hourHand, 3);
    p->restore();
//! [2]

//! [12]
    p->setPen(hourColor);

    for (int i = 0; i < 12; ++i) {
        p->drawLine(88, 0, 96, 0);
        p->rotate(30.0);
    }
//! [12] //! [13]
    p->setPen(Qt::NoPen);
    p->setBrush(minuteColor);
//! [13]

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

//! [4]
    p->setPen(minuteColor);

    for (int j = 0; j < 60; ++j) {
        if ((j % 5) != 0)
            p->drawLine(92, 0, 96, 0);
        p->rotate(6.0);
    }
//! [4]
}

int main(int argc, char **argv)  
{
    QGuiApplication app(argc, argv);

    AnalogClockWindow clock;
    clock.show();

    app.exec();
}
ok,我们的Clock三兄弟的故事就先到这里啦~




  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值