使用QSortFilterProxyModel对Qml ListView的数据进行排序。

时间:2017-08-08 08:38:26

标签: c++ qt qml

我正在制作聊天客户端,我正在使用qml ListView来显示“聊天室”中的所有消息。

我正在使用我自己的模型类,它派生自QAbstractListModel,用于存储用户所属的所有房间中的所有消息。

我希望根据最初发送的时间戳对所有消息进行排序,最新消息应显示在视图的底部,最旧的消息应位于顶部。

我还希望用户能够根据发送到哪个房间来过滤消息。我已经解决了这个问题。

这是我的自定义模型的声明

class MessageModel : public QAbstractListModel
{
public:
    enum MessageRoles {
            Id = Qt::UserRole + 1,
            RoomId = Qt::UserRole + 2,
            PersonId = Qt::UserRole + 3,
            PersonEmail = Qt::UserRole + 4,
            Created = Qt::UserRole + 5,
            Text = Qt::UserRole + 6
        };
    explicit MessageModel(QObject * parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QHash<int, QByteArray> roleNames() const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 
    void reset_data(QJsonArray new_data);
    void insert_unique_data(QJsonArray new_data);
private:
    QList<LocalData::Message> m_data;

};

这是我的模型的实现

MessageModel::MessageModel(QObject *parent) : QAbstractListModel(parent)
{}

int MessageModel::rowCount(const QModelIndex &parent) const
{
    return m_data.size();
}

QHash<int, QByteArray> MessageModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[Id] = "id";
    roles[RoomId] = "roomId";
    roles[PersonId] = "personId";
    roles[PersonEmail] = "personEmail";
    roles[Created] = "created";
    roles[Text] = "messageText";
    return roles;
}

QVariant MessageModel::data(const QModelIndex &index, int role) const {

    if(!index.isValid())
        return QVariant();

    if(index.row() >= m_data.size())
        return QVariant();

    switch (role) {
        case Id:
            return m_data.at(index.row()).id;
        case RoomId:
            return m_data.at(index.row()).roomId;
        case PersonId:
            return m_data.at(index.row()).personId;
        case PersonEmail:
            return m_data.at(index.row()).personEmail;
        case Text:
            return m_data.at(index.row()).text;
        case Created:
            return m_data.at(index.row()).created.toString(Qt::ISODateWithMs);
    }

    return QVariant();
}

void MessageModel::update_data(QJsonArray new_data)
{
    beginResetModel();
    m_data.clear();

    foreach (const QJsonValue & val, new_data) {
        m_data.push_back(LocalData::Message(val.toObject()));
    }
    endResetModel();
}

void MessageModel::insert_unique_data(QJsonArray new_data)
{
    QList<LocalData::Message> temp;
    foreach (const QJsonValue & val, new_data) {
        auto obj_to_insert = LocalData::Message(val.toObject());
        if(!m_data.contains(obj_to_insert))
            temp.push_back(obj_to_insert);
    }
    int begin = rowCount();
    int end = rowCount() + temp.size() - 1;

    beginInsertRows(QModelIndex(), begin, end);
    foreach (const LocalData::Message & msg, temp) {
        m_data.push_back(msg);
    }
    endInsertRows();
}

我想使用QSortFilterProxyModel对消息进行排序和过滤。 我设法让它根据RoomId角色正确过滤消息,但我在Created角色上正确排序消息时遇到了麻烦。

我正在使用的代理模型非常简单,我只是试图覆盖lessThan()函数,如下所示:

bool SortFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
    {
        QVariant leftData = sourceModel()->data(source_left);
        QVariant rightData = sourceModel()->data(source_right);

        if(leftData.type() == QVariant::DateTime)
        {
            return leftData.toDateTime() < rightData.toDateTime();
        }
        else {
            return leftData.toString() < rightData.toString();
        }
    }

另外,它只是一个普通的QSortFilterProxyModel。 当它被初始化时,我正在调用以下函数:

m_messageModel = new MessageModel;
m_messageProxyModel = new SortFilterProxyModel;

m_messageProxyModel->setSourceModel(qobject_cast<QAbstractListModel *>(m_messageModel));

m_engine.rootContext()->setContextProperty("messages", m_messageProxyModel);

m_messageProxyModel.setSortRole(MessageModel::Created);   

m_messageProxyModel.setDynamicSortFilter(true); 

m_messageProxyModel.sort(0, Qt::AscendingOrder);

m_messageModelm_messageProxyModelm_engine是在main()中实例化的类中声明的所有成员变量(指针)。 m_engine是一个QQmlApplicationEngine,它将变量公开给qml。

这是包含带有所有消息的ListView的qml文件。

import QtQuick 2.0
import QtQuick.Controls 1.4

