(2020-8-26 更新)
排序是一个常用的功能,QTableView 也可以点击表头进行排序,相关接口:
//允许点击排序
ui->tableView->setSortingEnabled(true);
//按第0列升序
ui->tableView->sortByColumn(0,Qt::AscendingOrder);
但只对 QTableView 进行设置还不能生效,需要借助 QAbstractItemModel 类的 sort 接口(需要重写 sort 接口进行排序),或者借助 QSortFilterProxyModel 类(可以重写 lessThan 接口自定义排序规则)。
Qt 提供的 QAbstractItemModel 及其派生类是有 sort 接口的,但是没有实现,对于一些简单的排序,可以继承并重写这个虚函数:
#include <QAbstractTableModel>
//自定义Model
class MyTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
//... ...
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override
{
if(modelData.isEmpty()||modelData.first().count()<=column)
return;
//判断升序降序
const bool is_asc = (order == Qt::AscendingOrder);
//排序
std::sort(modelData.begin(), modelData.end(),
[column, is_asc, this](const QList<QVariant> &left,const QList<QVariant> &right){
const QVariant &left_val = left.at(column);
const QVariant &right_val = right.at(column);
return is_asc?(left_val<right_val):(left_val>right_val);
});
//更新view
dataChanged(index(0,0),index(modelData.count()-1,modelHeader.count()-1));
}
// ... ...
private:
QList<QList<QVariant>> modelData;
};
使用 QAbstractItemModel 的 sort 有个问题,model 在 ResetModel() 重置数据之后不会自动重新排序。这时候我们可以借助 QSortFilterProxyModel 类,不再去重写 sort 函数,就能在重置数据时也自动排序了:
// 给普通的 ItemModel 加一层 SortProxy
MyModel *table_model = new MyModel(ui->tableView);
QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this);
//默认排序role应该是dispalyRole,我们可以修改
proxy_model->setSortRole(Qt::InitialSortOrderRole);
proxy_model->setSourceModel(table_model);
ui->tableView->setModel(proxy_model);
也可以重写 QSortFilterProxyModel 类的 lessThan 接口来实现自己的排序:
#include <QSortFilterProxyModel>
//自定义SortProxy
class MySortProxy: public QSortFilterProxyModel
{
Q_OBJECT
public:
BaseTableProxy(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
//排序role,也就是获取model-data时用的role
setSortRole(Qt::InitialSortOrderRole);
//本地化,对中文排序有影响
//在对字符串排序时,是否考虑本地因素,默认false
setSortLocaleAware(true);
}
//... ...
//重写虚函数接口
bool lessThan(const QModelIndex &source_left,
const QModelIndex &source_right) const override
{
//参照源码
QVariant l = (source_left.model() ? source_left.model()->data(source_left, sortRole()) : QVariant());
QVariant r = (source_right.model() ? source_right.model()->data(source_right, sortRole()) : QVariant());
return isVariantLessThan(l, r, sortCaseSensitivity(), isSortLocaleAware());
}
//修改自源码QAbstractItemModelPrivate
bool isVariantLessThan(const QVariant &left,
const QVariant &right,
Qt::CaseSensitivity cs = Qt::CaseSensitive,
bool isLocaleAware = false) const
{
//修改源码对无效值得判断
//if (left.userType() == QVariant::Invalid)
// left = false;
//if (right.userType() == QVariant::Invalid)
// right = true;
//无效值作为0来判断,这样就在正负数之间展示
if((left.userType() == QVariant::Invalid)||
(right.userType() == QVariant::Invalid))
return left.toDouble() < right.toDouble();
//下面未改动
switch (left.userType()) {
case QVariant::Int:
return left.toInt() < right.toInt();
case QVariant::UInt:
return left.toUInt() < right.toUInt();
case QVariant::LongLong:
return left.toLongLong() < right.toLongLong();
case QVariant::ULongLong:
return left.toULongLong() < right.toULongLong();
case QMetaType::Float:
return left.toFloat() < right.toFloat();
case QVariant::Double:
return left.toDouble() < right.toDouble();
case QVariant::Char:
return left.toChar() < right.toChar();
case QVariant::Date:
return left.toDate() < right.toDate();
case QVariant::Time:
return left.toTime() < right.toTime();
case QVariant::DateTime:
return left.toDateTime() < right.toDateTime();
case QVariant::String:
default:
if (isLocaleAware)
return left.toString().localeAwareCompare(right.toString()) < 0;
else
return left.toString().compare(right.toString(), cs) < 0;
}
}
//... ...
};
QSortFilterProxyModel 还有个问题是:排序之后,表头也会跟着排序,如果表头本身想展示固定行列号就尴尬了。一个临时的解决方法是在 model 的 data 接口返回 visualIndex 的 row(即直接返回当前显示的行数)。
QVariant MySortProxy::data(const QModelIndex & index, int role) const
{
//index有效值判断可以自己修改
QModelIndex source_index = mapToSource(index);
if (index.isValid() && !source_index.isValid())
return QVariant();
//orderColumn 就是我们指定的要显示行数的列
if (source_index.column() == orderColumn && role == Qt::DisplayRole) {
return QString::number(index.row() + 1);
}
return sourceModel()->data(source_index, role);
}