1. 程式人生 > >Qt模型/檢視原理(2):自定義模型

Qt模型/檢視原理(2):自定義模型

Qt模型/檢視原理(2):自定義模型

若對C++語法不熟悉,建議參閱《C++語法詳解》一書,電子工業出版社出版,該書語法示例短小精悍,對查閱C++知識點相當方便,並對語法原理作了詳細講解。

自定義模型至少需要實現QAbstractItemModel類中的以下5個純虛擬函式
columnCout()、rowCount()、index()、parent()、data()
為了能新增自已的資料到模型中,通常還需要重新實現setData()函式,若不重新實現setData()則無法向模型中新增資料。
自定義模型的基本原理及步驟如下
①、資料:實際資料可使用QList、陣列、整型、或單獨的一個類來儲存,資料可存放在模型中,也可存放在檔案等其他地方。
②、columnCout()、rowCount()、index()、parent()這4個函式用於共同設計模型的結構,因為使用索引表示模型中的某個資料項的位置,因此設計模型索引的結構就是設計模型的結構。具何如下
 行數和列數的設計:比如對於3行4列的表格結構,columnCout()應返回4,rowCount()應返回3;對於列表結構,則因為列表只需要1列,所以columncout()應總是返回1,rowCount()返回該列表的行數;對於樹形結構模型,則更復雜,需要根據當前父節點的情況進行判斷,以返回該父節點擁有的列數和行數。
 parent()函式(父模型索引)的設計:因為表格結構中的所有單元格都屬於同一個父索引之下,所以可把所有單元格都視為頂級節點,因此他們的父索引可以以無效模型索引作為父索引,因此parent()可以返回一個無效模型索引;對於列表結構的模型,同樣只需返回一個無效模型索引即可;對樹形結構模型,此步驟比較複雜,可以通過獲取當前節點的父節點及其行號和列號,然後使用createIndex()建立該父節點的索引。
 index()函式的設計:該函式用於為模型中的每個資料項建立索引,建立索引需要使用createIndex()函式,對於表格結構,只需向createIndex()函式傳遞當前資料項所在的行號、列號及使用的資料的指標即可;對於列表結構,則列號始終為0,其餘同表格結構;對於樹形結構,需要向該函式傳遞當前資料項位於父索引中的行號、列號及使用的資料的指標。
③、data()函式的返回值決定了檢視上應顯示的資料,也就是說在介面上使用者看到的資料是由該函式返回的,若返回不當的值,則資料無法正常顯示在檢視上,下面以使用內建的標準檢視類為例來講解怎樣設計此函式。data()函式會被檢視類呼叫多次,檢視每次都會向data傳遞一個不同的role(角色)引數值,然後檢視根據data返回的值,設定該role的資料, 因此在設計data函式的返回值時,需要根據role的不同值返回不同的資料,以使檢視正確的顯示。

示例8.4:自定義表格模型
注:本小節使用的QList的幾個成員函式,請參閱幫助文件,另外QModelIndexList是使用typedef重新命名後的QList。
//m.h檔案內容
#ifndef M_H
#define M_H
#include<QtWidgets>
#include <iostream>
using namespace std;
class D:public QAbstractItemModel{  //繼承抽象類QAbstractItemModel
    public:
    int col,rw;   		//表格模型的列數和行數
    QList<QVariant> s1;	//這是模型管理的資料
    QList<int> rol;    	//儲存資料的角色
    //讀者可把以一兩項單獨封裝在一個類(比如xxxItem)中進行管理,Qt內建模型就是這樣處理的
//建構函式表示建立一個i行j列的表格模型
    D(int i, int j):rw(i),col(j){	for(int k=0;k<col*rw;k++){ s1<<QVariant();	rol<<-1;} }
//①、返回表格模型的行數
int rowCount(const QModelIndex &parent = QModelIndex ()) const{return rw;	}
//②、返回表格模型的列數
int columnCount(const QModelIndex &parent =QModelIndex ()) const{return col;}
//③、返回表格模型的父索引,因為所有單元格都是頂級節點,所以使用無效節點作為父節點
QModelIndex parent(const QModelIndex &index) const{      return QModelIndex();}
//④、為每個單元格建立一個唯一的索引
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex ()) const
{	cout<<"index"<<endl;          //測試語句
    		//其他模型可能需要判斷傳遞進來的索引是否有效。
    		//if(!hasIndex(row,column,parent))return QModelIndex();
/*本示例僅需簡單的為傳遞進來的單元格建立一個索引即可,注意第3個引數的使用(請參閱createIndex()的原型),由此處可見,第3個引數其實質就是指指向模型實際所管理的資料。*/
    		return createIndex(row, column, (void*)&(s1.at(row*column+column)));     }
