自定义 listModel 不通知视图

Posted

技术标签:

【中文标题】自定义 listModel 不通知视图【英文标题】:Custom listModel does not notify the view 【发布时间】:2015-08-18 21:29:30 【问题描述】:

我有我的自定义列表模型,我在其中放置了应该在 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
            
        
    

奇怪的是,用line循环后

myListModel->addItem(key + SEPARATOR + val);

当我使用来自myListModel 的数据打印日志时,它会填充适当的数据,但视图通常会使用以前的数据进行更新。数据更改信号是否可能卡在某处?知道解决方案是什么吗?

【问题讨论】:

BrowsingItemModelBase::BrowsingItemModelBase(const QString &amp;) 构造函数是否存在? fillListModel 写得好像有这样一个构造函数。 QString::fromUtf16 被调用,大概std::string::c_str() 可能是一个错误? 【参考方案1】:

假设您从另一个线程调用模型的方法,并且模型基本上不是线程安全的,您有两种选择:

    使模型的某些方法成为线程安全的,或者

    以线程安全的方式显式调用方法。

但首先,您可以通过一次将所有项目添加为一个单元来获得一些性能。这样,模型将只为所有行发出一个信号,而不是每行一个信号。意见会很感激的。

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 的方法,因为重置模型具有更一般的含义:“改变太多以至于不值得发出单独的更改信号”。重置模型意味着“清除它”!因此:

void BrowsingModelBase::clear()

  beginResetModel();
  m_items.clear();
  endResetModel();

最后,按照安全调用模型方法的第二种方法,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);
  );

或者,按照第一种方法,您可以使 clearaddItems 方法线程安全:

/// 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);

【讨论】:

【参考方案2】:

问题很可能是因为您不是从主 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() 方法填充您的模型。

【讨论】:

这是朝着正确的方向发展,但非常不明智。 resetModel 必须被视为一个单元 - 它的全部应该在拥有模型的线程中运行。在不确保许多其他事情也是线程安全的情况下以任何其他方式执行它只是要求未定义的行为。 我会更直言不讳:这个答案是错误的。对于任何想在此处按原样使用代码的人:不要。它不仅促进了未定义的行为,而且整个 Qt 4 代理信号的想法在现代非常冗长和倒退。

以上是关于自定义 listModel 不通知视图的主要内容,如果未能解决你的问题,请参考以下文章

如何在通知内容扩展中为我的自定义视图应用自定义字体/颜色和半透明背景?

如何使用 SwiftUI 打开通知操作的自定义视图?

触发本地通知时的自定义视图

带有波纹和双击的 Android 锁屏通知自定义视图

Qt 自定义小部件如何通知 ScrollArea 父级视图更改

如何播放警报声音并呈现自定义视图而不仅仅是通知?