Qt開發(fā)系列3——Qt中的核心技術(shù)1

簡介

這里簡單介紹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,那么處理的次序是:

  1. QApplication::notify()對QEvent進(jìn)行派發(fā)
  2. 在QApplication::notify()中儿普,用安裝在QApplication上的事件過濾器處理
  3. 在QApplication::notify()中崎逃,調(diào)用QObject::event()對事件進(jìn)行處理
  4. 在QObject::event()中,用安裝在QWidget上的事件過濾器處理
  5. 在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)建插件:

  1. 高層的API:用于擴(kuò)展Qt本身灯谣,比如自定義的數(shù)據(jù)庫驅(qū)動潜秋,文本解碼插件,風(fēng)格插件等胎许。
  2. 低層的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 由三種對象組成冯乘。

  1. Model是應(yīng)用程序?qū)ο螅?/li>
  2. View是它的屏幕表示,
  3. 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辫红。

  1. model凭涂,與數(shù)據(jù)源通訊,并提供接口給結(jié)構(gòu)中的別的組件使用贴妻。通訊的性質(zhì)依賴于數(shù)據(jù)源的種類與model實現(xiàn)的方式切油;
  2. view,從model獲取model indexes,通過model indexes名惩,view可以從model數(shù)據(jù)源中獲取數(shù)據(jù)并組織澎胡;
  3. 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);
}
  1. 編輯提示

    編輯完成后帝簇,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中的下一項眶痰。

  2. 繼續(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)。

其它

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沮榜,隨后出現(xiàn)的幾起案子盘榨,更是在濱河造成了極大的恐慌,老刑警劉巖蟆融,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件草巡,死亡現(xiàn)場離奇詭異,居然都是意外死亡型酥,警方通過查閱死者的電腦和手機(jī)山憨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弥喉,“玉大人郁竟,你說我怎么就攤上這事∮删常” “怎么了棚亩?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虏杰。 經(jīng)常有香客問我讥蟆,道長,這世上最難降的妖魔是什么纺阔? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任瘸彤,我火速辦了婚禮,結(jié)果婚禮上笛钝,老公的妹妹穿的比我還像新娘质况。我一直安慰自己,他們只是感情好婆翔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布拯杠。 她就那樣靜靜地躺著,像睡著了一般啃奴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雄妥,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天最蕾,我揣著相機(jī)與錄音,去河邊找鬼老厌。 笑死瘟则,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枝秤。 我是一名探鬼主播醋拧,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丹壕?” 一聲冷哼從身側(cè)響起庆械,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎菌赖,沒想到半個月后缭乘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琉用,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年堕绩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邑时。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡奴紧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晶丘,到底是詐尸還是另有隱情黍氮,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布铣口,位于F島的核電站滤钱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脑题。R本人自食惡果不足惜件缸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叔遂。 院中可真熱鬧他炊,春花似錦、人聲如沸已艰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哩掺。三九已至凿叠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嚼吞,已是汗流浹背盒件。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舱禽,地道東北人炒刁。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像誊稚,于是被迫代替她去往敵國和親翔始。 傳聞我的和親對象是個殘疾皇子罗心,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345