//⑤、返回檢視上顯示的資料,該函式會被檢視多次呼叫(注:其他虛擬函式同樣會被Qt呼叫多次)
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const{
//測試用,讀者可看到每次呼叫時Qt內部傳遞進來的role(角色)的值。
cout<<"data="<<role<<endl;
    		int i=index.row()*col+index.column();  //單元格所在資料s1中的位置
//以下對不同角色返回的資料會被檢視使用,以正確的顯示在其單元格中。
/*對於QListView和QTreeView必須對所在節點設定大小,否則這兩個檢視不會顯示任何內容(因為節點大小為0)*/
    		//if(role==Qt::SizeHintRole)return QSize(55,55);
/*設定角色CheckStateRole的資料。本示例返回一個無效的QVariant作為該角色的資料, 若返回有效值,會使單元格的左側顯示一個複選框。*/
    		if(role==Qt::CheckStateRole)return QVariant();
    		//設定單元格中資料的對齊方式。本示例為左側垂直居中對齊
    		if(role==Qt::TextAlignmentRole)   return Qt::AlignLeft|Qt::AlignVCenter;
   		//設定角色DecorationRole(圖片)的資料
    		if(role==Qt::DecorationRole)
        		//若使用者設定了DecorationRole角色的資料,則返回使用者為該單元格設定的資料。
        		if(rol.at(i)==Qt::DecorationRole)          return s1.at(i);
//若使用者為角色EditRole或DisplayRole角色設定了資料,則返回使用者為該單元格設定的資料。
    		if(role==Qt::EditRole|role==Qt::DisplayRole)
if(rol.at(i)==Qt::EditRole|rol.at(i)==Qt::DisplayRole)	return s1.at(i);
//其餘角色使用無效資料
    		return QVariant();		}	//data()結束
//⑥、過載setData以使使用者可以向模型中新增資料
  	bool setData(const QModelIndex &index,const QVariant &value, int role=Qt :: EditRole)
  	{/*使用資料value和角色role分別替換列表s1和rol中原有的值,使用replace便於下一個示例(拖放)的使用。*/
  		s1.replace(index.row()*col+index.column(),value);
  		rol.replace(index.row()*col+index.column(),role);
     	emit dataChanged(index, index);    	//資料改變後,需要傳送此訊號。
     	return true;   } 		};			//返回true,表示資料設定成功。
#endif // M_H


//m.cpp檔案內容
#include "m.h"
int main(int argc, char *argv[]){    QApplication aa(argc,argv);
    D *d=new D(3,3);   					//建立一個3行3列的表格模型
    QTableView *pv2=new QTableView;   	//使用表格檢視來顯示以上模型管理的資料
/*讀者也可使用以下檢視來顯示模型d中的資料,但要去掉data()中對if(role==Qt::SizeHintRole)的註釋,還要注意,若使用樹形檢視顯示,有可能會使程式崩潰,因為本示例未完整的實現樹形模型結構*/
    //QListView *pv2=new QListView;
//QTreeView *pv2=new QTreeView;

//向模型中新增資料
    d->setData(d->index(0,0,QModelIndex()),"111",Qt::DisplayRole);
    d->setData(d->index(1,0,QModelIndex()),222);
    d->setData(d->index(1,1,QModelIndex()),333);
    d->setData(d->index(2,1,QModelIndex()),QIcon("F:/1i.png"),Qt::DecorationRole);
    pv2->setModel(d);    pv2->resize(333,222);    pv2->show();    return aa.exec();  }

執行結果及說明,見圖8-16,由圖可見,資料顯示正確
在這裡插入圖片描述

示例8.5:自定義拖放操作
注:本示例需在上一示例的main函式中增加以下程式碼(啟用檢視的拖放功能)。本示例的函式原型及其作用見示例後文對這些函式的講解。
pv2->setDragEnabled(true);
    	pv2->viewport()->setAcceptDrops(true);
    	pv2->setDropIndicatorShown(true);
    	pv2->setDragDropMode(QAbstractItemView::DragDrop);

再在類D之中增下如下函式的定義

