《QTreeView表项实现排序的2种方式》:系列教程之九

本文属于《QTreeView使用系列教程》之一,欢迎查看其它文章。

一、综述

基于QT的Model/View框架,我们可以自定义model实现数据与View分离。

让数据实现排序的方式有2种:

  1. 在QAbstractItemModel子类中重新实现sort()函数;
  2. 在QSortFilterProxyModel代理model子类中重新实现lessThan()函数,并用该子类代理model来包装原始model。

接下来,我们还是在之前自定义model的,代码基础上进行说明。

二、重写QAbstractItemModel的sort()实现排序

在QAbstractItemModel类中sort函数原型如下:

virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);

参数包括column-需要排序的列号;order-排序方式,升序还是降序。

我们在TreeModel类中实现sort函数,如下:

void TreeModel::sort(int column, Qt::SortOrder order)
{
    // 关键:发送模型layout即将改变信号
    emit layoutAboutToBeChanged();

    // 对每个"省份"下所有"人口"执行排序
    int provinceCount = _rootItem->childCount();
    for (int i = 0; i < provinceCount; i++)
    {
        TreeItem* pro = _rootItem->child(i);
        pro->sort(column, order);
    }

    // 关键:发送模型layout已改变信号
    emit layoutChanged();
}

其中2个信号很关键:

  1. layoutAboutToBeChanged,在我们修改model内部数据前,进行发送。
  2. layoutChanged,在我们修改model内部数据后,进行发送。

小贴士

这里引申一下,对于更新自定义model数据的方式,目前所知的有2种:

第一种,是更新前使用treeView->setUpdatesEnabled(false)禁止view从model读取数据,然后更新后,使用treeView->setUpdatesEnabled(true)启用view更新。对于model内部数据结构没有发生改变,纯粹是更新现有结构下的数据,这个方式是可以安全刷新model数据的。而对于需要打乱model内部数据结构,如排序,这种方式是不行的,因为View中还有个ModelIndex索引的概念,原始数据被打乱,而对应的索引没有更新,view显示时会有问题。

第二种,就是上文中提到的,使用这2个信号来包围model数据修改,即便排序打乱了model数据结构,但是,在发送layoutChanged信号后,view就会重建QModelIndex索引,故也可以安全刷新model数据,且比setUpdatesEnabled方式更强大。

引发的思考:在纯粹更新model数据,不改结构情况下,推荐使用setUpdatesEnabled方式,效率高,不用重建索引,不推荐使用信号方式,因为每次更新model都会把所有索引全部重建,这样效率可能要低些。若model结构发生改变,则只能使用信号方式了。

好,我们聊回刚才的话题。

我们需要实现对"省份"下"人口"节点的排序,那么
需要遍历每个"省",对每个"省"的子节点"人"进行排序。类似把人看做int,进行冒泡排序。

在TreeItem类中定义小于函数,实现对2个"人"的比较,只有当item1性别为女,item2性别为男时,表示item1小于item2,如下:

// item1的sex < item2的sex,则返回true
bool TreeItem::lessThan_sex(const TreeItem * item1, const TreeItem * item2)
{
    Person* left = static_cast<Person*>(item1->ptr());
    Person* right = static_cast<Person*>(item2->ptr());
    if (left->sex == "woman" && right->sex == "man") // 女士优先
    {
        return true;
    }
    return false;
}

_children中是未排序的TreeItems,使用qt排序算法qSort进行升序和降序,调用上面的lessThan_sex(),对sex列执行排序。

QList<TreeItem*> _children;   // 子节点
// 对sex列执行排序
void TreeItem::sort_sex(Qt::SortOrder order)
{
    if (order == Qt::AscendingOrder) // 升序
        qSort(_children.begin(), _children.end(), lessThan_sex);
    else  // 降序
        qSort(_children.rbegin(), _children.rend(), lessThan_sex);
}

此时,_children中是排序后的TreeItems。

我们实现了对sex列的排序逻辑,接下来如法炮制,实现age列排序逻辑。

定义age列的小于函数:

