自定义 listModel 不通知视图
Custom listModel does not notify the view
我有我的自定义列表模型,我在其中放置了应该显示在 QML 视图上的数据。但由于某种原因,QML 中的视图有时会正常更新,有时会使用以前的数据,有时不会执行更新。
这是我填充模型的函数 - 这个函数是从其他线程调用的。
void MyScreen::fillListModel()
{
const QString SEPARATOR = " ";
myListModel->resetModel();
for (int i = 0; i < MAX_ROWS; ++i)
{
QString key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
QString val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
myListModel->addItem(key + SEPARATOR + val);
}
}
模型重置的实现:
void BrowsingModelBase::resetModel()
{
beginResetModel();
m_items.clear();
endResetModel();
}
实施addItem()
:
void BrowsingModelBase::addItem(const BrowsingItemModelBase &item)
{
int count = m_items.size();
beginInsertRows(QModelIndex(), count, count);
m_items.append(item);
endInsertRows();
}
最后是我的 QML 文件:
MyScreen {
Column {
id: myFlowList
y: 110
x: 220
ListView {
height:1000
spacing: 35;
model: myListModelRoot.myListModel
delegate: Text {
text: text1
}
}
}
}
奇怪的是在循环之后用行
myListModel->addItem(key + SEPARATOR + val);
当我使用 myListModel
中的数据打印日志时,它会填充适当的数据,但视图通常会使用以前的数据进行更新。数据更改信号是否有可能卡在某处?知道解决方案是什么吗?
问题很可能是您不是从主 GUI 线程调用 fillListModel()
方法。您可以从其他线程更新模型,但必须在主 GUI 线程中调用 beginResetModel();
、endResetModel();
、beginInsertRows(QModelIndex(), count, count);
... 方法。
在 GUI 线程中调用这些方法的一种方法(可能不是最有效的)是:
- 为每个要调用的方法创建信号:
signals:
//these signals are emitted from worker thread
void requestBeginResetModel();
void requestEndResetModel();
- 创建将实际调用方法的插槽:
private slots:
//these slots execute the model reset operations in main thread
void callBeginResetModel();
void callEndResetModel();
- 连接信号和槽:
//connect the appropriate signals
connect(this, SIGNAL(requestBeginResetModel()),
this, SLOT(callBeginResetModel()));
connect(this, SIGNAL(requestEndResetModel()),
this, SLOT(callEndResetModel()));
- 您的重置模型将是:
void BrowsingModelBase::resetModel()
{
emit requestBeginResetModel();
m_items.clear();
emit requestEndResetModel();
}
- 最终插槽实现为:
void ObjectModel::callBeginResetModel()
{
beginResetModel();
}
void ObjectModel::callEndResetModel()
{
endResetModel();
}
请注意,您也必须对行插入方法执行相同的操作。或者,您可以在发出的信号之间用 resetModel()
方法填充模型。
假设您从另一个线程调用模型的方法,并且该模型基本上不是线程安全的,您有两个选择:
使模型的某些方法线程安全,或者
以线程安全的方式显式调用方法。
但首先,您可以通过一次添加所有项目作为一个单元来获得一些性能。这样一来,模型将只为所有行发出一个信号,而不是每行发出一个信号。意见会很感激。
class BrowsingModelBase {
...
};
Q_DECLARE_METATYPE(QList<BrowsingItemModelBase>)
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
}
您可能还应该有一个名为 clear
而不是 resetModel
的方法,因为重置模型具有更普遍的含义:"change it so much that it's not worth emitting individual change signals"。重置模型不是意味着"clear it"!因此:
void BrowsingModelBase::clear()
{
beginResetModel();
m_items.clear();
endResetModel();
}
最后,按照第2种方法安全调用模型的方法,fillListModel
变成如下。参见 this answer for discussion of postTo
。
template <typename F>
void postTo(QObject * obj, F && fun) {
if (obj->thread() != QThread::currentThread()) {
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
} else
fun();
}
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
postTo(myListModel, [this, items]{
myListModel->clear();
myListModel->addItems(items);
});
}
或者,按照第一种方法,您可以使 clear
和 addItems
方法线程安全:
/// This method is thread-safe.
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
postTo(this, [this, items]{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
});
}
/// This method is thread-safe.
void BrowsingModelBase::clear()
{
postTo(this, [this]{
beginResetModel();
m_items.clear();
endResetModel();
});
}
然后您不需要对 fillListModel
进行任何更改,除非使其使用 addItems
:
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
myListModel->clear();
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
myListModel->addItems(items);
}
我有我的自定义列表模型,我在其中放置了应该显示在 QML 视图上的数据。但由于某种原因,QML 中的视图有时会正常更新,有时会使用以前的数据,有时不会执行更新。
这是我填充模型的函数 - 这个函数是从其他线程调用的。
void MyScreen::fillListModel()
{
const QString SEPARATOR = " ";
myListModel->resetModel();
for (int i = 0; i < MAX_ROWS; ++i)
{
QString key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
QString val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
myListModel->addItem(key + SEPARATOR + val);
}
}
模型重置的实现:
void BrowsingModelBase::resetModel()
{
beginResetModel();
m_items.clear();
endResetModel();
}
实施addItem()
:
void BrowsingModelBase::addItem(const BrowsingItemModelBase &item)
{
int count = m_items.size();
beginInsertRows(QModelIndex(), count, count);
m_items.append(item);
endInsertRows();
}
最后是我的 QML 文件:
MyScreen {
Column {
id: myFlowList
y: 110
x: 220
ListView {
height:1000
spacing: 35;
model: myListModelRoot.myListModel
delegate: Text {
text: text1
}
}
}
}
奇怪的是在循环之后用行
myListModel->addItem(key + SEPARATOR + val);
当我使用 myListModel
中的数据打印日志时,它会填充适当的数据,但视图通常会使用以前的数据进行更新。数据更改信号是否有可能卡在某处?知道解决方案是什么吗?
问题很可能是您不是从主 GUI 线程调用 fillListModel()
方法。您可以从其他线程更新模型,但必须在主 GUI 线程中调用 beginResetModel();
、endResetModel();
、beginInsertRows(QModelIndex(), count, count);
... 方法。
在 GUI 线程中调用这些方法的一种方法(可能不是最有效的)是:
- 为每个要调用的方法创建信号:
signals:
//these signals are emitted from worker thread
void requestBeginResetModel();
void requestEndResetModel();
- 创建将实际调用方法的插槽:
private slots:
//these slots execute the model reset operations in main thread
void callBeginResetModel();
void callEndResetModel();
- 连接信号和槽:
//connect the appropriate signals
connect(this, SIGNAL(requestBeginResetModel()),
this, SLOT(callBeginResetModel()));
connect(this, SIGNAL(requestEndResetModel()),
this, SLOT(callEndResetModel()));
- 您的重置模型将是:
void BrowsingModelBase::resetModel()
{
emit requestBeginResetModel();
m_items.clear();
emit requestEndResetModel();
}
- 最终插槽实现为:
void ObjectModel::callBeginResetModel()
{
beginResetModel();
}
void ObjectModel::callEndResetModel()
{
endResetModel();
}
请注意,您也必须对行插入方法执行相同的操作。或者,您可以在发出的信号之间用 resetModel()
方法填充模型。
假设您从另一个线程调用模型的方法,并且该模型基本上不是线程安全的,您有两个选择:
使模型的某些方法线程安全,或者
以线程安全的方式显式调用方法。
但首先,您可以通过一次添加所有项目作为一个单元来获得一些性能。这样一来,模型将只为所有行发出一个信号,而不是每行发出一个信号。意见会很感激。
class BrowsingModelBase {
...
};
Q_DECLARE_METATYPE(QList<BrowsingItemModelBase>)
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
}
您可能还应该有一个名为 clear
而不是 resetModel
的方法,因为重置模型具有更普遍的含义:"change it so much that it's not worth emitting individual change signals"。重置模型不是意味着"clear it"!因此:
void BrowsingModelBase::clear()
{
beginResetModel();
m_items.clear();
endResetModel();
}
最后,按照第2种方法安全调用模型的方法,fillListModel
变成如下。参见 this answer for discussion of postTo
。
template <typename F>
void postTo(QObject * obj, F && fun) {
if (obj->thread() != QThread::currentThread()) {
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
} else
fun();
}
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
postTo(myListModel, [this, items]{
myListModel->clear();
myListModel->addItems(items);
});
}
或者,按照第一种方法,您可以使 clear
和 addItems
方法线程安全:
/// This method is thread-safe.
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
postTo(this, [this, items]{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
});
}
/// This method is thread-safe.
void BrowsingModelBase::clear()
{
postTo(this, [this]{
beginResetModel();
m_items.clear();
endResetModel();
});
}
然后您不需要对 fillListModel
进行任何更改,除非使其使用 addItems
:
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
myListModel->clear();
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
myListModel->addItems(items);
}