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

来源:互联网 发布:战舰世界 科技树数据 编辑:程序博客网 时间:2024/05/18 04:56

本系列所有文章可以在这里查看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-13496CONFIG += no_batchSOURCES += \    main.cpptarget.path = $$[QT_INSTALL_EXAMPLES]/gui/analogclockINSTALLS += target


rasterwindow.pri:

INCLUDEPATH += $$PWDSOURCES += $$PWD/rasterwindow.cppHEADERS += $$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_OBJECTpublic:    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三兄弟的故事就先到这里啦~




0 0
原创粉丝点击