9——Analog Clock Window Example
最后更新于:2022-04-01 07:21:24
本系列所有文章可以在这里查看[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://docs.gechiui.com/gc-content/uploads/sites/kancloud/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三兄弟的故事就先到这里啦~