//①、重新實現flags函式,以開啟模型的拖放功能
Qt::ItemFlags flags(const QModelIndex &index) const{
return 	Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled|
Qt::ItemIsEnabled|Qt::ItemIsSelectable;	}
//②、重新實現supportedDropActions以使模型即可複製又可移動
Qt::DropActions supportedDropActions() const{return Qt::CopyAction|Qt::MoveAction;}
//③、重新實現canDropMimeData函式,以決定單元格是否允許放置拖動過來的資料。
bool canDropMimeData(const QMimeData *data,Qt::DropAction action, int row, int column,
            										const QModelIndex &parent) const
{  /*本示例parent引數有效,row和column是無效的,以下程式碼表示,第3行第3列不接受來自拖放的資料。*/
if(parent.row()==2&&parent.column()==2) return false;	else return true;	}
//④、重新實現mimeData函式,以編碼拖動時的資料(就是把拖動時的資料儲存起來,並返回)
QMimeData* mimeData(const QModelIndexList &indexes) const{
QMimeData *const pm=new QMimeData();		//建立一個QMimeData物件。
     /*注意:indexes是被拖動的資料項的索引,若同時拖動了多個數據項,則indexes才會包含多個數據項。*/
for(int k=0;k<indexes.size();k++){
    		const QModelIndex &i=indexes.at(k);
          if (i.isValid()) {
              QByteArray t;
//提取資料項的索引為i,角色為DisplayRole的資料
              QVariant v=data(i, Qt::DisplayRole);   
              t.append(v.toString());			//把提取的資料儲存在t之中
               pm->setData("text/plain", t); 	//然後把t中的資料以MIME型別的形式編碼到pm中。
                         }   //if結束
      }   //for結束
      return pm;	}		//返回編碼後的資料
//⑤、重新實現dropMimeData函式,以解碼拖動時的資料並對其進行處理
bool dropMimeData(const QMimeData *data, Qt::DropAction action,int row, int column, 
const QModelIndex &parent)
    {/* 本示例parent引數有效,row和column無效,以下程式碼表示儲存單元格所在資料s1中的位置。索引parent是拖動之後需要放置的位置的索引。*/
 	int i=parent.row()*col+parent.column();
    QByteArray t=data->data("text/plain");   //解碼由mimeData函式編碼後的資料。
	//若是複製操作,則把新位置parent處的資料重置為t
    if(action==Qt::CopyAction)  { setData(parent,t); }
//若是移動操作,則把新位置parent處的資料重置為t
    if(action==Qt::MoveAction) {  setData(parent,t);    }
    return true;   }		//返回true表示資料已處理。

執行結果及說明見圖8-17
在這裡插入圖片描述

8.2.3 拖放支援及MIME型別

拖放操作分為拖動(Drag)和放下或放置(Drop)兩種操作,當拖動時,需要把拖動的資料進行儲存,儲存資料這一步驟稱為編碼,資料以MIME型別(見6.6節)儲存為QMimeData型別的物件(稱為放置資料),當執行放下操作時,需要對放置資料進行處理,若需要使用這些資料,則需要把儲存的資料讀取出來(即解碼),然後進行處理;當然,若不需要這些放置資料,也可以直接丟棄。
1、自定義拖放操作的步驟
(1)、啟用檢視的拖放支援
標準檢視自動支援內部拖放,預設情況下,這些檢視的拖放功能未啟用,要啟用檢視的拖放支援,需進行以下設定:
 設定檢視的dragEnabled屬性為true以啟用專案拖放。
 拖放的模式:即檢視是否支援拖放,是否接受放置資料,是否接受來自自身的移動操作等。需要設定檢視的dragDropMode屬性。
 若要允許使用者在檢視內放置內部或外部專案,需將檢視的viewport()的acceptDrops屬性設定為true。
 若要向用戶顯示拖動的專案放置位置(通常該位置會以不同的形式顯示,比如以陰影形式顯示等),此時需設定檢視的showDropIndicator屬性。
 下面為具體的程式碼

    	v->setDragEnabled(1);           		//啟用檢視的拖放支援
       	v->setAcceptDrops(1); 				//接受放置資料
       	v->setDropIndicatorShown(1);		//顯示放置位置
v->setDragDropMode(QAbstractItemView::DragDrop);   //設定拖放模式為支援拖放

