这个例子可能是我们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
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存在的。
$$[QT_INSTALL_EXAMPLES],这里的QT_INSTALL_EXAMPLES是Qt配置选项的值,它不是一个变量,是你装好Qt以后就定好的一个值,这里是你Qt官方demo的顶层绝对路径。有关$$,具体可以参考<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(QPainter *painter)函数用来进行绘图 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带来额外开销的时候来使用的。
#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]
#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) // 绘图代码参见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三兄弟的故事就先到这里啦~