表格排序是Qt內建支援的,用起來很簡單。只需要在QtCreator介面給QTableView或QTableWidget的SortingEnabled屬性設定為true就行了。
本文將對這兩種控制元件分別展示一下效果和一個自定義的排序例子。
一、QTableWidget
這個不需要任何程式碼,只需要設計介面時候啟用排序就行了。下面直接截圖:
二、QTableView
這種方式需要自己寫程式碼,下面給出一個6行3列的例子。程式碼中QtTest是主視窗類,ui.tbContent是QTableView控制元件:
QtTest::QtTest(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); QStandardItemModel* myModel = new QStandardItemModel(ui.tbContent); myModel->setHorizontalHeaderLabels({ u8"列1", u8"列2", u8"列3", }); myModel->setVerticalHeaderLabels({ u8"行1", u8"行2", u8"行3", u8"行4", u8"行5", u8"行6",}); for (int i = 0; i < 6; i++) { QStandardItem *col1 = new QStandardItem(); col1->setData(1000 + i, Qt::DisplayRole); myModel->setItem(i, 0, col1); QStandardItem *col2 = new QStandardItem(); col2->setData(u8"dsaasf" + QString::number(i), Qt::DisplayRole); myModel->setItem(i, 1, col2); QStandardItem *col3 = new QStandardItem(); col3->setData(32.123f + 2.5f * i, Qt::DisplayRole); myModel->setItem(i, 2, col3); } ui.tbContent->setSortingEnabled(true); ui.tbContent->setModel(myModel); }
Qt排序的原理是對Model中的資料排序,然後按照新順序重新顯示資料。程式執行之後的介面如下圖:
三、自定義排序
在Qt幫助中沒有說明預設的排序的細節。不同於標準庫裡的std::sort(...)直接說明了它引用的是operator<(...)函式。Qt沒有這樣說,所以大機率是不能過載operator<(...)函式實現對自定義資料的排序。那怎麼讓自定義的資料支援排序功能呢?放心,Qt提供了其它的方式實現自己的排序功能。就是
- 繼承QAbstractItemModel並實現它的sort(...)函式,或者;
- 用QSortFilterProxyModel包含你的Model然後實現其lessThan(...)函式,這個代理模型功能非常強大除了排序之外還支援資料查詢過濾的功能。
下面將給出一個例子闡述如何自定義排序。該例子使用第一種方法。作者也是第一次做這個功能,對下方程式碼是否地道的把握不是很大,可能有些不合理之處。主要是QAbstractTableModel裡的虛擬函式很多是預設空函式,所以不太清楚它們的功能和使用時機。比如:下方的函式beginResetModel()和endResetModel()什麼時候使用Qt幫助也沒說的很明確。這裡就認為只要Model中的資料改變就呼叫。標頭檔案:
class QStandardItem; class MItemModel : public QAbstractTableModel { Q_OBJECT public: struct MyDataType { int type; }; public: MItemModel(QObject* parent = 0); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(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) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; private: void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; private: QVector<QVector<QStandardItem*>> datas; }; Q_DECLARE_METATYPE(MItemModel::MyDataType); class MyDataDelegate : public QStyledItemDelegate { Q_OBJECT public: MyDataDelegate(QObject* parent = 0); QString displayText(const QVariant &value, const QLocale &locale) const override; };
CPP檔案:
MItemModel::MItemModel(QObject* parent) : QAbstractTableModel(parent) { } MItemModel::~MItemModel() { for (auto& item : datas) { qDeleteAll(item); } } QModelIndex MItemModel::index(int row, int column, const QModelIndex &parent) const { return createIndex(row, column, nullptr); } int MItemModel::rowCount(const QModelIndex &parent) const { return datas.size(); } int MItemModel::columnCount(const QModelIndex &parent) const { auto maxv = std::max_element(datas.begin(), datas.end(), [](const QVector<QStandardItem*>& x, const QVector<QStandardItem*>& y) { return x.size() < y.size(); }); return maxv->size(); } QVariant MItemModel::data(const QModelIndex &index, int role) const { const QVector<QStandardItem*>& rowData = datas[index.row()]; if (index.column() < rowData.size() && rowData[index.column()]) { return rowData[index.column()]->data(role); } return QVariant(); } bool MItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { int r = index.row(); int c = index.column(); if (r >= datas.size()) { datas.resize(r + 1); } if (c >= datas[r].size()) { datas[r].resize(c + 1); } if (!datas[r][c]) { datas[r][c] = new QStandardItem; } datas[r][c]->setData(value, role); return true; } /* 這是行頭和列頭 */ QVariant MItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Orientation::Horizontal) { QString header = u8"列"; return header + QString::number(section + 1); } if (orientation == Qt::Orientation::Vertical) { QString header = u8"行"; return header + QString::number(section + 1); } } return QVariant(); } void MItemModel::sort(int column, Qt::SortOrder order) { if (column != 0) { /* Qt預設為空,點選其他列是不會排序的 */ QAbstractTableModel::sort(column, order); return; } beginResetModel(); /* 這裡使用的是選擇排序 */ int count = rowCount(); for (int i = 0; i < count; i++) { int maxi = i; MyDataType maxv = data(index(i, column), Qt::DisplayRole).value<MyDataType>(); for (int j = i + 1; j < count; j++) { MyDataType curr = data(index(j, column), Qt::DisplayRole).value<MyDataType>(); if ((curr.type < maxv.type) ^ (order == Qt::DescendingOrder)) { maxv = curr; maxi = j; } } if (maxi != i) { std::swap(datas[maxi], datas[i]); } } endResetModel(); } ///////////////////////////////////////////////////////////////////////////////////////// MyDataDelegate::MyDataDelegate(QObject* parent) : QStyledItemDelegate(parent) { } QString MyDataDelegate::displayText(const QVariant &value, const QLocale &locale) const { MItemModel::MyDataType data = value.value<MItemModel::MyDataType>(); return u8":" + QString::number(data.type); }
主函式中初始化。程式碼中的QtTest是主視窗類,ui.tvContent是QTableView類:
QtTest::QtTest(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); MItemModel* myModel = new MItemModel(ui.tvContent); for (int i = 0; i < 6; i++) { myModel->setData(myModel->index(i, 0), QVariant::fromValue<MItemModel::MyDataType>({ 2 + i }), Qt::DisplayRole); myModel->setData(myModel->index(i, 1), u8"例項", Qt::DisplayRole); myModel->setData(myModel->index(i, 2), 324 + i, Qt::DisplayRole); } ui.tvContent->setModel(myModel); ui.tvContent->setItemDelegateForColumn(0, new MyDataDelegate(ui.tvContent)); ui.tvContent->setSortingEnabled(true); }
下面是執行時候的截圖: