簡介
這里簡單介紹Qt的一些核心機(jī)制,具體參見Qt文檔。
主要包含內(nèi)容:
- Qt的信號和槽击喂,以及事件機(jī)制
- Qt Object Model
- Qt Embedded for linux簡介
- 事件機(jī)制
- 顯示機(jī)制
- Qt的通信機(jī)制
- Qt的插件系統(tǒng)(機(jī)制)
- Qt內(nèi)存管理機(jī)制
- Qt的Model/View編程模式
- 繪制系統(tǒng)
具體如下算墨。
Qt的信號和槽绍坝,以及事件機(jī)制
信號和槽提供了一種在一個對象中赶熟,直接調(diào)用另一個對象任意成員函數(shù)的機(jī)制。類似回調(diào)陷嘴,但比直接調(diào)用回調(diào)函數(shù)靈活(例如會自動處理虛函數(shù)調(diào)用),相應(yīng)的調(diào)用的性能也有一定下降(開銷很小间坐,比new和delete操作小)灾挨。
Qt Object Model
需要注意兩點(diǎn):Qt對標(biāo)準(zhǔn)C++通過此模型進(jìn)行了一定的擴(kuò)展;Qt中對象的賦值和克隆完全不同竹宋,后者所做工作更多劳澄。
Meta-Object System
此特性通過Qt的moc工具,為每一個使用Qt特性的類生成一個moc對象來實現(xiàn)蜈七。它包含了Qt對C++的許多擴(kuò)展性能的處理和實現(xiàn)秒拔。如:
- 信號和槽的機(jī)制
- 動態(tài)添加類屬性的機(jī)制
- 不通過RTTI獲取類名的機(jī)制
- 獲取繼承關(guān)系的機(jī)制等。
使用此特性的方法很簡單飒硅,只需在相應(yīng)的Qt類中繼承QObject砂缩,并且在開始聲明Q_OBJECT宏。編譯時三娩,需要用moc生成相應(yīng)的moc對象實現(xiàn)的cpp文件庵芭,并鏈接;但是使用qmake工具的話雀监,會自動生成Makefile双吆,不用手動去做。
Qt Embedded for linux簡介
相對Qt的桌面程序会前,QTE自己提供了一個輕量級的窗口管理系統(tǒng)好乐,其應(yīng)用程序直接操作framebuffer,而不使用Xwindow系統(tǒng)這樣的桌面管理程序瓦宜,可以節(jié)省內(nèi)存蔚万。也可以使用VNC遠(yuǎn)程桌面控制協(xié)議,運(yùn)行應(yīng)用程序临庇。
(1)服務(wù)端和客戶端
QtEmbedded應(yīng)用啟動時笛坦,需要一個服務(wù)端,或者是一個已有的服務(wù)端苔巨,或者應(yīng)用程序本身作為一個服務(wù)端啟動版扩。任何一個QTE程序都可以成為服務(wù)端程序(通過啟動選項中的-qws指定,或者在編碼中指定)侄泽,啟動好一個服務(wù)端之后礁芦,后續(xù)的QTE程序都將作為客戶端的角色運(yùn)行。
服務(wù)端主要負(fù)責(zé)管理鼠標(biāo)鍵盤輸入、顯示輸出柿扣、屏幕保護(hù)以及光標(biāo)顯示等內(nèi)容(類似圖形系統(tǒng)中的桌面管理系統(tǒng))肖方,而客戶端則借助服務(wù)端提供的服務(wù),完成特定的應(yīng)用程序功能未状。所有系統(tǒng)產(chǎn)生的事件(例如鍵盤鼠標(biāo)事件)俯画,都會傳遞給服務(wù)端,然后分發(fā)給特定的客戶端處理司草。
運(yùn)行的應(yīng)用程序會不斷地通過添加和減少widgets來更改屏幕的外觀艰垂。服務(wù)端會在相應(yīng)的QWSWindow 類對象中維護(hù)沒一個頂層窗口的信息。當(dāng)服務(wù)端接受到事件埋虹,它會通過詢問它的頂層窗口棧猜憎,來找到包含事件位置的窗口。每個窗口又可以知道創(chuàng)建它自身的客戶應(yīng)用程序搔课。服務(wù)端會在最后將封裝成QWSEvent類對象的事件轉(zhuǎn)發(fā)給相應(yīng)的應(yīng)用程序(客戶端)胰柑。
輸入法使用一個介于服務(wù)端和客戶端的filter來實現(xiàn)。我們繼承QWSInputMethod來實現(xiàn)自定義的輸入法爬泥,再使用服務(wù)端的setCurrentInputMethod()來安裝它柬讨。另外,也可使用QWSServer::KeyboardFilter類來實現(xiàn)一個全局的底層的filter袍啡,對key events進(jìn)行過濾處理姐浮,這樣可以用于一些特殊目的,無需為所有應(yīng)用程序添加一個filter(例如通過一個按鈕進(jìn)行高級電源管理)葬馋。
(2)通信
server通過unix域套接字和client進(jìn)行通信卖鲤。客戶端和服務(wù)端通信時畴嘶,使用的是QCopChannel類蛋逾,QCOP是一個在不同channel可以進(jìn)行多對多通信的協(xié)議。一個channel通過一個名字標(biāo)識窗悯,任何程序都可以偵聽這個channel区匣,QCOP協(xié)議可以允許客戶端在相同地址空間也可在不同的進(jìn)程間通信。
(3)指針輸入
QTE服務(wù)端啟動時蒋院,使用QT的插件機(jī)制亏钩,將鼠標(biāo)驅(qū)動加載。鼠標(biāo)驅(qū)動接受設(shè)備產(chǎn)生的鼠標(biāo)事件欺旧,并將其封裝成 QWSEvent 類姑丑,傳遞給服務(wù)端。
QT默認(rèn)提供了一個鼠標(biāo)驅(qū)動辞友,我們可以繼承QWSMouseHandler實現(xiàn)自定義的鼠標(biāo)驅(qū)動栅哀。 QMouseDriverFactory默認(rèn)會在服務(wù)端運(yùn)行時震肮,自動檢測到該驅(qū)動,并將其加載留拾。
除通常的鼠標(biāo)輸入外戳晌,QTE提供了一個calibrated mouse handler。當(dāng)系統(tǒng)設(shè)備無法具有設(shè)備和屏幕的固定映射痴柔,以及有噪聲事件時(例如觸摸屏)沦偎,使用QWSCalibratedMouseHandler作為基類來實現(xiàn)使用模狭。
(4)字符輸入
QTE服務(wù)端啟動時蒙秒,使用QT的插件機(jī)制忍抽,將鍵盤驅(qū)動加載叉弦。鍵盤驅(qū)動接受設(shè)備產(chǎn)生的鍵盤事件,并將其封裝成 QWSEvent 類注盈,傳遞給服務(wù)端。
QT默認(rèn)提供了一個鍵盤驅(qū)動,我們可以繼承 QWSKeyboardHandler實現(xiàn)自定義的鍵盤驅(qū)動堆巧。QKbdDriverFactory默認(rèn)會在服務(wù)端運(yùn)行時,自動檢測到該驅(qū)動泼菌,并將其加載谍肤。
(5)顯示輸出
每個客戶端默認(rèn)會將它的widgets和decorations提交到內(nèi)存,同時服務(wù)端將相應(yīng)內(nèi)存拷貝到設(shè)備的framebuffer哗伯。
當(dāng)客戶端接收到一個可以更改它widgets的事件時荒揣,應(yīng)用程序就會更新它內(nèi)存緩存的相應(yīng)部分。
decoration在客戶應(yīng)用程序啟動時通過QT插件系統(tǒng)加載焊刹,可以通過繼承QDecoration來自定義一個decoration插件系任。默認(rèn)QDecorationFactory會自動檢測到這個插件并加載給應(yīng)用程序。我們可以用QApplication::qwsSetDecoration()函數(shù)來為應(yīng)用程序指定一個給定的decoration虐块。
事件機(jī)制
事件機(jī)制主要用于對Qt類的實現(xiàn)俩滥,與信號和槽的區(qū)別是它用于類自身使用而非為其它對象調(diào)用提供接口(例如在事件處理函數(shù)中發(fā)送信號)。由Qt自身的事件循環(huán)機(jī)制維護(hù)贺奠。
(a)事件循環(huán)
Qt程序啟動后霜旧,通過QApplication::exec()進(jìn)入事件循環(huán),對事件進(jìn)行派發(fā)處理儡率。
該循環(huán)大致如下:
while ( !app_exit_loop )
{
while( !postedEvents ) { processPostedEvents() }
while( !qwsEvnts ){ qwsProcessEvents(); }
while( !postedEvents ) { processPostedEvents() }
}
先處理Qt事件隊列中的事件, 直至為空. 再處理系統(tǒng)消息隊列中的消息, 直至為空, 在處理系統(tǒng)消息的時候會產(chǎn)生新的Qt事件, 需要對其再次進(jìn)行處理.
事件的派發(fā)處理通過QApplication::notify()進(jìn)行挂据。
調(diào)用QApplication::sendEvent的時候, 消息會立即被處理,是同步的. 實際上QApplication::sendEvent()是通過調(diào)用QApplication::notify(), 直接進(jìn)入了事件的派發(fā)和處理環(huán)節(jié).
(b)派發(fā)處理
假設(shè)Qt程序(QApplication)的QWidget發(fā)生事件QEvent,那么處理的次序是:
- QApplication::notify()對QEvent進(jìn)行派發(fā)
- 在QApplication::notify()中儿普,用安裝在QApplication上的事件過濾器處理
- 在QApplication::notify()中崎逃,調(diào)用QObject::event()對事件進(jìn)行處理
- 在QObject::event()中,用安裝在QWidget上的事件過濾器處理
- 在QObject::event()中眉孩,調(diào)用QWidget自己的XXXEvent函數(shù)進(jìn)行處理
(c)事件轉(zhuǎn)發(fā)
事件在QWidget中處理后婚脱,通過返回true或false來標(biāo)志是否處理完。處理完則不轉(zhuǎn)發(fā),否則向上依次轉(zhuǎn)發(fā)給父窗口障贸,直至被處理或到頂層窗口错森。
顯示機(jī)制
Qt默認(rèn)情況,客戶端提交顯示widgets的相關(guān)請求到內(nèi)存篮洁,服務(wù)端會遍歷所有客戶端的頂層窗口確認(rèn)顯示區(qū)域涩维,將所有客戶端的顯示相關(guān)請求從內(nèi)存中拷貝到屏幕上,期間袁波,會使用到Qt的screen驅(qū)動(screen驅(qū)動的加載涉及到Qt的插件機(jī)制)瓦阐。但是對于已知硬件信息的時候,客戶端可以直接來操作和控制硬件而不用借助服務(wù)端(這在嵌入式系統(tǒng)中也是很常見的)篷牌,后面會介紹兩種直接和硬件交互的方法睡蟋。另外,我們還可創(chuàng)建自己的顯示機(jī)制枷颊,充分利用硬件性能戳杀。
(1)關(guān)于screen驅(qū)動顯示
screen驅(qū)動會根據(jù)一個和顯示區(qū)域有交疊的所有頂層的窗口列表,來確認(rèn)更新顯示的內(nèi)存夭苗。每個頂層窗口都有一個QWSWindowSurface類來表示其繪制區(qū)域信卡,screendriver根據(jù)這些類對象來獲取相應(yīng)的內(nèi)存塊指針。最后题造,screen驅(qū)動會對這些內(nèi)存塊進(jìn)行合成傍菇,并將更新的顯示區(qū)域提交到framebuffer顯示出來。
Qt提供的顯示驅(qū)動
主要有:
- Linux framebuffer:直接在linux系統(tǒng)的framebuffer上進(jìn)行顯示相關(guān)操作界赔。
- the virtual framebuffer:通過qvfb程序模擬出虛擬的framebuffer設(shè)備丢习,在其中進(jìn)行顯示相關(guān)操作。
- transformed screens:和屏幕旋轉(zhuǎn)相關(guān)的操作淮悼。
- VNC servers:服務(wù)端會啟動一個小型的vnc服務(wù)泛领,網(wǎng)絡(luò)上的其它機(jī)器通過vnc方式訪問其內(nèi)容,服務(wù)端通過vnc進(jìn)行顯示敛惊。
- multi screens:同時支持多種顯示驅(qū)動的驅(qū)動渊鞋。
需在編譯QTE時,配置好需要使用的驅(qū)動瞧挤。
指定顯示驅(qū)動
可通過環(huán)境變量"QWS_DISPLAY"锡宋,或者命令行選項"-display"指定通過哪種驅(qū)動顯示。
例如:啟動服務(wù)后特恬,
-
通過環(huán)境變量:
#export QWS_DISPLAY="VNC:0" #myApplication&
-
通過命令行選項:
#myApplication -display "VNC:0"
兩種方式均表示使用vnc進(jìn)行顯示执俩,其它具體顯示參數(shù)需參見文檔。
插件自定義顯示驅(qū)動
我們可以通過繼承QScreen類以及創(chuàng)建一個繼承自QScreenDriverPlugin類的插件癌刽,來使用自己定義的顯示驅(qū)動插件役首。QScreenDriverFactory類默認(rèn)會自動檢測到這個插件尝丐,并在運(yùn)行時將它加載至服務(wù)程序。
(2)關(guān)于客戶端直接顯示
前面提到的衡奥,客戶端可以直接來操作和控制硬件而不用借助服務(wù)端來顯示的兩種方式:
第一種方式
設(shè)置 Qt::WA_PaintOnScreen
屬性(如果所有的widget都這樣顯示爹袁,我們可以設(shè)置環(huán)境變量 QT_ONSCREEN_PAINT
)。設(shè)置后矮固,應(yīng)用程序會直接將其widget顯示到屏幕上失息,并且其相關(guān)的顯示區(qū)域?qū)⒉粫籹creen驅(qū)動修改(除非有一個持有更高窗層焦點(diǎn)的程序在同樣的區(qū)域有提交窗口更新相關(guān)的請求)。
第二種方式
使用 QDirectPainter
档址。這樣可以完全控制一處預(yù)先保留的framebuffer區(qū)域(通過持有一塊framebuffer的指針)盹兢,screen驅(qū)動也再也無法修改這片區(qū)域了。但是如果當(dāng)前屏幕有子屏幕守伸,我們還是需要借助screen驅(qū)動的相關(guān)函數(shù)來獲取正確的屏幕绎秒,獲取當(dāng)前的屏幕,以及恢復(fù)之前的framebuffer指針尼摹。
(3)加速顯示
對QTE來說见芹,繪制顯示是一個純軟件實現(xiàn),為充分利用特殊硬件的顯示加速特性窘问,我們可以自己添加更高性能的圖形繪制驅(qū)動辆童。
前面說過宜咒,客戶端使用Qt的繪圖系統(tǒng)將每個窗口提交給一個window surface對象惠赫,然后將其保存到內(nèi)存,screen驅(qū)動會訪問這些內(nèi)存并將這些surface合并故黑,并顯示出來儿咱。
為了添加一個加速的圖形顯示驅(qū)動,我們需要自己創(chuàng)建一個screen场晶,一個圖形繪制引擎混埠,一個支持繪制引擎的圖形繪制設(shè)備,一個支持圖形設(shè)備的窗口的surface诗轻,并且使screen可以識別這個surface钳宪。具體需參見"accelerated graphics driver"的文檔。
Qt的通信機(jī)制
Qt為Qt應(yīng)用程序提供以下通信機(jī)制
(1)D-Bus
QtDBus模塊是一個unix庫扳炬,可以使用它基于D-Bus協(xié)議進(jìn)行通信吏颖。它將Qt的信號和槽的機(jī)制擴(kuò)展到IPC層,允許一個進(jìn)程的信號可以連接另外一個進(jìn)程的槽恨樟。
(2)TCP/IP
跨平臺的QtNetwork模塊提供的類便于網(wǎng)絡(luò)編程和移植半醉。它提供了高層類(如Http,QFtp)等,可以用于和特定應(yīng)用層協(xié)議通信劝术;也提供了低層類(如QTcpSocket,QTcpServer,QSslSocket)來實現(xiàn)協(xié)議缩多。
(3)Shared Memory
跨平臺的共享內(nèi)存類呆奕,QSharedMemory提供了訪問操作系統(tǒng)共享內(nèi)存的實現(xiàn)。它允許多線程和進(jìn)程安全的訪問共享內(nèi)存段衬吆。另外梁钾,QSystemSemaphore可以對系統(tǒng)共享資源的訪問以及進(jìn)程通信進(jìn)行控制。
(4)Qt COmmunications Protocol (QCOP)
QCopChannel類實現(xiàn)了客戶進(jìn)程通過有名channels傳輸消息的協(xié)議咆槽。它只能用于Qt for Embedded Linux陈轿,類似QtDBus,QCOP將Qt的信號和槽機(jī)制擴(kuò)展到IPC層次秦忿,允許一個進(jìn)程的信號可以連接另外一個進(jìn)程的槽麦射,但是與QtDBus不同的是QCOP不依賴第三方庫。
Qt的插件系統(tǒng)(機(jī)制)
Qt提供兩組API用于創(chuàng)建插件:
- 高層的API:用于擴(kuò)展Qt本身灯谣,比如自定義的數(shù)據(jù)庫驅(qū)動潜秋,文本解碼插件,風(fēng)格插件等胎许。
- 低層的API:用于擴(kuò)展應(yīng)用程序本身峻呛。
高層API實際建立在低層API之上。
1.擴(kuò)展Qt本身的高層插件
編寫一個用于擴(kuò)展Qt本身的高層插件辜窑,主要做的就是:繼承一個特定類型的插件基類钩述、實現(xiàn)一些函數(shù)、再增加一個宏穆碎。
編好的插件存放在特定的目錄下牙勘,Qt會自動找到并加載。此方式創(chuàng)建的插件類型固定所禀,每個類型對應(yīng)一個$QTDIR/plugins目錄下的子目錄(也是Qt插件系統(tǒng)自動搜索的路徑之一)方面,插件就存放于其中。
Qt加載插件的路徑搜索規(guī)則色徘,以及添加插件的方法恭金,具體請參見文檔。大致如下:
- 將當(dāng)前可執(zhí)行程序路徑作為插件搜索根目錄褂策,搜索特定類型的插件(如styles)横腿,可使用QCoreApplication::applicationDirPath()獲得此根路徑。
- 將QLibraryInfo::location(QLibraryInfo::PluginsPath)獲得的路徑作為插件搜索根目錄斤寂,搜索特定類型的插件(如styles)耿焊,一般為:QTDIR/plugins。
- 應(yīng)用程序可使用 QCoreApplication::addLibraryPath()追加額外的搜索路徑根目錄扬蕊。
- 編寫一個qt.conf來替換Qt內(nèi)部硬編碼后確定的路徑搀别,此文件存在于/qt/etc/qt.conf(根據(jù)系統(tǒng)有所不同),以及當(dāng)前程序執(zhí)行路徑尾抑。
- 另外歇父,啟動程序前蒂培,若指定 QT_PLUGIN_PATH,則使用此變量中的路徑來搜索插件榜苫。
每種類型的插件有其不同的實現(xiàn)規(guī)則护戳,基本上是繼承相應(yīng)的插件基類(如QStylePlugin),實現(xiàn)一些特定的函數(shù)垂睬,最后用Q_EXPORT_PLUGIN2宏進(jìn)行相應(yīng)聲明媳荒。
一般使用插件的方式是將其直接包含并編譯到應(yīng)用程序中,或者將其編譯成動態(tài)庫驹饺,并鏈接钳枕。如果想要讓插件可加載,那么就按照前面的規(guī)則赏壹,在搜索目錄的相應(yīng)位置為插件建立一個目錄鱼炒,并將插件拷貝進(jìn)去。
2.擴(kuò)展應(yīng)用程序本身的低層插件
不僅是Qt本身蝌借,Qt應(yīng)用程序也可通過插件擴(kuò)展昔瞧。應(yīng)用程序通過QPluginLoader來檢測和加載插件。應(yīng)用程序的插件不僅限于Qt插件的那幾種類型(如data base菩佑、style等)自晰,可以任意,較Qt的插件稍坯,靈活性更大酬荞。
創(chuàng)建一個應(yīng)用程序插件,大致包含下面的步驟:
- 聲明一個只包含純虛函數(shù)接口的類劣光,用于描述插件功能供插件實現(xiàn)袜蚕。
- 使用Q_DECLARE_INTERFACE()宏將上述接口通知給Qt的meta-object系統(tǒng)糟把。
- 在應(yīng)用程序中使用QPluginLoader來加載插件绢涡。
- 使用qobject_cast()檢測插件是否實現(xiàn)了指定接口。
編寫插件包含如下步驟:
- 聲明一個插件類遣疯,繼承自QObject和之前的接口類雄可。
- 使用Q_INTERFACES()宏將上述接口通知給Qt的meta-object系統(tǒng)。
- 使用Q_EXPORT_PLUGIN2()宏將插件導(dǎo)出缠犀。
- 使用合適的.pro文件編譯插件数苫。
3.插件加載與檢測
-
高(主和次)版本Qt編譯鏈接的插件,不能被低(主和次)版本Qt加載辨液。
例如: 4.5.3編譯的插件虐急,不能被4.5.0加載。
-
低主版本號Qt編譯鏈接的插件不能被高主版本號的Qt庫加載滔迈。
例如:
Qt 4.3.1 不會加載Qt 3.3.1編譯鏈接的插件止吁。
Qt 4.3.1 會加載Qt 4.3.0 and Qt 4.2.3編譯鏈接的插件被辑。
-
Qt庫和所有插件用一個聯(lián)編關(guān)鍵字來聯(lián)編。如果Qt庫的和插件的聯(lián)編關(guān)鍵字匹配則加載敬惦,否則不加載盼理。
編譯插件來擴(kuò)展應(yīng)用程序時,需確保插件和應(yīng)用程序用同樣的配置俄删。
如果應(yīng)用程序是release模式編譯的宏怔,那么插件也要是release模式。
若將Qt配置為debug和release模式都編譯畴椰,但只在release模式下編譯應(yīng)用程序臊诊,就要確保你的插件也是在release模式下編譯的。
缺省的斜脂,若Qt的debug編譯可用妨猩,插件就只在debug模式下編譯。要強(qiáng)制插件用release模式編譯秽褒,要在工程中添加:CONFIG += release
這能確保插件兼容應(yīng)用程序中所用的庫版本壶硅。更多內(nèi)容,參見官方文檔销斟。
注:個人理解庐椒,Qt驅(qū)動一般就是指Qt插件,其實現(xiàn)根據(jù)底層被操作設(shè)備而不同蚂踊,但對上提供統(tǒng)一的接口约谈。
Qt內(nèi)存管理機(jī)制
所謂Qt內(nèi)存管理機(jī)制,是一種半自動的垃圾回收機(jī)制犁钟,所有繼承于QObject的類棱诱,并設(shè)置了parent(在構(gòu)造時,或用setParent函數(shù)涝动,或parent的addChild相關(guān)信息)迈勋,在parent被delete時,這個parent的相關(guān)所有child都會自動delete醋粟,不用用戶手動處理靡菇。
程序通常最上層會有一個根的QOBJECT,就是放在setCentralWidget()中的那個QOBJECT米愿,這個QOBJECT在 new的時候不必指定它的父親厦凤,因為這個語句將設(shè)定它的父親為總的QAPPLICATION,當(dāng)整個QAPPLICATION沒有時它就自動清理育苟,所以也無需清理(這里QT4和QT3有不同较鼓,QT3中用的是setmainwidget函數(shù),但是這個函數(shù)不作為里面QOBJECT的父親违柏,所以QT3中這個頂層的QOBJECT要自行銷毀)博烂。
我們需要注意如下容易出錯的三種情況:
(1)child被單獨(dú)釋放
parent是用一個數(shù)組來保存childs的指針的拓哺,當(dāng)一個child被銷毀時,parent會知道的脖母。child的析構(gòu)函數(shù)會調(diào)用parent并把parent的指針數(shù)據(jù)中自己對數(shù)的值改為0士鸥,那么最后是0的指不管多少次都無所謂了。
但是當(dāng)一個QOBJECT正在接受事件隊列中途就被你DELETE掉了谆级,會出現(xiàn)問題烤礁,所以QT中建議不要直接DELETE掉一個 QOBJECT,如果一定要這樣做肥照,要使用QOBJECT的deleteLater()函數(shù)脚仔,它會讓所有事件都發(fā)送完一切處理好后馬上清除這片內(nèi)存,而且就算調(diào)用多次的deleteLater也不會有問題(具體可查看deleteLater在api文檔中的解釋)舆绎。
(2)非new出來的child的釋放
parent不區(qū)別它的child是不是new出來的鲤脏,只要是它的child,它在銷毀時就直接delete吕朵。因此如下代碼是有錯誤的:
{
QObject*parent=new QObject(0);
QObject child(parent);
delete parent;
}
上面代碼中delete parent時猎醇,會對child進(jìn)行delete,而child不是new的努溃,導(dǎo)致出錯硫嘶。在正確的QT開發(fā)中,頂級的patent一般是在main函數(shù)中梧税,而patent生命周期一般都會比child長沦疾,而上述代碼中,parent的生命周期比child短第队。
(3)在parent范圍外持有childs的指針
在parent釋放后哮塞,其child不知道自己被delete了,此時child的指針就是野指針凳谦。Qt不建議在一個parent的范圍之外持有對childs的指針忆畅,這樣就不會出現(xiàn)前面那樣野指針的問題了。但是非要在parent外持有child的指針晾蜘,那么Qt推薦使用QPointer邻眷,QPointer相當(dāng)于一個智能指針眠屎,不用智能指針前的代碼如下:
{
QObject*parent=new QObject(0);
QObject*child=new QObject(parent);
delete parent;
child->...
}
這里第4步"child->"會出錯剔交,因為其parent已經(jīng)在前面"delete parent"時也將它釋放了。應(yīng)該這樣:
{
QObject*parent=new QObject(0);
QObject*child=new QObject(parent);
QPointerp=child;
delete parent;
if(p.isNull()){
p->...
}
}
這里使用QPointer改衩,可以判斷出child是否被釋放岖常。
Qt的Model/View編程模式
Qt 4使用model/view結(jié)構(gòu)來管理數(shù)據(jù)與表示層的關(guān)系。這種結(jié)構(gòu)將顯示與數(shù)據(jù)分離葫督,給開發(fā)人員帶來更大的彈性來定制數(shù)據(jù)項的表示竭鞍。
Model-View-Controller(MVC)板惑, 是從Smalltalk發(fā)展而來的一種設(shè)計模式,常被用于構(gòu)建用戶界面偎快。MVC 由三種對象組成冯乘。
- Model是應(yīng)用程序?qū)ο螅?/li>
- View是它的屏幕表示,
- Controller定義了用戶界面如何對用戶輸入進(jìn)行響應(yīng)晒夹。
在MVC之前裆馒,用戶界面設(shè)計傾向于三者揉合在一起,MVC對它們進(jìn)行了解耦丐怯,提高了靈活性與重用性喷好。
假如把view與controller結(jié)合在一起,結(jié)果就是model/view結(jié)構(gòu)读跷。這個結(jié)構(gòu)依然是把數(shù)據(jù)存儲與數(shù)據(jù)表示進(jìn)行了分離梗搅,它與MVC都基于同樣的思想,但它更簡單一些效览。這種分離使得在幾個不同的view上顯示同一個數(shù)據(jù)成為可能无切,也可以重新實現(xiàn)新的view,而不必改變底層的數(shù)據(jù)結(jié)構(gòu)。為了更靈活的對用戶輸入進(jìn)行處理丐枉,引入了delegate這個概念订雾。它的好處是,數(shù)據(jù)項的渲染與編程可以進(jìn)行定制矛洞。
許多便利類都源于標(biāo)準(zhǔn)的view類洼哎,它們方便了那些使用Qt中基于項的view與table類,它們不應(yīng)該被子類化沼本, 它們只是為Qt 3的等價類提供一個熟悉的接口噩峦。QListWidget,QTreeWidget,QTableWidget,它們提供了如Qt 3中的QListBox, QlistView,QTable相似的行為。這些類比View類缺少靈活性抽兆,不能用于任意的models,推介使用model/view的方法處理數(shù)據(jù)识补。
Qt采用Model/View的方式,主要相關(guān)類可以被分成上面所提到的三組:models,views,delegates辫红。
- model凭涂,與數(shù)據(jù)源通訊,并提供接口給結(jié)構(gòu)中的別的組件使用贴妻。通訊的性質(zhì)依賴于數(shù)據(jù)源的種類與model實現(xiàn)的方式切油;
- view,從model獲取model indexes,通過model indexes名惩,view可以從model數(shù)據(jù)源中獲取數(shù)據(jù)并組織澎胡;
- delegate,會在標(biāo)準(zhǔn)的views中對數(shù)據(jù)項進(jìn)行進(jìn)一步渲染或編輯,當(dāng)某個數(shù)據(jù)項被選中時攻谁,delegate通過model indexes與model直接進(jìn)行交流稚伍。
models,views,delegates之間通過信號,槽機(jī)制來進(jìn)行通訊戚宦。
1.Models
所有的item models都基于QAbstractItemModel類个曙,這個類定義了用于views和delegates訪問數(shù)據(jù)的接口。數(shù)據(jù)本身不必存儲在model,數(shù)據(jù)可被置于一個數(shù)據(jù)結(jié)構(gòu)或另外的類受楼,文件困檩,數(shù)據(jù)庫,或別的程序組件中那槽。QT提供了一些現(xiàn)成的models用于處理數(shù)據(jù)項:
- QStringListModel 用于存儲簡單的QString列表悼沿。
- QStandardItemModel 一個多用途的model,可用于表示list,table,tree views所需要的各種不同的數(shù)據(jù)結(jié)構(gòu)骚灸,這個數(shù)據(jù)每項都可以包含任意數(shù)據(jù)糟趾。
- QDirModel 提供本地文件系統(tǒng)中的文件與目錄信息。
- QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel用來訪問數(shù)據(jù)庫甚牲。
假如這些標(biāo)準(zhǔn)Model不滿足需要义郑,我們可以子類化QAbstractItemModel,QAbstractListModel或是QAbstractTableModel來定制自己所需的數(shù)據(jù)。
(1)ModelIndex
通過model index丈钙,可以引用model中的數(shù)據(jù)項非驮,而不必關(guān)注底層的數(shù)據(jù)結(jié)構(gòu)。
Views和delegates都使用indexes來訪問數(shù)據(jù)項雏赦,然后再顯示出來劫笙。這使得數(shù)據(jù)存儲與數(shù)據(jù)訪問分開,只有model需要了解如何獲取數(shù)據(jù)星岗,
model index需要關(guān)注關(guān)于model的三個屬性:行數(shù)填大,列數(shù),父項的model index俏橘。
另外允华,有時model會重新組織內(nèi)部的數(shù)據(jù)結(jié)構(gòu),所以保存臨時的model indexes可能會失效寥掐,所以這時應(yīng)該創(chuàng)建一個長期的model index保存靴寂,這個引用會保持更新。
臨時的model indexes由QModelIndex提供召耘,而具有持久能力的model indexes則由QPersistentModelIndex提供百炬。
(2)Model role
model中的項可以作為各種角色來使用,這意味著在不同的環(huán)境下怎茫,Model會提供不同的數(shù)據(jù)(給View或Delegate)收壕。
例如Qt::DisplayRole用于訪問一個字符串妓灌,設(shè)置此角色后轨蛤,數(shù)據(jù)項會作為文本在view中顯示蜜宪。標(biāo)準(zhǔn)的角色在Qt::ItemDataRole中定義。我們可以通過指定model index與角色來獲取我們需要的數(shù)據(jù)祥山。
一個通過Model Index 和role訪問Model項的例子:
QDirModel *model = new QDirModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);//獲取model的尺寸
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);//樹形目錄結(jié)構(gòu)需要row和parentIndex信息定位特定數(shù)據(jù)項
tring text = model->data(index, Qt::DisplayRole).toString();//指定role為Qt::DisplayRole圃验,可獲取相應(yīng)字符串?dāng)?shù)據(jù)。
// Display the text in a widget.
}
(3)自己設(shè)計Model的例子:
假設(shè)我們實現(xiàn)一個自己的model, 用來顯示字符串列表缝呕。
類聲明如下:
class MyStringListModel : public QAbstractListModel
{
Q_OBJECT
public:
MyStringListModel(const QStringList &strings, QObject *parent = 0): QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index,const QVariant &value, int role);
private:
QStringList stringList;
};
除了構(gòu)造函數(shù)澳窑,我們僅需要實現(xiàn)兩個函數(shù):rowCount()返回model中的行數(shù),data()返回與特定model index對應(yīng)的數(shù)據(jù)項供常。具有良好行為的model也會實現(xiàn)headerData()摊聋,它返回tree和table views需要的,在標(biāo)題中顯示的數(shù)據(jù)栈暇。
因為這是一個非層次結(jié)構(gòu)的model,我們不必考慮父子關(guān)系麻裁。假如model具有層次結(jié)構(gòu),我們也應(yīng)該實現(xiàn)index()與parent()函數(shù)源祈。
每個函數(shù)實現(xiàn):
int MyStringListModel::rowCount(const QModelIndex &parent) const
{//數(shù)據(jù)的長度即stringList長度
return stringList.count();
}
QVariant MyStringListModel::data(const QModelIndex &index, int role) const
{//獲取數(shù)據(jù)煎源,這里只有一個角色:Qt::DisplayRole
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
}
QVariant MyStringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{//頭部的顯示信息增加界面友好性
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
}
至此,我們創(chuàng)建的Model可以表示一個字符串列表香缺,供Views來顯示手销。如果想要修改其內(nèi)容,需要再添加其它函數(shù)實現(xiàn)(如flags, setData)图张,并借助Delegates來實現(xiàn)和用戶的特定交互方式(editline,還是combo box等)锋拖,可參見后面。
2.Views
不同的view都完整實現(xiàn)了各自的功能:QListView把Model的數(shù)據(jù)顯示為一個列表祸轮,QTableView把Model的數(shù)據(jù)以table的形式表現(xiàn)姑隅,QTreeView 用具有層次結(jié)構(gòu)的列表來顯示model中的數(shù)據(jù)。這些類都基于QAbstractItemView抽象基類倔撞,盡管這些類都是現(xiàn)成的讲仰,完整的進(jìn)行了實現(xiàn),但它們都可以用于子類化以便滿足定制需求痪蝇。
在model/view架構(gòu)中鄙陡,view從model中獲得數(shù)據(jù)項然后顯示給用戶。數(shù)據(jù)顯示的方式不必與model提供的表示方式相同躏啰,可以與底層存儲數(shù)據(jù)項的數(shù)據(jù)結(jié)構(gòu)完全不同趁矾。這種內(nèi)容與顯式的分離是通過由QAbstractItemModel提供的標(biāo)準(zhǔn)模型接口,由QAsbstractItemview提供的標(biāo)準(zhǔn)視圖接口共同實現(xiàn)的给僵。
普遍使用model index來表示數(shù)據(jù)項毫捣。view負(fù)責(zé)管理從model中讀取的數(shù)據(jù)的外觀布局详拙。它們自己可以去渲染每個數(shù)據(jù)項,也可以利用delegate來既處理渲染又進(jìn)行編輯蔓同。
除了顯示數(shù)據(jù)饶辙,views也處理數(shù)據(jù)項的導(dǎo)航,參與有關(guān)于數(shù)據(jù)項選擇的部分功能斑粱。view也實現(xiàn)一些基本的用戶接口特性割卖,如上下文菜單與拖拽功能空猜。view也為數(shù)據(jù)項提供了缺省的編程功能碍侦,也可搭配delegate實現(xiàn)更為特殊的定制編輯的需求呐能。一個view創(chuàng)建時必不需要model,但在它能顯示一些真正有用的信息之前,必須提供一個model尚揣。view通過使用selections來跟蹤用戶選擇的數(shù)據(jù)項涌矢。每個view可以維護(hù)單獨(dú)使用的selections,也可以在多個views之間共享(例如在QListView中選擇一項快骗,同時也在使用同一個selections同一model的QTreeView中顯示出來)娜庇。
有些views,如QTableView和QTreeView,除數(shù)據(jù)項之外也可顯示標(biāo)題(Headers),標(biāo)題部分通過一個view來實現(xiàn)滨巴,QHeaderView思灌。標(biāo)題與view一樣總是從相同的model中獲取數(shù)據(jù)。從 model中獲取數(shù)據(jù)的函數(shù)是QabstractItemModel::headerDate()恭取,一般總是以表單的形式中顯示標(biāo)題信息泰偿。可以從QHeaderView子類化蜈垮,以實現(xiàn)更為復(fù)雜的定制化需求耗跛。
(1)使用model
a.前面的例子中創(chuàng)建過一個string list model,這里給它設(shè)置一些數(shù)據(jù),再創(chuàng)建一個view把model中的內(nèi)容展示出來:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = new StringListModel(numbers);
//要注意的是攒发,這里把StringListModel作為一個QAbstractItemModel來使用调塌。這樣我們就可以
//使用model中的抽象接口,而且如果將來我們用別的model代替了當(dāng)前這個model,這些代碼也會照樣工作惠猿。
//QListView提供的列表視圖足以滿足當(dāng)前這個model的需要了羔砾。
QListView *view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
view會渲染model中的內(nèi)容,通過model的接口來訪問它的數(shù)據(jù)偶妖。當(dāng)用戶試圖編輯數(shù)據(jù)項時姜凄,view會使用缺省的delegate來提供一個編輯構(gòu)件。
b.一個model,多個views
為多個views提供相同的model是非常簡單的事情趾访,只要為每個view設(shè)置相同的model态秧。
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model);
在model/view架構(gòu)中信號、槽機(jī)制的使用意味著model中發(fā)生的改變會傳遞中聯(lián)結(jié)的所有view中扼鞋,這保證了不管我們使用哪個view申鱼,訪問的都是同樣的一份數(shù)據(jù)愤诱。
c.多個views之間共享選擇
接著上邊的例子,我們可以這樣:
secondTableView->setSelectionModel(firstTableView->selectionModel());
現(xiàn)在所有views都在同樣的選擇模型上操作捐友,數(shù)據(jù)與選擇項都保持同步淫半,在firstTableView上選擇的項,在secondTableView上也會高亮出來楚殿。
(2)使用選擇項
另外撮慨,在views中還可使用選擇項竿痰,設(shè)定選擇哪些數(shù)據(jù)脆粥,以及更新和讀取選擇的狀態(tài)等。這里省略影涉,可參見QItemSelection变隔。
3.Delegates
與MVC模式不同,model/view結(jié)構(gòu)沒有用于與用戶交互的完全獨(dú)立的組件蟹倾。一般來講匣缘, view負(fù)責(zé)把數(shù)據(jù)展示給用戶,也處理用戶的輸入鲜棠。為了獲得更多的靈活性肌厨,交互通過delegagte執(zhí)行。
Delegate既提供輸入功能又負(fù)責(zé)渲染view中的每個數(shù)據(jù)項豁陆。 控制delegates的標(biāo)準(zhǔn)接口在QAbstractItemDelegate類中定義柑爸,Delegates通過實現(xiàn)paint()和sizeHint()以達(dá)到渲染內(nèi)容的目的。然而盒音,簡單的基于widget的delegates,可以從QItemDelegate子類化表鳍,而不是QAbstractItemDelegate,這樣可以使用它提供的上述函數(shù)的缺省實現(xiàn)。delegate可以使用widget來處理編輯過程祥诽,也可以直接對事件進(jìn)行處理譬圣。
Qt提供的標(biāo)準(zhǔn)views都使用QItemDelegate的實例來提供編輯功能。它以普通的風(fēng)格來為每個標(biāo)準(zhǔn)view渲染數(shù)據(jù)項雄坪。這些標(biāo)準(zhǔn)的views包括:QListView,QTableView,QTreeView厘熟。所有標(biāo)準(zhǔn)的角色都通過標(biāo)準(zhǔn)views包含的缺省delegate進(jìn)行處理。一個view使用的delegate可以用itemDelegate()函數(shù)取得,而setItemDelegate() 函數(shù)可以安裝一個定制delegate维哈。
實現(xiàn)自定制的delegate
在前面绳姨,我們已經(jīng)建立了一個基于字符串的QStringListModel,我們這里用自己定義的delegate來控制每一項的編輯和渲染。我們建立了一個list view來顯示model的內(nèi)容笨农,用我們定制的delegate來編輯和顯示就缆,這個delegate使用QLineEdit來提供編輯和顯示的功能(當(dāng)然我們也可用自己定義的窗口組件,這里使用編輯器有點(diǎn)誤導(dǎo)人谒亦,好像delegate只能編輯似的竭宰,實際我們可以將delegate看作一個任意的窗口部件空郊,其輸入輸出就是model中的數(shù)據(jù),而views實際就是對delegate以列表切揭,樹形結(jié)構(gòu)等方式組織起來狞甚,更進(jìn)一步用什么方式展示數(shù)據(jù),由delegate決定)廓旬。
類聲明
我們繼承QItemDelegate哼审,這樣可以利用它缺省實現(xiàn)的顯示功能。當(dāng)然我們必需提供函數(shù)來管理用于編輯的widget:
class LineEditDelegate : public QItemDelegate
{
Q_OBJECT
public:
LineEditDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;//提供編輯器
void setEditorData(QWidget *editor, const QModelIndex &index) const;//用編輯器渲染model的data
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;//向model提交修改的data孕豹。
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;//更新編輯器幾何布局
};
需要注意的是,當(dāng)一個delegate創(chuàng)建時涩盾,不需要安裝一個widget,只有在真正需要時才創(chuàng)建這個用于編輯的widget励背。
類實現(xiàn)
當(dāng)List view需要提供一個編輯器時春霍,它要求delegate提供一個widget編輯器,修改當(dāng)前的數(shù)據(jù)項叶眉。createEditor()函數(shù)用于創(chuàng)建那個編輯器址儒。
QWidget *LineEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
我們不需要跟蹤這個widget的指針,因為view會在不需要時銷毀這個widget衅疙。我們也可以根據(jù)不同的model index來創(chuàng)建不同的編輯器莲趣,比如,我們有一列整數(shù)饱溢,一列字符串喧伞,我們可以根據(jù)哪種列被編輯來創(chuàng)建一個QSpinBox或是QLineEdit。
delegate必需能夠把model中的數(shù)據(jù)拷貝到編輯器中理朋,以起到渲染的作用絮识。這需要我們實現(xiàn)setEditorData()函數(shù)。
void LineEditDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data(index, Qt::DisplayRole).toString();
QLineEdit *line = static_cast(editor);
line->setText(text);
}
這樣嗽上,數(shù)據(jù)便以QLineEdit的方式被渲染出來次舌。
delegate必需能夠把在編輯器中修改好的數(shù)據(jù)提交給model。這需要我們實現(xiàn)另外一個函數(shù)setModelData()兽愤。
void LineEditDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit *line = static_cast(editor);
QString text = line->text();
model->setData(index, text);
}
標(biāo)準(zhǔn)的QItemDelegate類當(dāng)它完成編輯時會發(fā)射closeEditor()信號來通知view彼念。view保證編輯器widget關(guān)閉與銷毀。本例中我們只提供簡單的編輯功能浅萧,因此不需要發(fā)送個信號逐沙。
delegate負(fù)責(zé)管理編輯器的幾何布局。這些幾何布局信息在編輯創(chuàng)建時或view的尺寸位置發(fā)生改變時洼畅,都應(yīng)當(dāng)被提供吩案。view通過一個view option可以提供這些必要的信息。
void LineEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
-
編輯提示
編輯完成后帝簇,delegate會給別的組件提供有關(guān)于編輯處理結(jié)果的提示徘郭,也提供用于后續(xù)編輯操作的一些提示靠益。這可以通過發(fā)射帶有某種hint的closeEditor()信號完成。這些信號會被安裝在line edit上的缺省的QItemDelegate事件過濾器捕獲残揉。對這個缺省的事件過濾來講胧后,當(dāng)用戶按下回車鍵,delegate會對model中的數(shù)據(jù)進(jìn)行提交抱环,并關(guān)閉spin box壳快。 我們可以安裝自己的事件過濾器以迎合我們的需要,例如镇草,我們可以發(fā)射帶有EditNextItem hint的 closeEditor()信號來實現(xiàn)自動開始編輯view中的下一項眶痰。
-
繼續(xù)完善model以支持delegate
delegate會在創(chuàng)建編輯器之前檢查數(shù)據(jù)項是否是可編輯的。model必須得讓delegate知道它的數(shù)據(jù)項是可編輯的陶夜。這可以通過為每一個數(shù)據(jù)項返回一個正確的標(biāo)記得到凛驮,在本例中裆站,我們假設(shè)所有的數(shù)據(jù)項都是可編輯可選擇的条辟。所以需要為MyStringListModel實現(xiàn)flags函數(shù)。
Qt::ItemFlags MyStringListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; }
model不必知道delegate執(zhí)行怎樣實際的編輯處理過程宏胯,但需提供給delegate一個方法羽嫡,delegate會使用它對model中的數(shù)據(jù)進(jìn)行設(shè)置。這個特殊的函數(shù)就是setData()肩袍。
bool MyStringListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { stringList.replace(index.row(), value.toString()); emit dataChanged(index, index); return true; } return false; }
這里當(dāng)數(shù)據(jù)被設(shè)置后杭棵,model必須得讓views知道一些數(shù)據(jù)發(fā)生了變化,這里通過發(fā)射一個dataChanged() 信號實現(xiàn)氛赐。因為只有一個數(shù)據(jù)項發(fā)生了變化魂爪,因此在信號中說明的變化范圍只限于一個model index。
另外艰管,還可實現(xiàn)插入行和刪除行的功能滓侍,需要實現(xiàn)兩個函數(shù),并在view處做相應(yīng)處理牲芋,這里不細(xì)述撩笆。
bool MyStringListModel::insertRows(int position, int rows, const QModelIndex &parent) {//beginInsertRows()通知其他組件行數(shù)將會改變。endInsertRows()對操作進(jìn)行確認(rèn)與通知缸浦。返回true表示成功夕冲。 beginInsertRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.insert(position, ""); } endInsertRows(); return true; } bool MyStringListModel::removeRows(int position, int rows, const QModelIndex &parent) { beginRemoveRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.removeAt(position); } endRemoveRows(); return true; }
繪制系統(tǒng)
Qt的繪制系統(tǒng)主要由三部分組成,QPainter, QPaintDevice, QPaintEngine裂逐。
- QPainter 是一個繪制接口類歹鱼,提供繪制各種面向用戶的命令;
- QPaintDevice 是對被QPainter繪制的設(shè)備(目的地)進(jìn)行抽象形成的2維空間,相當(dāng)于畫布;
- 而QPaintEngine 是介于兩者之間的基本繪制命令的具體實現(xiàn)卜高,被兩者在內(nèi)部調(diào)用弥姻,它根據(jù)被繪制的設(shè)備的不同而不同秩霍。
由于這個結(jié)構(gòu),我們繪制時就不用關(guān)心具體的設(shè)備(QPaintDevice)蚁阳,直接和QPainter打交道即可铃绒。注意對于Windows平臺來說,當(dāng)繪制目標(biāo)是一個widget的時候螺捐,QPainter只能在 paintEvent() 里面或者由paintEvent()導(dǎo)致調(diào)用的函數(shù)里面使用颠悬。另外,Qt提供了對OpenGL的支持定血,通過和QWidget類似的方式赔癌,使用OpenGL的功能。
1.Matrix, Coordinate, View port & window
默認(rèn)情況下澜沟,QPainter 使用的是 當(dāng)前 device 的坐標(biāo)系灾票,坐標(biāo)原點(diǎn)是左上角,x軸向右遞增茫虽,y軸向下遞增刊苍,坐標(biāo)單位,對于基于像素的設(shè)備是1個像素濒析,對于打印機(jī)是1/72英寸正什。
但是QPainter 對于坐標(biāo)系變換提供了很好的支持,主要有如下的一些坐標(biāo)系變換:
- 旋轉(zhuǎn)
- 縮放
- 平移
- shearing
用 scale() 來縮放号杏, rotate() 用來對坐標(biāo)系進(jìn)行順時針旋轉(zhuǎn)婴氮, translate() 對坐標(biāo)系執(zhí)行平移操作。也可以通過函數(shù) shear() 對坐標(biāo)系執(zhí)行扭曲盾致。類似對矩陣執(zhí)行雅克比切變主经,讓 坐標(biāo)系的 x 和 y 不再是正交的向量。這里提到的變換都是作用在 worldTransform()的矩陣上庭惜。 還有一個矩陣 deviceTransform 用來把邏輯坐標(biāo)變換到設(shè)備的坐標(biāo)罩驻。
當(dāng)用QPainter執(zhí)行繪制的時候,我們指定的頂點(diǎn)坐標(biāo)都是邏輯坐標(biāo)蜈块,這個邏輯坐標(biāo)最終會被轉(zhuǎn)換成設(shè)備的物理坐標(biāo)鉴腻,從邏輯坐標(biāo)到物理坐標(biāo)的轉(zhuǎn)換,是通過矩陣combinedTransform()執(zhí)行的百揭,這個矩陣爽哎,結(jié)合了 viewport() , window(), 和 worldTransform()器一。 其中 viewport()代表的是物理坐標(biāo)系中的任意的一個矩形區(qū)域课锌,而window()是以邏輯坐標(biāo)的形式描述viewport()指定的同一個矩形。其中worldTransform() 就等于變換矩陣。
2.繪制內(nèi)容
對于QPainter來說渺贤,內(nèi)部有一個狀態(tài)堆棧雏胃,任何時候都可以通過調(diào)用 save() 和 restore() 對QPainter的內(nèi)部狀態(tài)(如旋轉(zhuǎn)角度等)執(zhí)行 進(jìn)棧保存和壓棧還原的操作。
QPainter 提供了大部分基本二維幾何元的繪制命令志鞍,可以繪制如:QPoint, QLine, QRect, QRegion, QPolygon等表示的圖形瞭亮,以及一些復(fù)雜的圖形可通過QPainterPath來進(jìn)行。QPainterPath實際是一個各種基本繪制操作的容器固棚,將復(fù)雜的繪制操作存于QPainterPath中统翩,然后通過 QPainter::drawPath()一次性繪制出來。
另外此洲,QPainter還可以繪制文字厂汗,以及pixmap(對圖片的像素表示,不能直接顯示需要轉(zhuǎn)成相應(yīng)的圖片格式顯示)呜师。
3.填充
填充使用QBrush來完成娶桦,可以指定填充的顏色和風(fēng)格。填充的顏色用QColor表示汁汗,風(fēng)格 Qt::BrushStyle 枚舉列出衷畦。還可通過 QGradient 來自行指定填充的梯度,以及通過QPixmap自行指定填充紋理碰酝。
4.創(chuàng)建繪制設(shè)備
QPaintDevice是繪制設(shè)備的基類霎匈。QPainter可以在任何QPaintDevice子類對象上進(jìn)行繪制。目前Qt實現(xiàn)的QPaintDevice包括:QWidget, QImage, QPixmap, QGLWidget, QGLPixelBuffer, QPicture 和 QPrinter和子類等送爸。
如果添加自己的繪制后端:
- 我們需要繼承QPaintDevice,重新實現(xiàn)虛函數(shù):QPaintDevice::paintEngine()以確定QPaintDevice使用哪個engine暖释。
- 我們還需繼承QPaintEngine來實現(xiàn)這個engine袭厂,以便能夠在添加的設(shè)備上進(jìn)行繪制。
5.讀寫圖片文件
Qt提供了四種類用來處理圖片數(shù)據(jù):QImage, QPixmap, QBitmap以及QPicture球匕。
- QImage對I/O的操作進(jìn)行了優(yōu)化纹磺,用于直接對像素進(jìn)行訪問和操作。
- QPixmap對顯示圖片到屏幕上進(jìn)行了優(yōu)化亮曹。
- QBitmap是QPixmap的子類橄杨,保證了色深為1。
- QPicture是一個繪制設(shè)備照卦,用來記錄和回放QPainter的操作式矫。
對圖片操作最常用的類就是QImage和QPixmap∫鄹可以通過其構(gòu)造函數(shù)采转,或者load、save函數(shù)瞬痘。另外Qt也提供了QImageReader和 QImageWriter可以對圖片處理提供更多更方便的控制故慈。
QMovie用來顯示動畫板熊,其內(nèi)部使用了QImageReader。QImageWriter和QImageReader依賴QImageIOHandler察绷,QImageIOHandler為Qt提供了操作所有格式圖片的一些通用接口干签。QImageWriter和QImageReader內(nèi)部就使用QImageIOHandler為Qt添加不同圖片格式的支持。
Qt里支持了一些格式的圖片拆撼,可通過 QImageReader::supportedImageFormats() 和 QImageWriter::supportedImageFormats()來查詢筒严。若為Qt添加新圖片格式的支持,通過插件實現(xiàn)情萤。繼承QImageIOHandler以及創(chuàng)建用于創(chuàng)建handler的QImageIOPlugin 對象后鸭蛙,就可以用QImageWriter和QImageReader來操作這個新格式的圖片了。
另外Qt還支持靜態(tài)SVG圖片筋岛,見QtSvg相關(guān)文檔娶视。
6.風(fēng)格化
Qt的內(nèi)建控件一般都使用QStyle來進(jìn)行繪制。QStyle是風(fēng)格的基類睁宰。每種風(fēng)格都代表一種gui的顯示特性肪获,例如(windows下的,linux下的)柒傻,如果定義自己顯示個性的風(fēng)格孝赫,那么可以通過插件機(jī)制來實現(xiàn)。
大多數(shù)函數(shù)繪制風(fēng)格元素需要四個參數(shù):
- 一個表示哪種圖形元素的枚舉红符。
- 一個QStyleOption描述怎樣以及向哪來提交元素青柄。
- 一個QPainter對象用于繪制元素。
- 一個QWidget對象预侯,繪制的動作將在其上進(jìn)行(可選)致开。
QStylePainter 繼承自QPainter,可以方便地利用QStyle進(jìn)行風(fēng)格繪制萎馅。
7.選擇繪制后端
Qt4.5開始双戳,我們可以選擇替換用于widgets,pixmaps,和無屏雙緩沖的engines和devices。各個系統(tǒng)默認(rèn)的后端是:
Windows系統(tǒng):Software Rasterizer
X11系統(tǒng):X11
Mac OS X系統(tǒng):CoreGraphics
Embedded系統(tǒng):Software Rasterizer
我們可以通過啟動程序時指定"-graphicssystem raster"來告訴Qt使用rasterizer軟件作為程序的后端糜芳。rasterizer軟件對所有的平臺都支持的很好飒货。如:
$analogclock -graphicssystem raster
也可以使用"-graphicssystem opengl",來指定使用OpenGL繪制峭竣。當(dāng)前塘辅,這個引擎處于實驗階段,可能不能正確繪制所有內(nèi)容邪驮。
Qt也支持"-graphicssystem raster|opengl"配置莫辨,這樣所有的應(yīng)用程序?qū)褂孟鄳?yīng)的圖形系統(tǒng)。