代碼:
model的排序苟翻?
我們?cè)谶\(yùn)行demo4的時(shí)候發(fā)現(xiàn)韵卤,這些文件的排列是雜亂的,如果我們要讓他們變得有序該怎么辦呢崇猫?
最簡(jiǎn)單直接的想法是沈条,每次model改變的時(shí)候,我們對(duì)數(shù)據(jù)進(jìn)行重新排序诅炉,但是這對(duì)于需要多種排序方式或者需要提供正向反向排序的視圖來說蜡歹,設(shè)計(jì)這樣一個(gè)model是困難的。
model的篩選涕烧?
如果我們不想讓隱藏的文件顯示在我們的view中月而,該怎么辦呢?
最簡(jiǎn)單直接的想法是议纯,每次篩選的時(shí)候父款,我們需要從數(shù)據(jù)中剔除不符合條件的數(shù)據(jù),但是這種方式會(huì)對(duì)數(shù)據(jù)造成損失瞻凤,對(duì)于樹狀結(jié)構(gòu)憨攒,實(shí)現(xiàn)剔除也是比較麻煩的。
QSortFilterProxyModel——專為排序和篩選而生的Model
好在qt提供了這樣一個(gè)model解決上面兩個(gè)問題阀参,它就是QSortFilterProxyModel肝集。QSortFilterProxyModel是一種model所以它也可以結(jié)合view使用,但是與一般的model不同的是蛛壳,QSortFilterProxyModel它本身不像其它model一樣和數(shù)據(jù)掛鉤杏瞻,而是和model進(jìn)行綁定。view和model可以通過QSortFilterProxyModel這一個(gè)中介實(shí)現(xiàn)數(shù)據(jù)無損的排序和篩選衙荐,我們下面看看它具體是如何實(shí)現(xiàn)的捞挥。
以demo4中的model為例,我們希望我們的model可以按名稱進(jìn)行排序忧吟,并且文件夾在文件之前树肃,同時(shí)我們不希望顯示隱藏文件。
雖然QSortFilterProxyModel提供了一些默認(rèn)的接口用于排序和篩選瀑罗,但是考慮到我們的model并不那么規(guī)范胸嘴,數(shù)據(jù)也不是特別統(tǒng)一雏掠,我們采用繼承QSortFilterProxyModel并且實(shí)現(xiàn)自定義篩選和排序的形式,自定義QSortFilterProxyModel的篩選和排序劣像,首先要override這兩個(gè)方法:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
注意:這兩個(gè)方法中的index屬于source model乡话,我們?cè)趐roxy model中使用source model的index()方法時(shí),一定要小心耳奕,因?yàn)檫@個(gè)時(shí)候可能index可能無法正常創(chuàng)建绑青,這也是和開發(fā)這的代碼流程相關(guān)的,我們盡量使用parent的internalPointer(并結(jié)合sourceRow)進(jìn)行數(shù)據(jù)的檢索屋群,這樣調(diào)試起來也會(huì)方便一些闸婴。
我們看看它的具體實(shí)現(xiàn):
bool SortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
??? //qDebug()<<sourceParent;
??? //return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
??? FileNode *node = static_cast<FileNode*>(sourceParent.internalPointer());
??? if (node) {
??????? QString displayName = node->m_children.at(sourceRow)->m_info->displayName;
??????? return !displayName.startsWith(".");
??? } else {
??????? SimpleGVfsTreeModel *model = static_cast<SimpleGVfsTreeModel*>(sourceModel());
??????? QString displayName = model->m_root_node->m_children.at(sourceRow)->m_info->displayName;
??????? return !displayName.startsWith(".");
??? }
}
bool SortFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
??? qDebug()<<"lessThan"<<source_left.column();
??? FileNode *left = static_cast<FileNode*>(source_left.internalPointer());
??? FileNode *right = static_cast<FileNode*>(source_right.internalPointer());
??? switch (sortColumn()) {
??? case 0: {
??????? if (!left->hasChildren() && !right->hasChildren()) {
??????????? return left->m_info->displayName < right->m_info->displayName;
??????? }
??????? if (left->hasChildren() && right->hasChildren()) {
??????????? return left->m_info->displayName < right->m_info->displayName;
??????? }
??????? bool isAscendingOrder = sortOrder()==Qt::AscendingOrder;
??????? if (left->hasChildren()) {
??????????? return isAscendingOrder?true:false;
??????? }
??????? if (right->hasChildren()) {
??????????? return isAscendingOrder?false:true;
??????? }
??????? return left->m_info->displayName < right->m_info->displayName;
??? }
??? default:
??????? return QSortFilterProxyModel::lessThan(source_left, source_right);
??? }
}
我目前只針對(duì)第一列進(jìn)行了排序,大家可以自行完善之后幾列的排序芍躏。
另外邪乍,由于view通過proxy model展示數(shù)據(jù),我們從view中獲取的index是proxy model 的index对竣,如果需要獲取對(duì)應(yīng)source model的index庇楞,我們需要使用proxy model的mapToSource()方法。大家也可以嘗試仿照demo2對(duì)demo5進(jìn)行優(yōu)化否纬。
使用QSortFilterProxyModel需要注意Model設(shè)計(jì)的規(guī)范性
我們?cè)赿emo3中做這樣一個(gè)實(shí)驗(yàn)——將insertRow的row永遠(yuǎn)設(shè)置為0吕晌,你會(huì)發(fā)現(xiàn)view的結(jié)果任然沒有變化。把row永遠(yuǎn)設(shè)為0顯然是不合理的临燃,但是我們?nèi)匀坏玫搅苏_的結(jié)果睛驳,這是因?yàn)樵趍odel中,我們的index位置實(shí)際上已經(jīng)由我們的數(shù)據(jù)結(jié)構(gòu)定死了膜廊,insertRow就變成了一個(gè)觸發(fā)model更新和view重繪的方法柏靶,row這個(gè)參數(shù)自然就失去了它的意義。
然而在proxy model中溃论,如果我們不按照邏輯的進(jìn)行insert/remove,則會(huì)出現(xiàn)問題痘昌,這是因?yàn)閜roxy model在響應(yīng)model index的insert/remove時(shí)钥勋,是按照正常的insert/remove邏輯進(jìn)行的,如果我們的邏輯出現(xiàn)問題辆苔,展示出來的結(jié)果自然也會(huì)有問題算灸。
如果想要使用proxy model,則必須規(guī)范model修改的邏輯驻啤,當(dāng)然你也可以不使用proxy model菲驴,自己處理model的sorting,但是這對(duì)于多類型的sorting是很復(fù)雜的骑冗,對(duì)于開發(fā)者的考驗(yàn)很大赊瞬,而且如果你想要篩選數(shù)據(jù)先煎,則你也必須在model中刪除不符合條件的data,這對(duì)于想要重新進(jìn)行篩選的model來說是比較困難的巧涧,你要么從頭開始薯蝎,要么把原來的數(shù)據(jù)備份一份,再改變的時(shí)候再做一次映射谤绳,不管怎么樣都沒有使用proxy model來的方便占锯。
效率的取舍
QSortFilterProxyModel帶來方便的同時(shí),實(shí)際上也會(huì)帶來效率上的損失缩筛,拿上面的demo舉例消略,我們每次改變model,proxy model會(huì)進(jìn)行大量的重排和篩選操作瞎抛。
為了盡可能的減少上面的問題艺演,我們可以在proxy model進(jìn)行排序或者篩選的同時(shí)對(duì)model的數(shù)據(jù)進(jìn)行同樣的邏輯操作,然而這樣對(duì)于需要快速改變排序方式的view來說也是一個(gè)很大的負(fù)擔(dān)婿失。
不管怎么樣钞艇,使用QSortFilterProxyModel帶來的效率上的問題是不可避免的,我們需要做出取舍豪硅,尤其是在model數(shù)據(jù)過于龐大和復(fù)雜的時(shí)哩照,一點(diǎn)點(diǎn)效率上的問題都會(huì)被無限放大,我們圖方便的做法可能就會(huì)導(dǎo)致用戶體驗(yàn)受到影響了懒浮。
盡可能的發(fā)掘QSortFilterModel的潛力
當(dāng)然在大部分的情況下我更偏向于使用QSortFilterProxyModel進(jìn)行model的排序篩選與數(shù)據(jù)展示飘弧。QSortFilterProxyModel的潛力遠(yuǎn)不止我代碼中所寫的這些,我們甚至可以使用它進(jìn)行我們平時(shí)所認(rèn)為的“搜索功能”砚著,比如按文件名或者日期搜索等等次伶,它其實(shí)上是通過篩選得到的結(jié)果。然而稽穆,我們應(yīng)該能夠發(fā)現(xiàn)冠王,篩選受到了數(shù)據(jù)集,也就是sourceModel的限制舌镶,想要打破這一限制柱彻,我們需要獲取更加完善的數(shù)據(jù)集,而不是僅僅只局限于一個(gè)目錄之下餐胀。如果有時(shí)間哟楷,我會(huì)另外寫一篇關(guān)于如何實(shí)現(xiàn)文件搜索的文章,告訴大家如何綜合運(yùn)用QSortFilterProxyModel和搜索框架否灾。