前一篇水文中,老周演示了 QAbstractItemModel 抽象類的繼承方法。其實,在 Qt 的庫裡面,QAbstractItemModel 類也派生了兩個基類,能讓開發者繼承起來【稍稍】輕鬆一些。 這兩個類是 QAbstractListModel 和 QAbstractTableModel。 ...
前一篇水文中,老周演示了 QAbstractItemModel 抽象類的繼承方法。其實,在 Qt 的庫裡面,QAbstractItemModel 類也派生了兩個基類,能讓開發者繼承起來【稍稍】輕鬆一些。
這兩個類是 QAbstractListModel 和 QAbstractTableModel。
- QAbstractListModel類專門用來實現一維列表結構模型的。它實現了 index、parent 等方法,並且把 columnCount 方法變成了私有成員(一維列表不需要它)。繼承時直接實現 rowCount、data、setData 這幾個方法即可;
- QAbstractTableModel類專門用來實現二維表結構的模型。它實現了 index、parent 等方法。繼承時咱們要實現 rowCount、columnCount、data、setData 等方法。
雖然它幫咱們實現了一些成員,但實際上也省不了多功夫的。下麵咱們用 QAbstractListModel 舉例,和上篇中的一樣,操作一個 QList<int> 數據。畢竟一維的比較簡單,演示出來大伙都容易懂。
// 頭文件 #ifndef LIST_H #define LIST_H #include <QAbstractListModel> #include <QList> class CustListModel: public QAbstractListModel { Q_OBJECT public: explicit CustListModel(QObject* parent = nullptr); ~CustListModel(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: // 私有,存數據用 QList<int> m_list; }; #endif
先弄 rowCount 方法,返回總行數,這個簡單一點。
int CustListModel::rowCount(const QModelIndex &parent) const { if(parent.isValid()) { return 0; } return m_list.size(); }
實現 data 方法,獲取數據時用。
QVariant CustListModel::data(const QModelIndex &index, int role) const { if( role == Qt::DisplayRole || role == Qt::EditRole) { if(!index.isValid()) { return QVariant(); } // 獲取索引 int i = index.row(); // 返回指定索引處的值 return m_list.at(i); } return QVariant(); }
要返回 QList<T> 中指定的元素,用 at 方法,傳遞索引給它即可。
實現 setData 方法,編輯結束後用於更新數據的。
bool CustListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(!index.isValid()) return false; if(role == Qt::EditRole || role == Qt::DisplayRole) { // 解包數據 bool ok; int val = value.toInt(&ok); if(ok) { // 設置值 m_list.replace(index.row(), val);
// 發出信號
emit dataChanged(index,index,{role});
return true; } } return false; }
要修改某個索引處的值,用 replace 方法替換。
實現 flags 方法,表明該模型的列表項支持交互、編輯、被選擇。
Qt::ItemFlags CustListModel::flags(const QModelIndex &index) const { return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; }
ItemIsEnabled 表明列表項是活動的,用戶可操作的;ItemIsEditable 表明列表項可編輯;ItemIsSelectable 表示列表項可以選擇。
下麵這兩個方法是有點麻煩的,這裡先介紹一下。
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
row 參數表示要插入元素的索引,count 表示插入元素個數,插入元素後,原來的元素向後移動。例如:A、B、C、D,現在我要在B處插入兩個元素。那麼 row = 1,count = 2,B 處放入 E、F,B、C、D 向後移,變成 A、E、F、B、C、D。
然後,刪除元素的方法也類似。
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
row 是要刪除的索引,count 是連續要刪掉的元素個數。
insertRows 和 removeRows 方法的返回值含義相同:成功返回 true,失敗返回 false。
好,現在上代碼。
bool CustListModel::insertRows(int row, int count, const QModelIndex &parent) { if(parent.isValid()) return false; // 註意這裡!! beginInsertRows(parent, row, row + count - 1); // 開始插入 m_list.insert(row, count, 0); // 註意這裡!! endInsertRows(); return true; } bool CustListModel::removeRows(int row, int count, const QModelIndex &parent) { if(parent.isValid()) return false; // 註意這裡!!! beginRemoveRows(parent, row, row + count - 1); // 刪除 m_list.remove(row, count); // 註意這裡!!! endRemoveRows(); return true; }
在插入數據前必須調用 beginInsertRows 方法。註意這個方法的參數含義和 insertRows 有些不一樣。
void beginInsertRows(const QModelIndex &parent, int first, int last)
insertRows 方法是指定開始索引 ,然後連續加入多少個元素,而 beginInsertRows 方法是你插入元素後,它們在列表中的開始索引和結束索引。如 A、B、C,在B處插入兩個元素。開始索引是 1,結束索引是 2,即結束索引的計算方法是: endIndex = startIndex + count -1。插入元素結束後必須調用 endInsertRows 方法。這一對方法的作用是讓用戶界面上的視圖(如 QListView、QTableView 等)能及時做出響應。
同理,在刪除元素時,beginRemoveRows 和 endRemoveRows 方法也必須調用。beginRemoveRows 方法的參數與 beginInsertRows 方法一樣,也是索引的起止值。即要刪除元素的起始索引,和被刪除的最後一個元素的索引。如1、2、3、4,要刪除2、3、4,那麼起始索引是 1,結束索引 3。
模型已完工,下麵做個界面試一試。
int main(int argc, char** argv) { QApplication myapp(argc, argv); // 視窗 QWidget *window = new QWidget; // 佈局 QGridLayout *layout = new QGridLayout; window->setLayout(layout); // 列表視圖 QListView* view = new QListView(window); // 實例化模型 CustListModel* model = new CustListModel(window); // 設置到視圖中 view->setModel(model); layout->addWidget(view, 0, 0, 3, 1); // 按鈕 QPushButton* btnAdd=new QPushButton("新增", window); QPushButton* btnDel = new QPushButton("移除", window); QPushButton* btnShowAll = new QPushButton("顯示列表", window); layout->addWidget(btnAdd, 0, 1); layout->addWidget(btnDel, 1, 1); layout->addWidget(btnShowAll, 2, 1); layout->setColumnStretch(0, 1); // 連接clicked信號 QObject::connect( btnAdd, &QPushButton::clicked, [&view, &model, &window]() { bool res; int val = QInputDialog::getInt( window, //父視窗 "輸入", //視窗標題 "請輸入整數:", //提示文本 0, //預設值 0, //最小值 1000, //最大值 1, //步長值 &res //表示操作結果 ); if(!res) return; // 索引為count int i = model->rowCount(); // 添加一項 model->insertRow(i); // 獲取新索引 QModelIndex newIndex = model->index(i); // 設置此項的值 model->setData(newIndex, QVariant(val), Qt::DisplayRole); } ); QObject::connect( btnDel, &QPushButton::clicked, [&window, &view, &model]() { // 當前項 QModelIndex curri = view->currentIndex(); if(!curri.isValid()) return; // 刪除 model->removeRow(curri.row()); } ); QObject::connect( btnShowAll, &QPushButton::clicked, [&model, &window]() { // 獲取總數 int c = model->rowCount(); QString msg; for(int x = 0; x < c; x++) { // 獲取值 QVariant value = model->data(model->index(x), Qt::DisplayRole); if(value.isValid()) { int n = qvariant_cast<int>(value); msg += QString(" %1").arg(n); } } QMessageBox::information(window,"提示",msg); } ); // 標題 window->setWindowTitle("小型列表"); // 顯示 window->show(); return QApplication::exec(); }
QListView 組件用來顯示模型中的數據。三個按鈕分別用來添加、刪除和顯示列表項。最後一個按鈕顯示模型中的所有數據,它的作用是驗證模型是否工作正常。
“新增”按鈕被點擊後,先通過 QInputDialog.getInt 靜態方法,彈出一個輸入內容的對話框,然後把輸入的值添加到模型中。模型中添加元素用的就是 insertRow 方法——只插入一個元素,它調用了咱們上面實現的 insertRows 方法,只是把 count 參數設置為 1。這裡我實現的是新元素總是追加在列表最後,即新元素的行號等於 rowCount。
刪除元素時,QListView 組件的 currentIndex 方法返回當前選中項的索引。在單選模式下,99.9% 被選中的項是等同於當前項的。多選模式下就不好說了,所以,如果需要,可以訪問 selectionModel 方法,再通過 selectedIndexes 方法獲得被選項的列表。
看看效果。
QAbstractTableModel 類的用法也是差不多的,只不過要實現行和列的讀寫。
這兩個類哪怕幫咱們實現了一些代碼,但用起來還是麻煩,要是有不需要繼承、開箱就用的類就會好多了。Qt 還真的提供了這樣的類型。
首先說的是 QStringListModel,這個就是針對 QStringList 類的——字元串列表。QStringList 其實就是 QList<QString>。這個 QStringListModel 模型就是以字元串列表為數據源的。不需要繼承(除非你需要自定義),直接可用。
咱們演練一下。
// 頭文件 #ifndef DEMO_H #define DEMO_H #include <QWidget> #include <QPushButton> #include <QListView> #include <QVBoxLayout> class MyWindow : public QWidget { Q_OBJECT public: explicit MyWindow(QWidget* parent = nullptr); ~MyWindow(); private: QVBoxLayout* m_layout; QPushButton* m_btn; QListView* m_view; // 用於連接clicked信號 void onClicked(); }; #endif
三個組件:按鈕被點擊後載入數據;QListView 是視圖組件,顯示數據;QVBoxLayout 是佈局用的,界面元素沿垂直方向排列(縱向)。公共成員就是構造函數和析構函數。析構是空白的,實現構造函數即可。
#include "../include/demo.h" #include <QStringList> #include <QStringListModel> MyWindow::MyWindow(QWidget * parent) : QWidget::QWidget(parent) { m_layout = new QVBoxLayout(); setLayout(m_layout); // 按鈕 m_btn = new QPushButton("載入數據", this); m_layout->addWidget(m_btn); // 視圖 m_view = new QListView(this); m_layout->addWidget(m_view, 1); // 連接信號 connect(m_btn, &QPushButton::clicked, this, &MyWindow::onClicked); } MyWindow::~MyWindow() { }
下麵是重點,實現 onClicked 成員方法,構建數據併在視圖中顯示。
void MyWindow::onClicked() { // 創建字元串列表 QStringList list; list << "手榴彈" << "地雷" << "魚雷" << "燃燒彈"; // 創建模型實例 QStringListModel *model = new QStringListModel(list, this); // 設置給視圖 m_view->setModel(model); }
代碼不複雜。QStringList 和我們熟悉的 cout 一樣,可以用“<<”運算符寫入字元串。每一次運算就寫入一個元素,故上述代碼向列表填充了四個元素。接著實例化 QStringListModel 類,傳給 setModel 方法就可以與視圖關聯。這裡實例化模型類最好使用指針類型,可以將 this 傳給構造函數,這樣,當前視窗會管理它的生命周期,你不用擔心堆記憶體沒有釋放。
main 函數就沒什麼了,初始化應用程式,顯示視窗就完事了。
int main(int argc, char** argv) { QApplication app(argc, argv); MyWindow* win = new MyWindow(); win->setWindowTitle("示例"); win->resize(320, 275); win->show(); return QApplication::exec(); }
運行代碼後,點一下按鈕,就能看到字元串列表了。
QStringListModel 只針對字元串列表,對於複雜一些的列表項,還是不夠用的。所以咱們要請出下一位表演者—— QStandardItemModel。字面翻譯:標準列表模型。這個模型就不僅僅可以用字元串列表當源了,它可以設置更複雜的數據。比如圖標、顯示文本、背景色、文本顏色等。
為了便於使用,標準列表模型中的“項”由 QStandardItem 封裝。你可以先創建 QStandardItem 實例,設置好各種數據後,再添加進 QStandardItemModel 中。在設置子項,你可以不用 setData,而改用 setItem 方法。setItem 方法調用時只要指出行號、列號(如果是一維列表,調用另一個重載可以忽略列號)即可。
對於行標題、列標題,還有更方便的 setHorizontalHeaderLabels(水平)和 setVerticalHeaderLabels(垂直)方法,只要提供一個字元串列表,就能設置行列標題了。
現在,咱們瞭解一下 QStandardItem 這廝怎麼用,這個很重要,用好標準模型得靠它。它的構造函數傳參允許你指定顯示文本、圖標,或者總共多少行多少列。畢竟它有四個重載:
QStandardItem(); explicit QStandardItem(const QString &text); QStandardItem(const QIcon &icon, const QString &text); explicit QStandardItem(int rows, int columns = 1);
當然,可以調用無參數構造函數,然後再調用 set**** 方法來設置各項數據,比如:
- setText:設置要顯示的文本,就是你想讓用戶看到的文本;
- setIcon:設置圖標。不管是 QTableView 視圖還是 QTreeView 視圖,其實只有第一列才會顯示圖標。說直接點就是其他非第一列的項,你設置了圖標也是呵呵;
- setToolTip:設置工具提示。就是當滑鼠懸浮在上面時顯示的文本;
- setWhatsThis:設置 What the FK,不,是 What is This 幫助信息;
- setFont:設置顯示該項所使用的字體;
- setTextAlignment:設置顯示該項時,文本的對齊方式;
- setBackground:設置該項的背景顏色;
- setForeground:前景色,就是設置文本的顏色;
- setStatusTip:顯示在狀態欄上的提示信息(這個好像不常用);
- setDragEnabled、setDropEnabled:該項支不支持拖放操作;
- setAutoTristate:樹形視圖時用得上,比如父節點被 check 後,是否所有子節點也跟著 check;
- setCheckable:該項是否顯示一個 CheckBox 框,讓用戶可以在那裡勾來勾去;
- setSelectable:該項允許用戶選擇嗎;
- setEnabled:若是 true,用戶可以操作該項,如選中它;如為 false,此項為禁用狀態;
- setEditable:該項允許編輯嗎。
你要是覺得 setEnabled、setEditable、setSelectable 這些方法麻煩,可以用 setFlags 方法一次性解決,用 Or 運算組合的 Qt::ItemFlags 枚舉來設置。
當然,還有些成員我沒列出,看著那麼多成員方法好像很複雜,其實我們不一定全調用一遍的,看需要,不需要的可以不管的。
下麵咱們也弄個例子。這個例子,我直接從 QTableView 類派生。
// 頭文件 #ifndef CUST_H #define CUST_H #include<qwidget.h> #include<qstandarditemmodel.h> #include <qtableview.h> #include <qcolor.h> class MyTableView : public QTableView { Q_OBJECT public: explicit MyTableView(QWidget* parent = nullptr); ~MyTableView(); private: QStandardItemModel *model; }; #endif
私有成員是模型對象,然後,咱們實際要做的是實現構造函數,實例化模型,再往裡面塞數據。
MyTableView::MyTableView(QWidget *parent) : QTableView(parent) { // 實例化模型類 model = new QStandardItemModel(this); // 五行三列 // model->setRowCount(5); // model->setColumnCount(3); // 設置列標題 model->setHorizontalHeaderLabels({"學號", "姓名", "分數"}); // 好,我們開始準備數據 // 第一行 QStandardItem *cell00 = new QStandardItem(QIcon("a.png"), "2572"); QStandardItem *cell01 = new QStandardItem("小李"); QStandardItem *cell02 = new QStandardItem("58"); // 不合格,讓它顯示黃色 cell02->setBackground(QColor("yellow")); cell02->setForeground(QColor("red")); // 設置到模型中 model->setItem(0, 0, cell00); model->setItem(0, 1, cell01); model->setItem(0, 2, cell02); // 第二行 QStandardItem *cell10 = new QStandardItem(QIcon("a.png"), "2055"); QStandardItem *cell11 = new QStandardItem("小陳"); QStandardItem *cell12 = new QStandardItem("85"); model->setItem(1, 0, cell10); model->setItem(1, 1, cell11); model->setItem(1, 2, cell12); // 第三行 QStandardItem *cell20 = new QStandardItem(QIcon("a.png"), "1069"); QStandardItem *cell21 = new QStandardItem("小杜"); QStandardItem *cell22 = new QStandardItem("70"); model->setItem(2, 0, cell20); model->setItem(2, 1, cell21); model->setItem(2, 2, cell22); // 第四行 QStandardItem *cell30 = new QStandardItem(QIcon("a.png"), "2469"); QStandardItem *cell31 = new QStandardItem("小王"); QStandardItem *cell32 = new QStandardItem("100"); // 滿分,給他點獎勵 cell32->setBackground(QColor("blue")); cell32->setForeground(QColor("white")); model->setItem(3, 0, cell30); model->setItem(3, 1, cell31); model->setItem(3, 2, cell32); // 第五行 QStandardItem *cell40 = new QStandardItem(QIcon("a.png"), "6394"); QStandardItem *cell41 = new QStandardItem("小張"); QStandardItem *cell42 = new QStandardItem("89"); model->setItem(4, 0, cell40); model->setItem(4, 1, cell41); model->setItem(4, 2, cell42); // 設置模型 setModel(model); } MyTableView::~MyTableView() { }
setRowCount、setColumnCount 方法可以不調用,模型會根據你放的數據調整。有兩點得註意:
1、這裡每個 QStandardItem 代表的是一個單元格的數據,而不是一行;
2、QStandardItem 類型的變數要聲明為指針類型,並且要在堆上分配;不能用棧分配,會顯示空白(提前析構了)。
然後,main 函數就更簡單了。
#include <QApplication> #include "cust.h" int main(int argc, char* argv[]) { QApplication app(argc, argv); MyTableView *win = new MyTableView(); win->setWindowTitle("月考結果"); win->resize(300, 260); win->show(); return QApplication::exec(); }
結果如下圖:
第一行和第四行,咱們修改過背景色和文本顏色。
// 不合格,讓它顯示黃色 cell02->setBackground(QColor("yellow")); cell02->setForeground(QColor("red")); …… // 滿分,給他點獎勵 cell32->setBackground(QColor("blue")); cell32->setForeground(QColor("white"));
好了,今天就聊到這兒,QStandardItemModel 還有其他耍法,尤其是在樹形結構上。咱們下一期繼續扯。