// item1的age < item2的age,则返回true
bool TreeItem::lessThan_age(const TreeItem *item1, const TreeItem * item2)
{
    Person* left = static_cast<Person*>(item1->ptr());
    Person* right = static_cast<Person*>(item2->ptr());
    return (left->age < right->age);
}

对age列执行排序

// 对age列执行排序
void TreeItem::sort_age(Qt::SortOrder order)
{
    if (order == Qt::AscendingOrder) // 升序
        qSort(_children.begin(), _children.end(), lessThan_age);
    else  // 降序
        qSort(_children.rbegin(), _children.rend(), lessThan_age);
}

已经具备对age、sex列排序的函数,再进行一下封装,如下:

void TreeItem::sort(int column, Qt::SortOrder order)
{
    if (_type != PROVINCE) // 对"省份"节点下进行排序
        return;

    if (column == COLUMN_SEX)
        sort_sex(order);
    else if (column == COLUMN_AGE)
        sort_age(order);
}

然后,我们在最上面提到的,重写QAbstractItemModel的sort()中,调用TreeItem的sort函数,如下:

void TreeModel::sort(int column, Qt::SortOrder order)
{
    // 关键:发送模型layout即将改变信号
    emit layoutAboutToBeChanged();

    // 对每个"省份"下所有"人口"执行排序
    int provinceCount = _rootItem->childCount();
    for (int i = 0; i < provinceCount; i++)
    {
        TreeItem* pro = _rootItem->child(i);
        pro->sort(column, order);
    }

    // 关键:发送模型layout已改变信号
    emit layoutChanged();
}

在for循环中实现了排序,是真实的把TreeItem指针在QList中进行了排序,故用信号包围起来。

最后,记得设置允许排序:

treeView->setSortingEnabled(true);

以上说明,默认已掌握自定义model前提下,否则,可能看的不太懂,源码在文章末尾,感兴趣可以自行阅读。

运行效果:
在这里插入图片描述

本章涉及工程代码:

文末,公众号回复:34SortExample1,即可下载。

三、重写QSortFilterProxyModel的lessThan()实现排序

上面那种方法,似乎自己实现的多了点,接下来我们看这种更简单的方法。

使用代理model类QSortFilterProxyModel实现排序,我们重写它的lessThan(),如下:

bool SortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
    TreeItem *item1 = static_cast<TreeItem*>(left.internalPointer());
    TreeItem *item2 = static_cast<TreeItem*>(right.internalPointer());
    if (left.column() == COLUMN_SEX && right.column() == COLUMN_SEX)
    {
        return TreeItem::lessThan_sex(item1, item2);
    }
    else if (left.column() == COLUMN_AGE && right.column() == COLUMN_AGE)
    {
        return TreeItem::lessThan_age(item1, item2);
    }
    return QSortFilterProxyModel::lessThan(left, right);
}

若left<right,那么返回true,否则返回false。
我们只处理COLUMN_SEX、COLUMN_AGE列排序,其他交给基类默认函数处理。

在TreeItem类中实现item比较sex属性的函数,实现item比较age属性的函数,如下:

// item1的sex < item2的sex,则返回true
bool TreeItem::lessThan_sex(const TreeItem * item1, const TreeItem * item2)
{
    Person* left = static_cast<Person*>(item1->ptr());
    Person* right = static_cast<Person*>(item2->ptr());
    if (left->sex == "woman" && right->sex == "man") // 女士优先
    {
        return true;
    }
    return false;
}

// item1的age < item2的age,则返回true
bool TreeItem::lessThan_age(const TreeItem *item1, const TreeItem * item2)
{
    Person* left = static_cast<Person*>(item1->ptr());
    Person* right = static_cast<Person*>(item2->ptr());
    return (left->age < right->age);
}

然后,使用代理model包装我们的原始model,如下:

SortFilterProxyModel* proxy = new SortFilterProxyModel(treeView);
proxy->setSourceModel(model);
treeView->setModel(proxy);

最后,记得设置允许排序:

treeView->setSortingEnabled(true);

运行效果:
在这里插入图片描述

本章涉及工程代码:

文末,公众号回复:34SortExample2,即可下载。



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值