无效的QSortFilterProxyModel筛选器导致在选择项目时崩溃。

huangapple go评论76阅读模式
英文:

Invalidating QSortFilterProxyModel filter causes crash when items are selected

问题

我正在尝试在Qt 5.15.3中创建一个游戏文件系统的文件浏览器,左侧是目录树视图,右侧是文件表格视图。当在树中选择一个目录时,该目录的子项应该显示在表格中,但仅限于文件,不包括嵌套目录。

我实现了两个QSortFilterProxyModels,一个用于树视图,该代理模型的filterAcceptsRow()实现仅检查模型索引是否代表一个目录,但表视图的代理模型稍微复杂一些,因为当选择不同目录时,表使用的活动根索引会更改:

void FileSystemBrowserWidget::onDirectoryActivated(const QModelIndex& index)
{
  // 由于代理模型基于源模型进行过滤,我们获取
  // 用于过滤的源索引。我们收到的索引来自树视图的代理模型。
  const QModelIndex sourceIndex = m_treeProxyModel->mapToSource(index);

  // 告诉表的代理模型有了新的根。
  m_tableProxyModel->setRootForFiltering(sourceIndex);

  // 将表视图的根设置为要显示的根。
  // 表视图显示代理模型,因此我们映射到这个模型。
  m_fileSystemTableView->setRootIndex(m_tableProxyModel->mapFromSource(sourceIndex));
}

表代理模型的setRootForFiltering()函数存储了这个新的根,并使过滤失效。我发现必须这样做,因为如果表视图的根索引更改,需要重新评估过滤。这是因为通常情况下,目录不会通过过滤以显示在表中,因此如果将其选择为表视图的根,此过滤将层次应用,并不会显示目录的子文件。

void FileSystemBrowserTableProxyModel::setRootForFiltering(const QModelIndex& sourceIndex)
{
  if (m_rootForFiltering == sourceIndex)
  {
    return;
  }

  m_rootForFiltering = sourceIndex;
  invalidateFilter();
}

为了修复过滤问题,表代理模型的filterAcceptsRow()函数允许项目与已设置的根索引匹配:

bool FileSystemBrowserTableProxyModel::filterAcceptsRow(
  int sourceRow,
  const QModelIndex& sourceParent) const
{
  QAbstractItemModel* src = sourceModel();
  const QModelIndex srcIndex = src->index(sourceRow, 0, sourceParent);

  if (srcIndex == m_rootForFiltering)
  {
    // 始终允许,否则子项不会显示。
    return true;
  }

  // 如果此索引代表文件而不是目录,则允许。
  return itemAtIndexIsAFile(srcIndex);
}

这似乎使过滤正常工作。但是,如果我在表视图中选择一个项目,然后在树视图中选择不同的目录,就会出现一个奇怪的问题。我的终端会连续出现QSortFilterProxyModel: index from wrong model passed to mapFromSource的错误消息,然后出现崩溃:

___lldb_unnamed_symbol11407 (@___lldb_unnamed_symbol11407:41)
QSortFilterProxyModel::flags(QModelIndex const&) const (@QSortFilterProxyModel::flags(QModelIndex const&) const:43)
QAbstractItemView::focusInEvent(QFocusEvent*) (@QAbstractItemView::focusInEvent(QFocusEvent*):41)
QWidget::event(QEvent*) (@QWidget::event(QEvent*):763)
QFrame::event(QEvent*) (@QFrame::event(QEvent*):14)
QApplicationPrivate::notify_helper(QObject*, QEvent*) (@QApplicationPrivate::notify_helper(QObject*, QEvent*):42)

... 调用堆栈继续 ...

我已经检查了我的代码,关于索引来自错误模型的错误似乎不是由我自己的调用触发的。我只能假设我在某种方式上错误使用了代理模型,但我不确定具体在哪里。我没有向模型添加或删除项目,因此底层数据不应该发生变化。

如果我删除invalidateFilter()的调用,这个问题就不会发生。还应该注意,我不能简单地开启recursiveFilteringEnabled:这意味着任何可见的子项都会导致其父项也可见,这会导致包含文件的目录通过过滤并显示在表视图中。如果有用的话,我是在Kubuntu上开发和测试这个实现。

英文:

I'm trying to make a file browser for a game file system in Qt 5.15.3, with a directory tree view on the left and a file table view on the right. When a directory is selected in the tree, the children of the directory should be displayed in the table, but only the files and not other nested directories.

