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();
}