Qt Model View 框架


Model-View及Qt實現

Model-View-Controller架構最早出現在SmallTalk語言中,至今出現了很多變體。

Model是負責維護數據(如管理數據庫),View負責顯示與用戶交互(如各種界面),Controller將控制業務邏輯。這種分層的做法在大型程序中使得數據、邏輯與界面分離,便於維護更新。

Qt引入了與MVC架構相似的模式Model-View架構,並加入了代理(delegate),用於自定義數據的編輯和渲染。

因為架構中的Model以表格的抽象方式訪問數據,事實上並非Model-View的最佳選擇。

Qt中Model,View,Delegate均由抽象類定義,並通過信號槽進行交互:

  • Model的信號通知View數據發生了改變

  • View的信號通知用戶交互事件

  • Delegate的信號在編輯數據時用於通知Model和View的狀態

QAbstractItemModel是所有Model的基類,它定義了View和Delegate訪問數據的接口。

模型並不存儲數據,而是通過與數據源交互得到數據。數據源包括數據庫,文件,內存中的對象以及IO設備。

Qt 內置了許多標准模型:

  • QStringListModel:存儲簡單的字符串列表。

  • QStandardItemModel:可以用於樹結構的存儲,提供了層次數據。

  • QFileSystemModel:本地系統的文件和目錄信息。

  • QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel:存取數據庫數據。

如果這些標准模型不能滿足需要,可以繼承QAbstractItemModel創建新的Model。

QAbstractListModel或QAbstractTableModel提供了一些基本的實現,繼承它們可能是更好的選擇。

QAbstractItemView是所有View的基類。Qt 還提供了一系列標准的視圖:QListView用於顯示列表,QTableView用於顯示表格,QTreeView用於顯示層次數據,它們與List,Table, Tree這些布局在一定程度上對應。

QAbstractItemDelegate則是所有委托的抽象基類。自 Qt 4.4 之后,默認的委托實現是QStyledItemDelegate。但是,QStyledItemDelegate和QItemDelegate都可以作為視圖的編輯器,二者的區別在於,QStyledItemDelegate使用當前樣式進行繪制。在實現自定義委托時,推薦使用QStyledItemDelegate作為基類,或者結合 Qt style sheets。

QListWidget,QtreeWidget,QTableWidget

基於MVC架構,Qt提供了QListWidget,QtreeWidget,QTableWidget三個可視化組件,它們均繼承了相應的View類,集成了Model-View的功能,程序員可以方便地使用這些類進行開發標准的List、Tree和Table組件。

QListWidget

QListWidget用於顯示列表,QListWidget的創建方法與其它Widget一樣需要指定一個父組件QListWidget(QWidget * parent = 0)

QListWidget的列表項是QListWidgetItem對象,QListWidgetItem可以存儲和顯示圖標及文本。

在建立QListWidgetItem 對象時指定一個QListWidget對象作為父對象即可將列表項添加到列表組件最后,調用 void addItem(QListWidgetItem * item)實例方法也可以將列表項添加到列表尾。

void insertItem(int row, QListWidgetItem * item);系列重載函數可以將菜單項添加到指定的位置。

void insertItems(int row, const QStringList & labels)void addItems(const QStringList & labels)方法則可以批量添加列表項。

QListWidgetItem可以在創建對象的時候指定圖標和文本。

QListWidgetItem(const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type),也可以通過setIcon(const QIcon & icon)setText(const QString & text)設置。

對於Item的顯示風格有很多選擇,詳情可以查閱Qt文檔。

QListWidget繼承了QListView的setViewMode函數可以設置列表的顯示風格。QListWidget定義了一系列信號,它們會在列表項被點擊等事件發生時發出。

QTreeWidget

QTreeWidget用於顯示樹狀組件,其用法與QListWidget非常相似,它的項是QTreeWidgetItem對象。

QTreeWidgetItem有非常多的重載構造函數,QTreeWidgetItem可以接受QTreeWidget或QTreeWidget作為父對象,由此可以創建樹狀結構。

QTreeWidget和QTreeWidgetItem中常使用QList來創建多根樹。

QTreeWidget可以用來顯示類似Windows資源管理器的界面。

void setColumnCount(int columns)可以設置列數目,而QTreeWidget的使用QStringList存儲文本也是為了存儲多列數據。

void setHeaderLabels(const QStringList & labels)可以設定列名稱。

QTableWidgets

QTableWidget的用法與前兩個相似,其項目類為QTableWidgetItem。初始化QTableWidget對象時需要先指定對象的寬和高,使用void setItem(int row, int column, QTableWidgetItem * item)將項目添加到指定單元格

Model

標准Model

QStringListModel

QStringListModel是最簡單的模型類,具備向視圖提供字符串數據的能力。QStringListModel是一個可編輯的模型,可以為組件提供一系列字符串作為數據,可以將其看作是封裝了QStringList的模型。

