標籤:
最近在看Qt的Model/View Framework,在網上搜了搜,好像中文的除了幾篇翻譯沒有什麼有價值的文章。E文的除了Qt的官方介紹,其它文章也很少。看到一個老外在blog中寫道Model/View是他認為Qt中最不好的一部分了。真的是這樣嗎?為了回饋開源社區,我寫了這篇blog,寫的是我認為比較有價值的東東。題目起得是解析,但也沒有特別細節的介紹,點到為止,有興趣的Tx可以繼續討論。我所看的資料有《C++ GUI Programming with Qt 4, Second Edition》、Qt官網和Qt原始碼。
在UI中,最常用的就是list/grid/tree了(在Qt中,grid被稱為table)。尤其是做那些資料庫相關的程式,可能每個介面都要用到list或grid。在Qt中,它們被歸為item view class。有兩種實現,一種叫item based,這些類名以widget結尾,如QListWidget等。另一種叫model based,這些類以view結尾,如QListView等。
item based widget使用起來很簡單,與MFC中的CListCtrl/CTreeCtrl等差不多。這些類既是資料容器(data container),又負責顯示(presentation),還要處理與使用者的互動(user action)。但使用這些類的缺點就是在處理大量資料顯示時顯得力不從心。我記得使用CListCtrl的report mode顯示超過5萬條記錄時需要等待很長時間。因為它要把所有的資料都拷貝進來,再顯示,比如這個list一次只能顯示50條記錄,但它把剩下的49950條也拷貝進了記憶體,並為每條記錄的顯示都加上格式等額外的資訊。我想這種實現對於專業的軟體來說是無法接受的。那麼怎麼提高它們的效能呢?有些人想出了virtual list/grid。它們顯示之前並不需要把所有的資料都裝進來,如有5萬條記錄,剛開始時它顯示前50條,顯示哪條時去取哪條。也就是說後49950條它不管,當使用者移動捲軸或上下鍵改變顯示內容時,它會根據一系列參數計算出目前需要顯示哪幾條。如1000-1050條,那麼再去取這幾條資料顯示。由於實現起來有一定的難度,所以很多MFC實現的virtual list/grid顯示出來都是“素顏”的(能快速顯示出來已經不錯了)。當然,coding無止境,這種取資料的方式我們稱為delayed fetch,如果實現不好的化,當電腦速度慢且使用者急速拖捲軸時也有可能出現白屏、拖尾、模糊等情況。所以還可以做prefetch和caching來儘力避免這些情況。好了,這裡鋪墊的已經夠多了,下面我們直奔主題。
model based view就是我們今天要重點“解析”的。對應的UI類有QListView/QTreeView/QTableView。那麼這些view與上面的widget有什麼區別呢?名字就是最大的區別。widget本來就是指小東東,而view可是很“大氣”的視圖哦。那麼這些view大哥是怎麼顯示的呢?下面就說說我不願多說但又不得不說的一個設計模式:MVC (Model/View/Controller),model代表資料(data set),view代表顯示(presentation),controller處理與使用者互動(user action)。MVC模式源自smalltalk,現在好像被用的很多了。它的作用其實是把上面介紹的類似CListCtrl這樣的類解放出來,因為它們要幹很多事情,太累了。還有就是對於有些程式員來說,實現一個功能複雜的類可能會力不從心。分成3個類每個類解決不同的問題可能更好實現。從軟體工程的角度上講,這種方式的decoupling可以降低系統風險。好了,關於MVC,大家想瞭解更多的話可以繼續在網上搜尋。
模式是MVC,那麼Qt是怎麼實現的呢?我開啟了Qt的官網介紹(http://qt.nokia.com/doc/4.6/model-view-programming.html)。我暈,看到了幾十個類,而且很多都有繼承關係。很容易讓人迷失啊。不過不要緊,經過2天的學習,我對它們做了總結,見。Tx們可以從我這幅圖開始,抓住該framework的主線,免得迷失在茫茫淚(類)海:-)
一般來說,Model裡面並沒有真正儲存資料(資料少的話也可以直接儲存在Model裡),它的資料是從真正的“肉(raw)”裡取得,如一個disk file,或database的query result set等等。那麼這個model究竟是幹什麼用的呢?說白了吧,它就是負責將“肉”資料擷取並提供給view,然後將view所做的對“肉”資料的修改更新至真正的“肉”中。所以,讀寫檔案、操作資料庫、網路通訊等一系列與資料打交道的工作就在model中做了。有的時候“肉”可能真的很肥,所以model還有一項重要的工作就是把這些“肉”編號。這樣就出現了Model Index這個非常重要的類。一般來說,它使用一個2維的編號(row/colum)來對“肉”編號。但對於tree這種有階層的資料來說,又加上一個parent index作為第3個編號。即一個父親下面的葉子也是從0,0開始編號,擷取model index的時候用遞迴來實現。OK,現在model已經有了一堆編好號碼的“肉”了,誰來買啊?“肉”便宜了。。。。。。
View適時出現,注意,很多view可以同時來買同一塊“肉”。(汗,不開玩笑了,這篇blog快水了)。當view需要顯示某些資料時,它們通過model index從model中擷取資料(調用model的data函數,當model的data變化時,它也會自動發dataChanged signal給所有的view以便它們更新)。當然,在view中也可以調用model的setData函數來設定某個model index所對應的資料。這裡要說明一下model中的資料,用QVarient來承載,可以是所有Qt支援的類型,比較貼心的是,資料可以分成多個角色(role),例如Qt::DisplayRole專用於顯示,Qt::BackgroundRole用於顯示背景色等等。所以在model中,你不光可以對“肉”進行編號,還可以對“肉”進行“深加工”,使它們更“好看”或是更“美味”。View組織這些資料並顯示,但卻沒有做真正的顯示工作,真正的工作留給了delegate。
Delegate就是MVC中的C。view讓它顯示時它就在paint函數中顯示。當然,你可以重載這個函數並實現你自己的顯示。你還可以給一個view設定row delegate和colum delegate專用於row和colum。當使用者觸發了view的edit trigger時(如雙擊滑鼠或斷行符號),view開始in place edit (beginEditing)。Delegate會在合適的地方建立一個合適的widget(如line edit或combo box等)處理使用者的輸入,使用者輸入完成以後delegate擷取使用者的輸入並返回。這些輸入可以通過調用model的setData函數儲存到真正的“肉”中。所以Delegate其實就是負責最終顯示資料和處理使用者互動的。
既然由使用者互動,最重要的肯定是使用者的選擇了。說一下selection model。View將使用者選擇的item index全部存入selection model中,顯示的時候根據selection model的內容顯示。另外,多個view可以共用同一個selection model,這樣,當你選中其中的一個時,另一個view中的相應item也會被選中。
最後總結一下,Smalltalk怎麼實現的MVC我不清楚,但是Qt model/view framework對MVC的實現完全是基於C++的虛基類和虛函數特性。MVC各部分之間的聯通除了應用signal/slot之外,就全是虛函數了。Tx們如果有興趣的話,可以看看model/view framework原始碼中那些QAbstract開頭的類,它們定義了統一的介面虛函數後,衍生類別只需要重新實現這些函數即可。這裡再重複一下學習這部分時Tx們應該關注的2條主線。
無論什麼view,作用都是把資料顯示出來,那麼第一條主線是資料怎樣從資料來源顯示到view中。
顯示的資料可能被修改,所以第二條主線就是修改後的資料怎樣再更新至資料來源中。如果你能把這2幅sequence map畫出來,相信你就已經完全明白了。
我不知道Qt在這裡的設計是不是做了太多的decoupling。反正靈活性是足夠了(model/view/delegate都可以繼承),但無法讓程式員快速掌握。雖然Qt已經弱化了delegate並而且為view提供預設的delegate。但在做具體項目時,程式員大部分時候還是要根據具體情況重寫衍生類別數的。這對於那些想快速做介面並顯示的程式員來說,可能會難以接受。總之,這個輪子做的夠精細,但一般在使用之前你還要自己擰點螺絲,上點油。
http://blog.csdn.net/superjoel/article/details/5112120
Qt的Model/View Framework解析(資料是從真正的“肉(raw)”裡取得,Model提供肉,所以讀寫檔案、操作資料庫、網路通訊等一系列與資料打交道的工作就在model中做了)