I have implemented two QSortFilterProxyModels, one for each view. For the tree view, the proxy model's filterAcceptsRow() implementation simply checks whether the model index represents a directory, but the table view's proxy model has to be slightly more complicated since the active root index that the table is using changes when a different directory is selected:

void FileSystemBrowserWidget::onDirectoryActivated(const QModelIndex& index)
{
  // Since the proxy model filters based on the source model, we get
  // the source index to use for filtering. The index we have
  // been passed is from the tree view's proxy model.
  const QModelIndex sourceIndex = m_treeProxyModel->mapToSource(index);

  // Tell the table's proxy model about this new root.
  m_tableProxyModel->setRootForFiltering(sourceIndex);

  // Set the table view's root to display from.
  // The table view shows the proxy model, so we map to this model.
  m_fileSystemTableView->setRootIndex(m_tableProxyModel->mapFromSource(sourceIndex));
}

The table proxy model's setRootForFiltering() function stores this new root, and invalidates the filter. I discovered that I had to do this because the filtering needed to be re-evaluated if the table view's root index was changed. This is because normally a directory would not pass the filter to be shown in the table, and so if it were selected as the root for the table view, this filtering would apply hierarchically and none of the directory's child files would be shown.

void FileSystemBrowserTableProxyModel::setRootForFiltering(const QModelIndex& sourceIndex)
{
  if (m_rootForFiltering == sourceIndex)
  {
    return;
  }

  m_rootForFiltering = sourceIndex;
  invalidateFilter();
}

To fix the filtering issue, the table proxy model's filterAcceptsRow() function allows the item if it matches the root index that's been set:

bool FileSystemBrowserTableProxyModel::filterAcceptsRow(
  int sourceRow,
  const QModelIndex& sourceParent) const
{
  QAbstractItemModel* src = sourceModel();
  const QModelIndex srcIndex = src->index(sourceRow, 0, sourceParent);

  if (srcIndex == m_rootForFiltering)
  {
    // Always allowed, or children don't show up.
    return true;
  }

  // Allow if this index represents a file and not a directory.
  return itemAtIndexIsAFile(srcIndex);
}

This seems to allow filtering to work properly. However, there is a strange issue if I select an item in the table view and then choose a different directory in the tree view. QSortFilterProxyModel: index from wrong model passed to mapFromSource is spammed to my terminal 9 times, and then I get a crash as follows:

___lldb_unnamed_symbol11407 (@___lldb_unnamed_symbol11407:41)
QSortFilterProxyModel::flags(QModelIndex const&) const (@QSortFilterProxyModel::flags(QModelIndex const&) const:43)
QAbstractItemView::focusInEvent(QFocusEvent*) (@QAbstractItemView::focusInEvent(QFocusEvent*):41)
QWidget::event(QEvent*) (@QWidget::event(QEvent*):763)
QFrame::event(QEvent*) (@QFrame::event(QEvent*):14)
QApplicationPrivate::notify_helper(QObject*, QEvent*) (@QApplicationPrivate::notify_helper(QObject*, QEvent*):42)

... call stack continues ...

I've checked my code and the errors about the index coming from the wrong model don't appear to be triggered by calls that I'm making myself. I can only assume that I'm mis-using the proxy models in some way, but I'm not sure exactly how. I'm not adding or removing items from the model, so the underlying data should not be changing.

This issue does not happen if I remove the call to invalidateFilter(). It should also be noted that I can't simply switch on recursiveFilteringEnabled: this means that any visible child will have its parent visible as well, which causes any directories which contain files to pass the filter and show up in the table view. In case it's useful, I'm developing and testing this implementation on Kubuntu.

答案1

得分: 1

经过进一步研究,我认为我已经解决了这个问题。如果在表格视图中显示了嵌套目录,那么链中父目录的所有模型索引也需要通过过滤器,否则什么都不会显示。我不太确定具体问题是什么,但我怀疑表格视图期望得到一定数量的索引,但由于我没有考虑到上述规则,它无法获得这些索引。在这种状态下与视图互动会导致某种未定义的行为。

在实施了上述规则的检查后,似乎一切都正常工作了。

英文:

After some further research, I think I've solved the issue. If a nested directory is being displayed in the table view, all model indices for parent directories in the chain also need to pass the filter, or nothing will display. I'm not quite sure what the exact issue was, but I suspect that the table view was expecting a certain number of indices that it wasn't able to get because I was not catering for the aforementioned rule. Interacting with the view in this state caused some sort of undefined behaviour.

After implementing a check for the above rule, things seem to work again.

huangapple
  • 本文由 发表于 2023年3月1日 16:45:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/75601355.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定