#(24):Qt 繪制系統簡介
Qt 的繪圖系統允許使用相同的 API 在屏幕和其它打印設備上進行繪制。整個繪圖系統基于`QPainter`,`QPainterDevice`和`QPaintEngine`三個類。
`QPainter`用來執行繪制的操作;`QPaintDevice`是一個二維空間的抽象,這個二維空間允許`QPainter`在其上面進行繪制,也就是`QPainter`工作的空間;`QPaintEngine`提供了畫筆(`QPainter`)在不同的設備上進行繪制的統一的接口。`QPaintEngine`類應用于`QPainter`和`QPaintDevice`之間,通常對開發人員是透明的。除非你需要自定義一個設備,否則你是不需要關心`QPaintEngine`這個類的。我們可以把`QPainter`理解成畫筆;把`QPaintDevice`理解成使用畫筆的地方,比如紙張、屏幕等;而對于紙張、屏幕而言,肯定要使用不同的畫筆繪制,為了統一使用一種畫筆,我們設計了`QPaintEngine`類,這個類讓不同的紙張、屏幕都能使用一種畫筆。
下圖給出了這三個類之間的層次結構(出自 Qt API 文檔):
[](http://files.devbean.net/images/2012/10/paint-system.png)
上面的示意圖告訴我們,Qt 的繪圖系統實際上是,使用`QPainter`在`QPainterDevice`上進行繪制,它們之間使用`QPaintEngine`進行通訊(也就是翻譯`QPainter`的指令)。
下面我們通過一個實例來介紹`QPainter`的使用:
~~~
//!!! Qt4/Qt5
class PaintedWidget : public QWidget
{
Q_OBJECT
public:
PaintedWidget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
};
~~~
注意我們重寫了`QWidget`的`paintEvent()`函數。這或許是我們在理解了 Qt 事件系統之后首次實際應用。接下來就是`PaintedWidget`的源代碼:
~~~
//!!! Qt4/Qt5
PaintedWidget::PaintedWidget(QWidget *parent) :
QWidget(parent)
{
resize(800, 600);
setWindowTitle(tr("Paint Demo"));
}
void PaintedWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawLine(80, 100, 650, 500);
painter.setPen(Qt::red);
painter.drawRect(10, 10, 100, 400);
painter.setPen(QPen(Qt::green, 5));
painter.setBrush(Qt::blue);
painter.drawEllipse(50, 150, 400, 200);
}
~~~
在構造函數中,我們僅僅設置了窗口的大小和標題。而`paintEvent()`函數則是繪制的代碼。首先,我們在棧上創建了一個`QPainter`對象,也就是說,每次運行`paintEvent()`函數的時候,都會重建這個`QPainter`對象。注意,這一點可能會引發某些細節問題:由于我們每次重建`QPainter`,因此第一次運行時所設置的畫筆顏色、狀態等,第二次再進入這個函數時就會全部丟失。有時候我們希望保存畫筆狀態,就必須自己保存數據,否則的話則需要將`QPainter`作為類的成員變量。
`QPainter`接收一個`QPaintDevice`指針作為參數。`QPaintDevice`有很多子類,比如`QImage`,以及`QWidget`。注意回憶一下,`QPaintDevice`可以理解成要在哪里去繪制,而現在我們希望畫在這個組件,因此傳入的是 this 指針。
`QPainter`有很多以 draw 開頭的函數,用于各種圖形的繪制,比如這里的`drawLine()`,`drawRect()`以及`drawEllipse()`等。當繪制輪廓線時,使用`QPainter`的`pen()`屬性。比如,我們調用了`painter.setPen(Qt::red)`將 pen 設置為紅色,則下面繪制的矩形具有紅色的輪廓線。接下來,我們將 pen 修改為綠色,5 像素寬(`painter.setPen(QPen(Qt::green, 5))`),又設置了畫刷為藍色。這時候再調用 draw 函數,則是具有綠色 5 像素寬輪廓線、藍色填充的橢圓。
運行一下我們的程序,可以看到最終效果:
[](http://files.devbean.net/images/2012/10/paint-demo.png)
我們會在后面的章節詳細介紹畫筆`QPen`和畫刷`QBrush`的屬性。
另外要說明一點,請注意我們的繪制順序,首先是直線,然后是矩形,最后是橢圓。按照這樣的繪制順序,可以看到直線是第一個繪制,位于最下一層;矩形是第二個繪制,在中間一層;橢圓是最后繪制,在最上層。
如果了解 OpenGL,肯定聽說過這么一句話:OpenGL 是一個狀態機。所謂狀態機,就是說,OpenGL 保存的只是各種狀態。比如,將畫筆顏色設置成紅色,那么,除非你重新設置另外的顏色,它的顏色會一直是紅色。`QPainter`也是這樣,它的狀態不會自己恢復,除非你使用了各種設置函數。因此,如果在上面的代碼中,我們在橢圓繪制之后再畫一個矩形,它的樣式還會是綠色 5 像素的輪廓線以及藍色的填充,除非你顯式地調用了設置函數進行狀態的更新。這是大多數繪圖系統的實現方式,包括 OpenGL、`QPainter`以及 Java2D。正因為`QPainter`是一個狀態機,才會引出我們前面曾經介紹過的一個細節問題:由于`paintEvent()`是需要重復進入的,因此,需要注意第二次進入時,`QPainter`的狀態是不是和第一次一致,否則的話可能會造成閃爍的現象。這個閃爍并不是由于雙緩沖的問題,而是由于繪制狀態的快速切換。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)