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

问题描述:

我正在创建一个聊天客户端,我正在使用qml ListView在“聊天室”中显示所有消息。使用QSortFilterProxyModel对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; 

}; 

的声明这是我想用一个QSortFilterProxyModel既排序和筛选邮件我的模型

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(); 
} 

的实施。 我已经成功地使过滤的基础上正确的RoomId角色的消息,但我有在Created角色正确分拣邮件的烦恼。

我使用代理模式很简单,我只是试着重写每种不超过()函数,就像这样:

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(); 
     } 
    } 

Othervise它只是一个普通的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

我需要的结果是有代理模型排序按升序消息(在钮最近的,最古老的顶部),每一个源模型更新时间/改变。意思是当调用void MessageModel::insert_unique_data(QJsonArray new_data)时,我期望新消息出现在底部,最近的消息是最后一条消息。

感谢您花时间阅读我的疯狂长篇。我只想涵盖所有的细节,因为将C++模型暴露给qml需要很多步骤。

+0

欢迎来到StackOverflow。 +1为一个写得好的第一个问题!我没有找到足够的改进来证明编辑的合理性。 – derM

我的猜测是你的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 
     } 
    } 
} 
+0

是的,你是对的!非常感谢你! 一个基于qml的代理模型似乎很聪明,我会试试:) – Hurlevent

我已经想通了!

SortProxyModel::lessThan()函数中,我调用QAbstractProxyModel::data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole)函数而不更改默认角色参数。这意味着我的lessThan()函数的执行不会与Created角色相比较。

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

+0

确实子类'QSortFilterProxyModel'不需要在你的情况下,使用正常的一个是正确的解决方案,我不知道我的答案是否应该被接受的人:) – GrecKo