Rectangle{
    property alias messageView: _messagePanelView
    Component {
        id: _messagePanelDelegate
        Item{
            id: _messagePanelDelegateItem;

            width: root.width * 0.8
            height: _messagePanelMessageColumn.height
            Column{
                id: _messagePanelMessageColumn;
                height: children.height;
                spacing: 20
                Text{ text: "<b>" + personEmail + "</b> <t />" + created; font.pointSize: 7 + Math.log(root.width) }
                Text{ text: messageText }
                Rectangle{
                    width: parent.width
                    height: 20
                    color: "#FFFFFF"
                }
            }
        }
    }
    ScrollView{
        anchors.fill: parent
        ListView {
            id: _messagePanelView
            height: root.height - 100
            model: messages
            delegate: _messagePanelDelegate
            interactive: true
        }
    }
}

当用户要求特定“聊天室”中的所有消息时,源模型当前填充有数据。当用户按下按钮时,将调用与此类似的功能。

void sync_messages_and_filter_based_on_roomId(QString roomId)
{
    m_messageProxyModel->setFilterRole(MessageModel::RoomId);
    m_messageProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    m_messageProxyModel->setFilterRegExp(QRegExp::escape(roomId));

    fetch_messages_in_room_from_server_async(roomId); 
}

fetch_messages_in_room_from_server_async(roomId);是一个函数,它向服务器请求新消息,接收JSON格式的响应有效负载,创建QJsonArray对象并调用void MessageModel::insert_unique_data(QJsonArray new_data)所有这些都发生在单独的工作线程中

当第一次调用此数据时,数据实际上按降序排序(最旧的位于底部,最近的位于顶部)。但是当它第二次调用时,当服务器有更多数据要提供时,客户端应该将所有新消息作为行插入源模型中,并让代理模型按升序对其进行排序(最近一次是在一旦插入新数据,就在底部,最旧的顶部。

目前,模型仅按降序排序,第一次将数据插入模型中。但是,一旦模型第一次更新,它就不再对数据进行排序,新消息显示在ListView的底部而不是顶部,这意味着代理模型已停止对模型进行排序

Here is an image of the ListView, after inserting a second time. Notice the timestamps This is what happens when I terminate the program, and insert all the messages at once

我想要的结果是每次更新/更改源模型时,让代理模型按升序排序消息(最近在botton,最旧的顶部)。调用void MessageModel::insert_unique_data(QJsonArray new_data)时的含义,我希望新消息显示在底部,最新消息作为最后一条消息。

感谢您抽出宝贵时间阅读我疯狂的长篇文章。我只想介绍所有细节,因为将c ++模型暴露给qml需要很多步骤。

2 个答案:

答案 0 :(得分:3)

我的猜测是你的lessThan功能无效。

当您致电QVariant leftData = sourceModel()->data(source_left);时,它会调用data函数并使用Qt::DisplayRole角色为您的模型返回无效QVariant。您的if(leftData.type() == QVariant::DateTime)永远不会true

您应该做的是明确获取QDateTime leftTimestamp = m_data.at(source_left.row()).created;的时间戳和source_right的时间戳。 您的if无用,您可以return leftTimestamp < rightTimeStamp;

或者,如果你想从QML而不是c ++进行排序和过滤,你可以像我这样使用我的SortFilterProxyModel

import QtQuick 2.0
import QtQuick.Controls 1.4
import SortFilterProxyModel 0.2

Rectangle{
    property alias messageView: _messagePanelView

    SortFilterProxyModel {
         id: proxyMessageModel
         sourceModel: sourceMessageModel // a context property you exposed
         filters: ValueFilter {
             roleName: "roomId"
             value: currentRoomId // a property you could add somewhere
         }
         sorters : RoleSorter {
             roleName: "created"
         }
    }
    Component {
        id: _messagePanelDelegate
        Item{
            id: _messagePanelDelegateItem;

            width: root.width * 0.8
            height: _messagePanelMessageColumn.height
            Column{
                id: _messagePanelMessageColumn;
                height: children.height;
                spacing: 20
                Text{ text: "<b>" + personEmail + "</b> <t />" + created; font.pointSize: 7 + Math.log(root.width) }
                Text{ text: messageText }
                Rectangle{
                    width: parent.width
                    height: 20
                    color: "#FFFFFF"
                }
            }
        }
    }
    ScrollView{
        anchors.fill: parent
        ListView {
            id: _messagePanelView
            height: root.height - 100
            model: proxyMessageModel
            delegate: _messagePanelDelegate
            interactive: true
        }
    }
}

答案 1 :(得分:2)

我已经明白了!

SortProxyModel::lessThan()函数中,我调用QAbstractProxyModel::data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole)函数而不更改默认角色参数。这意味着我对lessThan()函数的实现并没有在Created角色上进行比较。

我如何修复它只是使用普通的QSortProxyModel类型,而不是我自己的派生版本。子类化QSortProxyModel是一个错误。