QStringListModel很多時候都會作為QListView或者QComboBox這種只有一列的視圖組件的數據模型。

void setStringList(const QStringList & strings);用於設置QStringListModel所維護的StringList,使用View的void setModel(QAbstractItemModel * model)函數將View與Model關聯。

QFileSystemModel

QFileSystemModel的作用是維護一個目錄的信息。因此,它不需要保存數據本身,而是保存這些在本地文件系統中的實際數據的一個索引。我們可以利用QFileSystemModel訪問文件系統信息、甚至通過模型來修改文件系統。QTreeView是最適合應用QFileSystemModel的視圖。

model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());

treeView = new QTreeView(this);
treeView->setModel(model);
treeView->setRootIndex(model->index(QDir::currentPath()));

自定義Model

QAbstractItemModel定義了Model的標准接口。QAbstractItemModel及其派生類均以表格的形式提供訪問數據。

自定義Model需要繼承QAbstractItemModel並重寫下列函數:

  • data()

    QVariant QAbstractItemModel::data(const QModelIndex & index,
    int role = Qt::DisplayRole) const

訪問數據的接口,QModelIndex是存儲Model表格的索引,index.row()index.column()可以得到索引中指向的行或列。

role是一個枚舉代表了數據的渲染方式,QVariant是變體型可以被轉換為任意Qt兼容的數據類型。

  • setData()

    bool QAbstractItemModel::setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)

寫入數據的接口。

  • dataChanged信號

    void QAbstractItemModel::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())

在數據被改變后由setData()方法發送dataChanged()信號,通知視圖刷新數據。使用兩個QModelIndex通知刷新的范圍。

此外還有一些工具函數:

  • rowCount() / columnCount()

返回模型的行數 / 列數。

  • headerData()

返回表頭信息。

示例:

顯示匯率表的應用,底層的數據使用一個QMap<QString, double>類型的數據,作為key的QString是貨幣名字,作為value的double是這種貨幣對美元的匯率

CurrencyModel.h

class CurrencyModel : public QAbstractTableModel 
{ 
	Q_OBJECT
public: 
        CurrencyModel(QObject *parent = 0); 
        void setCurrencyMap(const QMap<QString, double> &map); 
        int rowCount(const QModelIndex &parent) const; 
        int columnCount(const QModelIndex &parent) const; 
        QVariant data(const QModelIndex &index, int role) const; 
		bool setData(const QModelIndex &index, const QVariant &value, int role); 
        QVariant headerData(int section, Qt::Orientation orientation, int role) const; 
private: 
        QString currencyAt(int offset) const; 
        QMap<QString, double> currencyMap; 
};

CurrencyModel.cpp

CurrencyModel::CurrencyModel(QObject *parent) 
    : QAbstractTableModel(parent) 
{ 
} 
 
int CurrencyModel::rowCount(const QModelIndex & parent) const 
{ 
        return currencyMap.count(); 
} 
 
int CurrencyModel::columnCount(const QModelIndex & parent) const 
{ 
        return currencyMap.count(); 
} 
 
QVariant CurrencyModel::data(const QModelIndex &index, int role) const 
{ 
        if (!index.isValid()) 
                return QVariant(); 
 
        if (role == Qt::TextAlignmentRole) { 
                return int(Qt::AlignRight | Qt::AlignVCenter); 
        } 
		else if (role == Qt::DisplayRole) { 
                QString rowCurrency = currencyAt(index.row()); 
                QString columnCurrency = currencyAt(index.column()); 
                if (currencyMap.value(rowCurrency) == 0.0) 
                        return "####"; 
                double amount = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency);
                return QString("%1").arg(amount, 0, 'f', 4); 
        } 
        return QVariant(); 
}

bool CityModel::setData(const QModelIndex &index, const QVariant &value, int role) 
{ 

	if (!index.isValid() || role != Qt::EditRole) {
		return false;
	}
    if ( currencyAt(index.column()) == "USD") { 
			QString currency = currencyAt(index.row());
			currencyMap[currency] = toInt(value);

            QModelIndex columnIndexBegin = createIndex(index.column(),0);
			QModelIndex columnIndexEnd = createIndex(index.column(),currencyMap.count()- 1);
            emit dataChanged(columnIndexBegin, columnIndexEnd);

			QModelIndex rawIndexBegin = createIndex(0,index.raw());
			QModelIndex rawIndexEnd = createIndex(currencyMap.count()- 1,index.raw());
            emit dataChanged(rawIndexBegin, rawIndexEnd); 
            return true; 
    } 
    return false; 
}  
 
QVariant CurrencyModel::headerData(int section, Qt::Orientation orientation, int role) const 
{ 
        if (role != Qt::DisplayRole) 
                return QVariant(); 
        return currencyAt(section); 
} 
 
void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map) 
{ 
        currencyMap = map; 
        reset(); 
} 
 
QString CurrencyModel::currencyAt(int offset) const 
{ 
        return (currencyMap.begin() + offset).key(); 
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM