介紹
目前創(chuàng)新互聯(lián)已為近千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機、網(wǎng)站改版維護、企業(yè)網(wǎng)站設(shè)計、昂仁網(wǎng)站維護等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
Qt 4推出了一組新的item view類,它們使用model/view結(jié)構(gòu)來管理數(shù)據(jù)與表示層的關(guān)系。這種結(jié)構(gòu)帶來的
功能上的分離給了開發(fā)人員更大的彈性來定制數(shù)據(jù)項的表示,它也提供一個標準的model接口,使得更多的
數(shù)據(jù)源可以被這些item view使用。這里對model/view的結(jié)構(gòu)進行了描述,結(jié)構(gòu)中的每個組件都進行了解釋,
給出了一些例子說明了提供的這些類如何使用。
Model/View 結(jié)構(gòu)
Model-View-Controller(MVC), 是從Smalltalk發(fā)展而來的一種設(shè)計模式,常被用于構(gòu)建用戶界面。經(jīng)典設(shè)計模式的著作中有這樣的描述:
MVC 由三種對象組成。Model是應(yīng)用程序?qū)ο?,View是它的屏幕表示,Controller定義了用戶界面如何對用戶輸入進行響應(yīng)。在MVC之前,用戶界面設(shè)計傾向于三者揉合在一起,MVC對它們進行了解耦,提高了靈活性與重用性。
假如把view與controller結(jié)合在一起,結(jié)果就是model/view結(jié)構(gòu)。這個結(jié)構(gòu)依然是把數(shù)據(jù)存儲與數(shù)據(jù)表示進行了分離,它與MVC都基于同樣的思想,但它更簡單一些。這種分離使得在幾個不同的view上顯示同一個數(shù)據(jù)成為可能,也可以重新實現(xiàn)新的view,而不必改變底層的數(shù)據(jù)結(jié)構(gòu)。為了更靈活的對用戶輸入進行處理,引入了delegate這個概念。它的好處是,數(shù)據(jù)項的渲染與編程可以進行定制。
如上圖所示,model與數(shù)據(jù)源通訊,并提供接口給結(jié)構(gòu)中的別的組件使用。通訊的性質(zhì)依賴于數(shù)據(jù)源的種類
與model實現(xiàn)的方式。view從model獲取model indexes,后者是數(shù)據(jù)項的引用。通過把model indexes提供給model,view可以從數(shù)據(jù)源中獲取數(shù)據(jù)。
在標準的views中,delegate會對數(shù)據(jù)項進行渲染,當某個數(shù)據(jù)項被選中時,delegate通過model indexes與model直接進行交流??偟膩碚f,model/view 相關(guān)類可以被分成上面所提到的三組:models,views,delegates。這些組件通過抽象類來定義,它們提供了共同的接口,在某些情況下,還提供了缺省的實現(xiàn)。抽象類意味著需要子類化以提供完整的其他組件希望的功能。這也允許實現(xiàn)定制的組件。models,views,delegates之間通過信號,槽機制來進行通訊:
從model發(fā)出的信號通知view數(shù)據(jù)源中的數(shù)據(jù)發(fā)生了改變。
從view發(fā)出的信號提供了有關(guān)被顯示的數(shù)據(jù)項與用戶交互的信息。
從delegate發(fā)生的信號被用于在編輯時通知model和view關(guān)于當前編輯器的狀態(tài)信息。
Models
所有的item models都基于QAbstractItemModel類,這個類定義了用于views和delegates訪問數(shù)據(jù)的接口。
數(shù)據(jù)本身不必存儲在model,數(shù)據(jù)可被置于一個數(shù)據(jù)結(jié)構(gòu)或另外的類,文件,數(shù)據(jù)庫,或別的程序組件中。
關(guān)于model的基本概念在Model Classes部分中描述。
QAbstractItemModel提供給數(shù)據(jù)一個接口,它非常靈活,基本滿足views的需要,無論數(shù)據(jù)用以下任何樣的形式
表現(xiàn),如tables,lists,trees。然而,當你重新實現(xiàn)一個model時,如果它基于table或list形式的數(shù)據(jù)結(jié)構(gòu),最好從QAbstractListModel,QAbstractTableModel開始做起,因為它們提供了適當?shù)某R?guī)功能的缺省實現(xiàn)。這些類可以被子類化以支持特殊的定制需求。子類化model的過程在Create New Model部分討論
QT提供了一些現(xiàn)成的models用于處理數(shù)據(jù)項:
QStringListModel 用于存儲簡單的QString列表。
QStandardItemModel 管理復(fù)雜的樹型結(jié)構(gòu)數(shù)據(jù)項,每項都可以包含任意數(shù)據(jù)。
QDirModel 提供本地文件系統(tǒng)中的文件與目錄信息。
QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel用來訪問數(shù)據(jù)庫。
假如這些標準Model不滿足你的需要,你應(yīng)該子類化QAbstractItemModel,QAbstractListModel或是
QAbstractTableModel來定制。
Views
不同的view都完整實現(xiàn)了各自的功能:QListView把數(shù)據(jù)顯示為一個列表,QTableView把Model 中的數(shù)據(jù)以table的形式表現(xiàn),QTreeView 用具有層次結(jié)構(gòu)的列表來顯示model中的數(shù)據(jù)。這些類都基于QAbstractItemView抽象基類,盡管這些類都是現(xiàn)成的,完整的進行了實現(xiàn),但它們都可以用于子類化以便滿足定制需求。
Delegates
QAbstractItemDelegate 是model/view架構(gòu)中的用于delegate的抽象基類。缺省的delegate實現(xiàn)在QItemDelegate類中提供。它可以用于Qt標準views的缺省 delegate.
排序
在model/view架構(gòu)中,有兩種方法進行排序,選擇哪種方法依賴于你的底層Model。
假如你的model是可排序的,也就是它重新實現(xiàn)了QAbstractItemModel::sort()函數(shù),QTableView與QTreeView都提供了API,允許你以編程的方式對Model數(shù)據(jù)進行排序。另外,你也可以進行交互方式下的排序(例如,允許用戶通過點擊view表頭的方式對數(shù)據(jù)進行排序),可以這樣做:把QHeaderView::sectionClicked()信號與QTableView::sortByColum()槽或QTreeView::sortByColumn()槽進行聯(lián)結(jié)就好了。
另一種方法是,假如你的model沒有提供需要的接口或是你想用list view表示數(shù)據(jù),可以用一個代理
model在用view表示數(shù)據(jù)之前對你的model數(shù)據(jù)結(jié)構(gòu)進行轉(zhuǎn)換。
便利類
許多便利類都源于標準的view類,它們方便了那些使用Qt中基于項的view與table類,它們不應(yīng)該被子類化,
它們只是為Qt 3的等價類提供一個熟悉的接口。這些類有QListWidget,QTreeWidget,QTableWidget,它們提供了如Qt 3中的QListBox, QlistView,QTable相似的行為。這些類比View類缺少靈活性,不能用于任意的models,推介使用model/view的方法處理數(shù)據(jù)。
介紹
Qt提供了兩個標準的models:QStandardItemModel和QDirModel。QStandardItemModel是一個多用途的
model,可用于表示list,table,tree views所需要的各種不同的數(shù)據(jù)結(jié)構(gòu)。這個model也持有數(shù)據(jù)。QDirModel
維護相關(guān)的目錄內(nèi)容的信息,它本身不持有數(shù)據(jù),僅是對本地文件系統(tǒng)中的文件與目錄的描述。
QDirModel是一個現(xiàn)成的model,很容易進行配置以用于現(xiàn)存的數(shù)據(jù),使用這個model,可以很好地展示如何
給一個現(xiàn)成的view設(shè)定model,研究如何用model indexes來操縱數(shù)據(jù)。
model與views的搭配使用
QListView與QTreeView很適合與QDirModel搭配。下面的例子在tree view與list view顯示了相同的信息,QDirModel提供了目錄內(nèi)容數(shù)據(jù)。這兩個Views共享用戶選擇,因此每個被選擇的項在每個view中都會被高亮。
先裝配出一個QDirModel以供使用,再創(chuàng)建views去顯示目錄的內(nèi)容。這給我展示了使用model的最簡單的方式。
model的創(chuàng)建與使用都在main()函數(shù)中完成:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
QDirModel *model = new QDirModel;
//從缺省目錄創(chuàng)建數(shù)據(jù)
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));
//配置一個view去顯示model中的數(shù)據(jù),只需要簡單地調(diào)用setModel(),并把目錄model作為參數(shù)傳遞
//setRootIndex()告訴views顯示哪個目錄的信息,這需要提供一個model index,然后用這個
//model index去model中去獲取數(shù)據(jù)
//index()這個函數(shù)是QDirModel特有的,通過把一個目錄做為參數(shù),得到了需要的model index
//其他的代碼只是窗口show出來,進入程序的事件循環(huán)就好了
splitter->setWindowTitle("Two views onto the same directory model");
splitter->show();
return app.exec();
}
Model類
基本概念
在model/view構(gòu)架中,model為view和delegates使用數(shù)據(jù)提供了標準接口。在Qt中,標準接口QAbstractItemModel類中被定義。不管數(shù)據(jù)在底層以何種數(shù)據(jù)結(jié)構(gòu)存儲,QAabstractItemModel的子類會以層次結(jié)構(gòu)的形式來表示數(shù)據(jù),結(jié)構(gòu)中包含了數(shù)據(jù)項表。我們按這種約定來訪問model中的數(shù)據(jù)項,但這個約定不會對如何顯示這些數(shù)據(jù)有任何限制。數(shù)據(jù)發(fā)生改變時,model通過信號槽機制來通知關(guān)聯(lián)的views。
Model Indexes
為了使數(shù)據(jù)存儲與數(shù)據(jù)訪問分開,引入了model index的概念。通過model index,可以引用model中的數(shù)據(jù)項,Views和delegates都使用indexes來訪問數(shù)據(jù)項,然后再顯示出來。因此,只有model需要了解如何獲取數(shù)據(jù),被model管理的數(shù)據(jù)類型可以非常廣泛地被定義。Model indexes包含一個指向創(chuàng)建它們的model的指針,這會在配合多個model工作時避免混亂。
QAbstractItemModel *model = index.model();
model indexes提供了對一項數(shù)據(jù)信息的臨時引用,通過它可以訪問或是修改model中的數(shù)據(jù)。既然model有時會重新組織內(nèi)部的數(shù)據(jù)結(jié)構(gòu),這時model indexes便會失效,因此不應(yīng)該保存臨時的model indexes。假如需要一個對數(shù)據(jù)信息的長期的引用,那么應(yīng)該創(chuàng)建一個persistent model index。這個引用會保持更新。臨時的model indexes由QModelIndex提供,而具有持久能力的model indexes則由QPersistentModelIndex提供。在獲取對應(yīng)一個數(shù)據(jù)項的model index時,需要考慮有關(guān)于model的三個屬性:行數(shù),列數(shù),父項的model index。
行與列
在最基本的形式中,一個model可作為一個簡單的表來訪問,每個數(shù)據(jù)項由行,列數(shù)來定位。這必不意味著
底層的數(shù)據(jù)用數(shù)組結(jié)構(gòu)來存儲。行和列的使用僅僅是一種約定,它允許組件之間相互通訊。可以通過指定
model中的行列數(shù)來獲取任一項數(shù)據(jù),可以得到與數(shù)據(jù)項一一對應(yīng)的那個index。
QModelIndex index = model->index(row, column, ...);
Model為簡單的,單級的數(shù)據(jù)結(jié)構(gòu)如list與tables提供了接口,它們?nèi)缟厦娲a所顯示的那樣,不再需要別的信息被提供。當我們在獲取一個model index時,我們需要提供另外的信息。
上圖代表一個基本的table model,它的每一項用一對行列數(shù)來定位。通過行列數(shù),可以獲取代表一個數(shù)據(jù)項的model index .
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
一個model的頂級項,由QModelIndex()取得,它們上式被用作父項。
父項
類似于表的接口在搭配使用table或list view時理想的,這種行列系統(tǒng)與view顯示的方式是確切匹配的。
然則,像tree views這種結(jié)構(gòu)需要model提供更為靈活的接口來訪問數(shù)據(jù)項。每個數(shù)據(jù)項可能是別的項的
父項,上級的項可以獲取下級項的列表。
當獲取model中數(shù)據(jù)項的index時,我們必須指定關(guān)于數(shù)據(jù)項的父項的信息。在model外部,引用一個數(shù)據(jù)
項的唯一方法就是通過model index,因此需要在求取model index時指定父項的信息。
QModelIndex index = model->index(row, column, parent);
上圖中,A項和C項作為model中頂層的兄弟項:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A有許多孩子,它的一個孩子B用以下代碼獲取:
QModelIndex indexB = model->index(1, 0, indexA);
項角色
model中的項可以作為各種角色來使用,這允許為不同的環(huán)境提供不同的數(shù)據(jù)。舉例來說,Qt::DisplayRole被用于訪問一個字符串,它作為文本會在view中顯示。典型地,每個數(shù)據(jù)項都可以為許多不同的角色提供數(shù)據(jù),標準的角色在Qt::ItemDataRole中定義。我們可以通過指定model index與角色來獲取我們需要的數(shù)據(jù):
QVariant value = model->data(index, role);
角色指出了從model中引用哪種類型的數(shù)據(jù)。views可以用不同的形式顯示角色,因此為每個角色提供正確
的信息是非常重要的。通過為每個角色提供適當數(shù)據(jù),model也為views和delegates提供了暗示,如何正確地
把這些數(shù)據(jù)項顯給用戶。不同的views可以自由地解析或忽略這些數(shù)據(jù)信息,對于特殊的場合,也可以定義
一些附加的角色。
概念總結(jié):
1,Model indexes為views與delegages提供model中數(shù)據(jù)項定位的信息,它與底層的數(shù)據(jù)結(jié)構(gòu)無關(guān)。
2,通過指定行,列數(shù),父項的model index來引用數(shù)據(jù)項。
3,依照別的組件的要求,model indexes被model構(gòu)建。
4,使用index()時,如果指定了有效的父項的model index,那么返回得到的model index對應(yīng)于父項的某個孩子。
5,使用index()時,如果指定了無效的父項的model index,那么返回得到的model index對應(yīng)于頂層項的某個孩子。
6, 角色對一個數(shù)據(jù)項包含的不同類型的數(shù)據(jù)給出了區(qū)分。
使用Model Indexes
QDirModel *model = new QDirModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);
tring text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
以上的例子說明了從model中獲取數(shù)據(jù)的基本原則:
1,model的尺寸可以從rowCount()與columnCount()中得出。這些函數(shù)通常都需要一個表示父項的model index。
2,model indexes用來從model中訪問數(shù)據(jù)項,數(shù)據(jù)項用行,列,父項model index定位。
3, 為了訪問model頂層項,可以使用QModelIndex()指定。
4, 數(shù)據(jù)項為不同的角色提供不同的數(shù)據(jù)。為了獲取數(shù)據(jù),除了model index之外,還要指定角色。
創(chuàng)建新的Models
介紹
model/view組件之間功能的分離,允許創(chuàng)建model利用現(xiàn)成的views。這也可以使用標準的功能 圖形用戶接口組件像QListView,QTableView和QTreeView來顯示來自各種數(shù)據(jù)源的數(shù)據(jù)為。
QAbstractListModel類提供了非常靈活的接口,允許數(shù)據(jù)源以層次結(jié)構(gòu)的形式來管理信息,也允許以某種
方式對數(shù)據(jù)進行插入、刪除、修改和存儲。它也提供了對拖拽操作的支持。
QAbstractListModel與QAbstractTableModel為簡單的非層次結(jié)構(gòu)的數(shù)據(jù)提供了接口,對于比較簡單的list和table models來說,這是不錯的一個開始點。
設(shè)計一個Model
當我們?yōu)榇嬖诘臄?shù)據(jù)結(jié)構(gòu)新建一個model時,首先要考慮的問題是應(yīng)該選用哪種model來為這些數(shù)據(jù)提供接口。
假如數(shù)據(jù)結(jié)構(gòu)可以用數(shù)據(jù)項的列表或表來表示,那么可以考慮子類化QAbstractListModel或QAbstractTableModel
,既然這些類已經(jīng)合理地對許多功能提供缺省實現(xiàn)。
然而,假如底層的數(shù)據(jù)結(jié)構(gòu)只能表示成具有層次結(jié)構(gòu)的樹型結(jié)構(gòu),那么必須得子類化QAbstractItemModel。
無論底層的數(shù)據(jù)結(jié)構(gòu)采取何種形式,在特定的model中實現(xiàn)標準的QAbstractItemModel API總是一個不錯的主意,這使得可以使用更自然的方式對底層的數(shù)據(jù)結(jié)構(gòu)進行訪問。這也使得用數(shù)據(jù)構(gòu)建model 更為容易,其他
的model/view組件也可以使用標準的API與之進行交互。
一個只讀model示例
這個示例實現(xiàn)了一個簡單的,非層次結(jié)構(gòu)的,只讀的數(shù)據(jù)model,它基于QStringistModel類。它有一個QStringList作為它內(nèi)部的數(shù)據(jù)源,只實現(xiàn)了一些必要的接口。為了簡單化,它子類化了QAbstractListModel,這個基類提供了合理的缺省行為,對外提供了比QAbstractItemModel更為簡單的接口。當我們實現(xiàn)一個model時,不要忘了QAbstractItemModel本身不存儲任何數(shù)據(jù),它僅僅提供了給views訪問
數(shù)據(jù)的接口。
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(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;
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需要的,在標題中顯示的數(shù)據(jù)。
因為這是一個非層次結(jié)構(gòu)的model,我們不必考慮父子關(guān)系。假如model具有層次結(jié)構(gòu),我們也應(yīng)該實現(xiàn)index()與parent()函數(shù)。
Model的尺寸
我們認為model中的行數(shù)與string list中的string數(shù)目一致:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
在缺省情況下,從QAbstractListModel派生的model只具有一列,因此不需要實現(xiàn)columnCount()。
Model 標題與數(shù)據(jù)
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
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 StringListModel::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);
}
一個數(shù)據(jù)項可能有多個角色,根據(jù)角色的不同輸出不同的數(shù)據(jù)。上例中,model中的數(shù)據(jù)項只有一個角色 ,
DisplayRole,然而我們也可以重用提供給DisplayRole的數(shù)據(jù),作為別的角色使用,如我們可以作為ToolTipRole來用。
可編輯的model
上面我們演示了一個只讀的model,它只用于向用戶顯示,對于許多程序來說,可編輯的list model可能更有用。我們只需要給只讀的model提供另外兩個函數(shù)flags()與setData()的實現(xiàn)。下列函數(shù)聲明被添加到類定義中:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
讓model可編輯
delegate會在創(chuàng)建編輯器之前檢查數(shù)據(jù)項是否是可編輯的。model必須得讓delegate知道它的數(shù)據(jù)項是可
編輯的。這可以通過為每一個數(shù)據(jù)項返回一個正確的標記得到,在本例中,我們假設(shè)所有的數(shù)據(jù)項都是
可編輯可選擇的:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
我們不必知道delegate執(zhí)行怎樣實際的編輯處理過程,我們只需提供給delegate一個方法,delegate會使用它對model中的數(shù)據(jù)進行設(shè)置。這個特殊的函數(shù)就是setData():
bool StringListModel::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;
}
當數(shù)據(jù)被設(shè)置后,model必須得讓views知道一些數(shù)據(jù)發(fā)生了變化,這可通過發(fā)射一個dataChanged() 信號實現(xiàn)。
因為只有一個數(shù)據(jù)項發(fā)生了變化,因此在信號中說明的變化范圍只限于一個model index。
插入,刪除行
在model中改變行數(shù)與列數(shù)是可能的。當然在本列中,只考慮行的情況,我們只需要重新實現(xiàn)插入、刪除
的函數(shù)就可以了,下面應(yīng)在類定義中聲明:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
既然model中的每行對應(yīng)于列表中的一個string,因此,insertRows()函數(shù)在string list 中指定位置插入一個空string,
父index通常用于決定model中行列的位置,本例中只有一個單獨的頂級項,困此只需要在list中插入空string。
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
}
beginInsertRows()通知其他組件行數(shù)將會改變。endInsertRows()對操作進行確認與通知。
返回true表示成功。
刪除操作與插入操作類似:
bool StringListModel::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;
}
View 類
概念
在model/view架構(gòu)中,view從model中獲得數(shù)據(jù)項然后顯示給用戶。數(shù)據(jù)顯示的方式不必與model提供的表示方式相同,可以與底層存儲數(shù)據(jù)項的數(shù)據(jù)結(jié)構(gòu)完全不同。
內(nèi)容與顯式的分離是通過由QAbstractItemModel提供的標準模型接口,由QAsbstractItemview提供的標準視圖接口共同實現(xiàn)的。普遍使用model index來表示數(shù)據(jù)項。view負責(zé)管理從model中讀取的數(shù)據(jù)的外觀布局。
它們自己可以去渲染每個數(shù)據(jù)項,也可以利用delegate來既處理渲染又進行編輯。
除了顯示數(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可以維護單獨使用的selections,也可以在多個views之間共享。有些views,如QTableView和QTreeView,除數(shù)據(jù)項之外也可顯示標題(Headers),標題部分通過一個view來實現(xiàn),QHeaderView。標題與view一樣總是從相同的model中獲取數(shù)據(jù)。從 model中獲取數(shù)據(jù)的函數(shù)是QabstractItemModel::headerDate(),一般總是以表單的形式中顯示標題信息??梢詮腝HeaderView子類化,以實現(xiàn)更為復(fù)雜的定制化需求。
使用現(xiàn)成的view
Qt提供了三個現(xiàn)成的view 類,它們能夠以用戶熟悉的方式顯示model中的數(shù)據(jù)。QListView把model中的數(shù)據(jù)項以一個簡單的列表的形式顯示,或是以經(jīng)典的圖標視圖的形式顯示。QTreeView把model中的數(shù)據(jù)項作為具有層次結(jié)構(gòu)的列表的形式顯示,它允許以緊湊的深度嵌套的結(jié)構(gòu)進行顯示。QTableView卻是把model中的數(shù)據(jù)項以表格的形式展現(xiàn),更像是一個電子表格應(yīng)用程序的外觀布局。
以上這些標準view的行為足以應(yīng)付大多數(shù)的應(yīng)用程序,它們也提供了一些基本的編輯功能,也可以定制特殊的需求。
使用model
以前的例子中創(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代替了當前這個model,這些代碼也會照樣工作。
//QListView提供的列表視圖足以滿足當前這個model的需要了。
QListView *view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
view會渲染model中的內(nèi)容,通過model的接口來訪問它的數(shù)據(jù)。當用戶試圖編輯數(shù)據(jù)項時,view會使用缺省的delegate來提供一個編輯構(gòu)件。
一個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)中信號、槽機制的使用意味著model中發(fā)生的改變會傳遞中聯(lián)結(jié)的所有view中,這保證了
不管我們使用哪個view,訪問的都是同樣的一份數(shù)據(jù)。
上面的圖展示了一個model上的兩個不同的views,盡管在不同的view中顯示的model中的數(shù)據(jù)是一致的,每個
view都維護它們自己的內(nèi)部選擇模型,但有時候在某些情況下,共享一個選擇模型也是合理的。
處理數(shù)據(jù)項的選擇
view中數(shù)據(jù)項選擇機制由QItemSelectionModel類提供。所有標準的view缺省都構(gòu)建它們自己的選擇模型,
以標準的方式與它們交互。選擇模型可以用selectionModel()函數(shù)取得,替代的選擇模型也可以通過
setSelectionModel()來設(shè)置。當我們想在一個model上提供多個一致的views時,這種對選擇模型的控制能力非常有用。通常來講,除非你子類化一個model或view,你不必直接操縱selections的內(nèi)容。
多個views之間共享選擇
接著上邊的例子,我們可以這樣:
secondTableView->setSelectionModel(firstTableView->selectionModel());
現(xiàn)在所有views都在同樣的選擇模型上操作,數(shù)據(jù)與選擇項都保持同步。
上面的例子中,兩個view的類型是相同的,假如這兩個view類型不同,那么所選擇的數(shù)據(jù)項在每個view
中的表現(xiàn)形式會有很大的不同。例如,在一個table view中一個連續(xù)的選擇,在一個tree view中表現(xiàn)出
來的可能會是幾個高亮的數(shù)據(jù)項片斷的組合。
在views中選擇數(shù)據(jù)項
概念
用于新的view類中的選擇模型比Qt3中的模型有了很大的改進。它為基于model/view架構(gòu)的選擇提供了更為全面的描述。盡管對提供了的views來說,負責(zé)操縱選擇的標準類已經(jīng)足以應(yīng)付,但是你也可以創(chuàng)建特定的選擇模型來滿足你特殊的需求。
關(guān)于在view被選擇的數(shù)據(jù)項的信息保持在QItemSelectionModel類的實例中。它也為每個獨立的model中的數(shù)據(jù)項維護model indexes信息,與任何views都關(guān)聯(lián)關(guān)系。既然一個model可用于多個views,那么在多個views之間共享選擇信息也是可以做到的,這使得多個views可以以一致的方式進行顯示。
選擇由多個選擇范圍組成。通過僅僅記錄開始model indexes與結(jié)束model indexes,最大化地記錄了可以選擇的范圍。非連續(xù)選擇數(shù)據(jù)項由多個選擇范圍來描述。選擇模型記錄model indexes的集合來描述一個選擇。最近選擇的數(shù)據(jù)項被稱為current selection。應(yīng)用程序可以通過使用某種類型的選擇命令來修改選擇的效果。
在進行選擇操作時,可以把QItemSelectionModel看成是model中所有數(shù)據(jù)項選擇狀態(tài)的一個記錄。一旦建立一個選擇模型,所有項的集合都可以選擇,撤消選擇,或者選擇狀態(tài)進行切換而不需要知道哪個數(shù)據(jù)項是否已經(jīng)被選擇過。所有被選擇的項的indexes在任何時候都可以得到,通過信號槽機制可以通知別的組件發(fā)生的變化。
使用選擇模型
標準view類提供了缺省的選擇模型,它們可以在大次數(shù)程序中使用。一個view中的選擇模型可以通過調(diào)用view的函數(shù)selectionModel()取得,也可以通過setSelectionModel()在多個views之間共享選擇模型,因此總的來說構(gòu)建一個新的模型一般情況不太必要。
通過給QItemSelection指定一個model,一對model indexes,可以創(chuàng)建一個選擇。indexes的用法依賴于給定的model,這兩個indexes被解釋成選擇的區(qū)塊中的左上角項和右下角項。model中的項的選擇服從于選擇模型。
選擇項
構(gòu)建一個table model ,它有32個項,用一個table view進行顯示:
TableModel *model = new TableModel(8, 4, &app);
QTableView *table = new QTableView(0);
table->setModel(model);
QItemSelectionModel *selectionModel = table->selectionModel();
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
結(jié)果如下:
讀取選擇狀態(tài)
存儲在選擇模型中indexes可以用selectionIndexes()函數(shù)來讀取。它返回一個未排序的model indexes列表,我們可以遍歷它,如果我們知道他們關(guān)聯(lián)于哪個model的話。
QModelIndexList indexes = selectionModel->selectedIndexes();
QModelIndex index;
foreach(index, indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
選擇模型在選擇發(fā)生變化時會發(fā)出信號。這用于通知別的組件包括整體與當前焦點項所發(fā)生的變化。我們可以連接selectionChanged()信號到一個槽,檢查當信號產(chǎn)生時哪些項被選擇或被取消選擇。這個槽被調(diào)用時帶有兩個參數(shù),它們都是QItemSelection對象,一個包含新被選擇的項,另一個包含新近被取消選擇的項。下面的代碼演示了給新選擇的項添加數(shù)據(jù)內(nèi)容,新近被取消選擇的項的內(nèi)容被清空。
void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndex index;
QModelIndexList items = selected.indexes();
foreach (index, items) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
foreach (index, items)
model->setData(index, "");
}
也可以通過響應(yīng)currentChanged()信號來跟蹤當前焦點項.對應(yīng)的槽就有兩個接收參數(shù),一個表示之前的焦點,另一個表示當前的焦點。
void MainWindow::changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous)
{
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
}
更新選擇
選擇指令是通過選擇標志提供的,它被定義在QItemSelectionModel::SelectionFlag中。常用的有Select標記,Toggle標記,Deselect標記,Current標記,Clear標記,其意義一目了然。沿上面例子的結(jié)果執(zhí)行以下代碼:
QItemSelection toggleSelection;
topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);
結(jié)果如下:
缺省情況下,選擇指令只針對單個項(由model indexes指定)。然而,選擇指令可以通過與另外標記的結(jié)合來改變整行和整列。舉例來說,假如你只使用一個index來調(diào)用select(),但是用Select標記與Rows標記的組合,那么包括那個項的整行都將被選擇??匆韵率纠?br /> QItemSelection columnSelection;
topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());
columnSelection.select(topLeft, bottomRight);
selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);
QItemSelection rowSelection;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());
rowSelection.select(topLeft, bottomRight);
selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows);
結(jié)果如下
選擇模型中所有項
為了選擇model中的所有項,必須先得創(chuàng)建一個選擇,它包括當前層次上的所有項:
QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent);
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
頂級index可以這樣:
QModelIndex parent = QModelIndex();
對具有層次結(jié)構(gòu)的model來說,可以使用hasChildren()函數(shù)來決定給定項是否是其它項的父項。
Qt Model/View 學(xué)習(xí)筆記 2010-06-28 12:29:10 閱讀28 評論0 字號:大中小 訂閱
Qt Model/View 學(xué)習(xí)筆記
Delegate 類
概念
與MVC模式不同,model/view結(jié)構(gòu)沒有用于與用戶交互的完全獨立的組件。一般來講, view負責(zé)把數(shù)據(jù)展示
給用戶,也處理用戶的輸入。為了獲得更多的靈性性,交互通過delegagte執(zhí)行。它既提供輸入功能又負責(zé)渲染view中的每個數(shù)據(jù)項。 控制delegates的標準接口在QAbstractItemDelegate類中定義。Delegates通過實現(xiàn)paint()和sizeHint()以達到渲染內(nèi)容的目的。然而,簡單的基于widget的delegates,可以從QItemDelegate子類化,而不是QAbstractItemDelegate,這樣可以使用它提供的上述函數(shù)的缺省實現(xiàn)。delegate可以使用widget來處理編輯過程,也可以直接對事件進行處理。
使用現(xiàn)成的delegate
Qt提供的標準views都使用QItemDelegate的實例來提供編輯功能。它以普通的風(fēng)格來為每個標準view渲染數(shù)據(jù)項。這些標準的views包括:QListView,QTableView,QTreeView。所有標準的角色都通過標準views包含的缺省delegate進行處理。一個view使用的delegate可以用itemDelegate()函數(shù)取得,而setItemDelegate() 函數(shù)可以安裝一個定制delegate。
一個簡單的delegate
這個delegate使用QSpinBox來提供編輯功能。它主要想用于顯示整數(shù)的models上。盡管我們已經(jīng)建立了一個基于整數(shù)的table model,但我們也可以使用QStandardItemModel,因為delegate可以控制數(shù)據(jù)的錄入。我們又建了一個table view來顯示model的內(nèi)容,用我們定制的delegate來編輯。
我們從QItemDelegate子類化,這樣可以利用它缺省實現(xiàn)的顯示功能。當然我們必需提供函數(shù)來管理用于編輯的widget:
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
需要注意的是,當一個delegate創(chuàng)建時,不需要安裝一個widget,只有在真正需要時才創(chuàng)建這個用于編輯的widget。
提供編輯器
在這個例子中,當table view需要提供一個編輯器時,它要求delegate提供一個可用于編輯的widget,它應(yīng)該適用于當前正被修改的數(shù)據(jù)項。這正是createEditor()函數(shù)應(yīng)該實現(xiàn)的:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}
我們不需要跟蹤這個widget的指針,因為view會在不需要時銷毀這個widget。我們也給編輯安裝了delegate缺省的事件過濾器,這提供了用戶期望的標準編輯快捷鍵。view通過我們定義相應(yīng)的函數(shù)來保證編輯器的數(shù)據(jù)與幾何布局被正確的設(shè)置。我們也可以根據(jù)不同的model index來創(chuàng)建不同的編輯器,比如,我們有一列整數(shù),一列字符串,我們可以根據(jù)哪種列被編輯來創(chuàng)建一個QSpinBox或是QLineEdit。delegate必需提供一個函數(shù)把model中的數(shù)據(jù)拷貝到編輯器中。
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QSpinBox *spinBox = static_cast
spinBox->setValue(value);
}
向model提交數(shù)據(jù)
這需要我們實現(xiàn)另外一個函數(shù)setModelData():
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value);
}
標準的QItemDelegate類當它完成編輯時會發(fā)射closeEditor()信號來通知view。view保證編輯器widget關(guān)閉與銷毀。本例中我們只提供簡單的編輯功能,因此不需要發(fā)送個信號。
更新編輯器幾何布局
delegate負責(zé)管理編輯器的幾何布局。這些幾何布局信息在編輯創(chuàng)建時或view的尺寸位置發(fā)生改變時,
都應(yīng)當被提供。幸運的是,view通過一個view option可以提供這些必要的信息。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
編輯提示
編輯完成后,delegate會給別的組件提供有關(guān)于編輯處理結(jié)果的提示,也提供用于后續(xù)編輯操作的一些提示。
這可以通過發(fā)射帶有某種hint的closeEditor()信號完成。這些信號會被安裝在spin box上的缺省的QItemDelegate事件過濾器捕獲。對這個缺省的事件過濾來講,當用戶按下回車鍵,delegate會對model中的數(shù)據(jù)進行提交,并關(guān)閉spin box。
我們可以安裝自己的事件過濾器以迎合我們的需要,例如,我們可以發(fā)射帶有EditNextItem hint的
closeEditor()信號來實現(xiàn)自動開始編輯view中的下一項。
if ($ != jQuery) { $ = jQuery.noConflict(); }