Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-91776

On Windows, calling QListView::scrollTo() before widget is shown breaks layout, causes crashes

    XMLWordPrintable

Details

    • Bug
    • Resolution: Cannot Reproduce
    • P1: Critical
    • None
    • 5.15.2, 6.0.1, 6.2.0 Alpha
    • Widgets: Itemviews
    • None
    • Windows 10 x64, MSVC ABI
      Reproduced on Qt 5.15.2, binary and from source, as well as Qt 6 dev branch built from source
    • Windows

    Description

      Steps to reproduce

      Create a Qt Widgets application which does the following:

      • Create a QListWidget, or QListView with any model (including QStringListModel).
      • Call QListView::setWrapping(true).
      • Call QListView::scrollTo(QListView::model()->index(0, 0)).
      • Both functions must happen during the QMainWindow subclass constructor call, before the event loop resumes and the widget shows up on-screen.

      I've attached a test case reproducer to this bug.

      Results

      On Windows (not Linux), this causes the QListView/Widget to layout incorrectly and take up more vertical space than available (creating a vertical scrollbar). Focusing any element in the first column, then pressing the keyboard down-arrow key until the focus wraps to the next column, results in an internal crash in QListModeViewBase::verticalScrollToValue(), at the line containin scrollValueMap.at(verticalScrollBar()->value()). Stack trace (taken from Qt 6) is available here.

      If you call setWrapping(true) a second time after scrollTo(), the bug goes away.

      Analysis

      • Create a QListWidget, or QListView with any model (including QStringListModel).
      • Call QListView::setWrapping(true). Looking at the Qt 5 source, this is what happens:
        • d->modeProperties |= uint(QListViewPrivate::Wrap);
        • d->setWrapping(enable); (merely assigns a bool)
        • d->doDelayedItemsLayout(); (starts a QTimer with 0 delay)
          • if (!delayedPendingLayout) (branch skipped, function returns)
      • Call QListView::scrollTo(QListView::model()->index(0, 0)). There's a second optional argument defaulting to EnsureVisible.
        • ... it doesn't matter.
        • const QRect rect = visualRect(index);
        • QListView::visualRect():
          • return d->mapToViewport(rectForIndex(index));
          • QListView::rectForIndex():
            • return d_func()->rectForIndex(index);
            • QListViewPrivate::rectForIndex():
              • ...
              • executePostedLayout();
              • QAbstractItemViewPrivate::executePostedLayout():
                • interruptDelayedItemsLayout();
                • QAbstractItemViewPrivate::interruptDelayedItemsLayout():
                  • delayedLayout.stop();
                  • delayedPendingLayout = false;
                • const_cast<QAbstractItemView*>(q_func())->doItemsLayout();
                • QListView::doItemsLayout():
                  • ???
        • ... it doesn't matter.
      • (Optional) To workaround the bug, call QListView::setWrapping(true) again.
        • d->doDelayedItemsLayout();
          • if (!delayedPendingLayout) (branch taken)
            • delayedPendingLayout = true;
            • delayedLayout.start(delay, q_func());
      • Once the widget becomes visible... QAbstractItemView::event()
        • switch (event->type()) case QEvent::Show:
          • d->executePostedLayout();
          • (see stack trace above)

      It seems as if QListView::scrollTo() runs the layout algorithm before the widget gets shown, but with the wrong widget dimensions or something. Then it fails to recompute something (not sure) once the widget gets shown. This results in too many items per column, and crashes when switching columns.

      I'm not sure about the best solution; QListView::visualRect() isn't even a well-defined concept for a list view that isn't shown. But a crash is probably not a good behavior; maybe Qt could output a warning about computing the visualRect or layout, or calling scrollTo(), on a hidden widget. Or maybe scrollTo() can be queued and applied, or reapplied, once the widget is shown.

      Versions

      This happens with both the WindowsVista and Fusion styles, on multiple versions of Qt (5.15.2 and dev branch "6.2.0").

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            qt.team.quick.subscriptions Qt Quick and Widgets Team
            nyanpasu64 Nyan Pasu
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes