本系列所有文章可以在這里查看[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.

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三兄弟的故事就先到這里啦~
- 前言
- 1——Fortune Server/Client
- 2——Multicast Sender/Receiverz
- 3——Broadcast Sender/Receiver
- 4——Blocking Fortune Client
- 5——Threaded Fortune Server
- 5(總結)——Fortune例程的各個實現區別
- 6——Loopback Example
- 7——Analog Clock Example
- 8——Shaped Clock Example
- 9——Analog Clock Window Example
- 10——Qt Quick Particles Examples - Emitters
- 11——Qt Quick Particles Examples - Affectors
- 12——Qt Quick Particles Examples - CustomParticles
- 13——Qt Quick Particles Examples - Image Particles
- 14——Qt Quick Particles Examples - System
- 15——Chapter 1: Creating a New Type
- 16——Chapter 2: Connecting to C++ Methods and Signals
- 17——Chapter 3: Adding Property Bindings
- 18——Chapter 4: Using Custom Property Types
- 19——Chapter 5: Using List Property Types
- 20——Chapter 6: Writing an Extension Plugin
- 21——Extending QML - Adding Types Example
- 22——Extending QML - Object and List Property Types Example
- 23——Extending QML - Inheritance and Coercion Example
- 24——Extending QML - Default Property Example
- 25——Extending QML - Methods Example
- 26——Extending QML - Grouped Properties Example
- 27——Extending QML - Attached Properties Example
- 28——Extending QML - Signal Support Example
- 29——Extending QML - Property Value Source Example
- 30——Extending QML - Binding Example
- 31——StocQt
- 32——Qt Quick Examples - Threading
- 33——Qt Quick Examples - Window and Screen
- 34——Concentric Circles Example
- 35——Music Player
- 36——Wiggly Example
- 37——Vector Deformation