本系列所有文章可以在这里查看[http://blog.csdn.net/cloud_castle/article/category/2123873](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. ![](https://box.kancloud.cn/2016-01-18_569cbd013a69c.jpg) 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文件](http://blog.csdn.net/dbzhang800/article/details/6348432)> 那么 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 乱乱乱谈(一)](http://blog.csdn.net/dbzhang800/article/details/6758204)> 既然谈到了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的原因。 好,我们现在来理一下这个思路。QT += XXX 实际上是为我们指明了一个路径。如果我们没有在pro文件中添加 QT += network ,然后#include出现什么情况?有个波浪线出来了吧,提示是,“QTcpSocket:没有这个文件或目录”。这就是因为在Qt在include目录下找不到名为QTcpSocket的文件或者目录。 那好说啊,我们这样#include不就能找到了吗?是的,可以看到波浪线没了,说明这个头文件确实能被找到,但是别忘了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三兄弟的故事就先到这里啦~