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

Sort the data of a Qml ListView by using a QSortFilterProxyModel.

本文关键字:数据 排序 ListView QSortFilterProxyModel Qml 使用      更新时间:2023-10-16

我正在制作一个聊天客户端,我正在使用 qmlListView来显示"聊天室"中的所有消息。

我正在使用自己的模型类,该类派生自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 是一个向 QML 公开变量的QQmlApplicationEngine

这是包含包含所有消息的列表视图的 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的底部而不是顶部,这意味着代理模型已停止对模型进行排序。

下面是第二次插入后的列表视图的图像。注意时间戳 这就是当我终止程序并一次插入所有消息时发生的情况

我想要的结果是每次更新/更改源模型时,代理模型都按升序对消息进行排序(最新的在 botton,最旧的在顶部(。这意味着当调用void MessageModel::insert_unique_data(QJsonArray new_data)时,我希望新消息出现在底部,最新的消息作为最后一条消息。

感谢您抽出宝贵时间阅读我疯狂的长篇文章。我只是想涵盖所有细节,因为将 c++ 模型公开到 qml 需要很多步骤。

我的猜测是你的lessThan函数什么都不做。

调用QVariant leftData = sourceModel()->data(source_left);时,它会调用具有角色Qt::DisplayRoledata函数,该函数返回模型的无效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
}
}
}

我已经想通了!

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

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