(2)、啟用資料項的拖放支援
重新實現QAbstractItemModel::flags()函式以提供合適的標誌來指示哪些專案可以被拖動,哪些專案將接受放置(Drop)。
(3)、編碼資料
重新實現QAbstractItemModel::mimeData()函式,把編碼後的資料儲存在該函式返回的QMimeData物件中。
(4)、 處理放置資料
1)、內建模型對放置資料的處理
不同型別的模型以不同的方式處理放置資料。列表模型和表格模型只提供了儲存資料項的平面結構。因此,他們可能會在資料放入檢視中的現有專案時插入新行(和列),或者可能會使用提供的一些資料覆蓋模型中專案的內容。樹模型通常能夠將包含新資料的子項新增到其基礎資料儲存中,因此就使用者而言,其行為將更具可預測性。
2)、對放置資料的自行處理
通過重新實現QAbstractItemModel::dropMimeData()函式來處理放置資料,此時需要對放置資料進行解碼(即讀取出QMimeData物件儲存的資料的內容),並將其插入模型的底層資料結構中(或進行其他處理),若該函式修改了資料項或模型的尺寸,則必須注意確保發出所有相關的訊號。因為該函式需要插入或刪除等操作,所以簡單的呼叫QAbstractItemModel子類中的已經實現了的setData(),insertRows()和insertColumns()等函式會更方便。另外,還可以使用dropMimeData()函式的預設實現來處理放置資料,dropMimeData()函式的預設實現不會覆蓋模型中的任何資料,它會將放置資料作為專案的同胞插入,或者作為該專案的子項插入。若要使用該函式的預設實現,就需要重新實現以下函式:insertRows()、insertColumns()、setData()、setItemData()。
2、自定義拖放操作的其他設定
(1)、模型能接受的拖放操作的型別
重新實現QAbstractItemModel::supportedDropActions() 函式可以指示模型能接受的拖放操作的型別(比如移動、複製等),雖然可以給出Qt::DropActions的值的任意組合,但需要編寫程式碼來支援它們,比如,對於Qt::MoveAction(移動)操作,則模型就需要提供QAbstractItemModel :: removeRows()的實現。
(2)、對多種MIME型別的支援
預設情況下,內建模型和檢視只支援一種MIME型別,即
application/x-qabstractitemmodeldatalist。
若要讓模型支援多種MIME型別,則需要重新實現QAbstractItemModel::mimeTypes(),該函式返回拖放操作時模型可以處理的MIME型別列表,注意,自定義資料型別必須宣告為元物件。
(3)、禁止在專案上放置資料
重新實現QAbstractItemModel:: canDropMimeData()函式可禁止在專案上放置資料。

3、下面為相關的函式及其原型(QAbstractItemModel類中的成員函式)
18)、

virtual Qt::ItemFlags flags(const QModelIndex &index) const;   //虛擬的
	返回索引index的資料項的標誌組合,預設實現為Qt::ItemIsEnabled | Qt::ItemIsSelectable,重新實現該函式可控制資料項的屬性(比如是否可被選中,可拖動等)。Qt::ItemFlag列舉見表8-2

在這裡插入圖片描述

19)、virtual QMimeData* mimeData(const QModelIndexList &indexes) const;  //虛擬的

 返回一個包含與索引列表indexes相對應的序列化資料項。若indexes為空或沒有支援的MIME型別,應返回0而不是序列化的空列表。該函式用於編碼拖放的資料。

20)、bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);  //虛擬的

 處理以動作action結束的拖放操作提供的資料data,即解碼放置資料data並進行處理。若資料已由模型處理,則返回true,否則返回false。引數row、column、parent表示操作結束時資料項的位置。Qt::DropAction列舉見表8-3
 該函式的預設實現不會覆蓋模型中的任何資料,它會將放置資料作為專案的同胞插入,或者作為該專案的子項插入,比如:對QTreeView中的資料項執行放置操作時,可能會導至新資料項被插入到row、column、parent所指資料項的子項,或作為該資料項的兄弟項。當row和column為-1時,通常意味著把資料項追加為parent的子項,若row和column大於或等於0,則意味著應在指定的row和column之前發生。
 該函式會呼叫mimeTypes()函式,以獲取可接受的MIME型別的列表。

21)、virtual bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const;    //虛擬的

 若模型可以接受放置資料data,則返回true。預設實現僅檢查data是否在mimeTypes()列表中至少有一種格式,以及action是否在模型的supportedDropActions()中。若要在row、column、parent上測試資料是否可以放置(drop),則需要重新實現此函式,若不需要進行此種測試,就不必重新實現該函式。Qt::DropAction列舉見表8-3

22)、virtual QStringList mimeTypes() const;   //虛擬的

 返回允許的MIME型別列表,預設情況下,內建模型和檢視使用的MIME型別為:application/x-qabstractitemmodeldatalist。若在自定義模型中實現拖放支援時,需要處理預設型別以外的格式,則需要重新實現此函式以返回需要的MIME型別列表。若重新實現此函式,還必須重新實現呼叫該函式的mimeData()和dropMimeData()函式。

23)、virtual Qt::DropActions supportedDragActions() const;   //虛擬的

 返回模型中資料支援的動作,預設實現返回supportedDropActions()。當發生拖放時,該函式的返回值被用作QAbstractItemView::startDrag()的預設值。

24)、virtual Qt::DropActions supportedDropActions() const;   //虛擬的

 返回此模型支援的放置動作,預設返回Qt::CopyAction。若重新實現此函式,還必須重新實現dropMimeData()函式以處理其他動作。Qt::DropAction列舉見表8-3
在這裡插入圖片描述

本文作者:黃邦勇帥(原名:黃勇)