Метод QML ListView positionViewAtEnd() делает прямо противоположное

Я схожу с ума. У меня есть ListView внутри ScrollView, подключенный к модели, которая наследует QAbstractListModel. Когда объекты добавляются в модель, ListView показывает их с помощью делегата. Все идет нормально.

Но я действительно хочу, чтобы представление оставалось прокручиваться до дна (например, в окне чата), и мне очень трудно сделать это. Вот соответствующий код QML:

Rectangle {
    ScrollView {
        [anchor stuff]

        ListView {
            id: messageList
            model: textMessageFiltered
            delegate: messageDelegate
        }
    }

    TextField {
        id: messageEditor
        [anchor stuff]

        onAccepted: {
            controller.sendTextMessage(text)
            text = ""

            /* This works. */
            //messageList.positionViewAtEnd();
        }
    }

    Component {
        id: messageDelegate
        Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right

            color: "white"
            height: nameText.height + 4

            Text {
                id: nameText
                wrapMode: Text.Wrap
                text: "<b>" + authorName + " (" + authorId + ")</b>  " + message
                [anchor stuff]
            }
            ListView.onAdd: {
                console.log("This prints just fine!")
                messageList.positionViewAtEnd()
            }
        }
    }
}

Действительно странная вещь, это то, что messageList.positionViewAtEnd() (в конце файла) фактически переходит к началу. Без вызова представление остается на месте, даже когда в списке появляются новые записи. И действительно, если вы посмотрите на документацию Qt для ListView.positionViewAtEnd (), она говорит:

Позиционирует вид в начале или в конце, учитывая...

Это глупая ошибка в документации или как? Я перепробовал все, что мог придумать, чтобы сделать эту работу, особенно positionViewAtIndex() метод и использование маркеров, чтобы вызвать прокрутку. Но ничего не работает. Обратите внимание /* This works. */ комментарий в исходном коде выше. Когда это включено, это работает совершенно нормально! (кроме, конечно, он переходит к индексу ListView.count()-2, а не к концу списка)

Кто-нибудь знает, что здесь может быть не так? Какие-нибудь примеры, которые я мог бы попытаться доказать, что есть ужасная, ужасная ошибка в QML?

Я использую Qt 5.3.1 с QtQuick 2.0 (или 2.1 или 2.2 тоже не удается). Я перепробовал много, много других конфигураций и кода, поэтому, пожалуйста, спросите, нужна ли вам дополнительная информация. Я полностью исчерпал свое Google-фу.

Спасибо!


Редактировать 1

Хотя принятый ответ решает вышеуказанную проблему, он включает в себя добавление Component.onCompleted делегату. Кажется, это вызывает проблемы при прокрутке списка, потому что (я полагаю) делегаты добавляются в представление при прокрутке вверх, вызывая вызов триггера onCompleted, даже если элемент модели не является новым. Это крайне нежелательно. На самом деле приложение зависает, когда я пытаюсь прокрутить вверх, а затем добавить новые элементы в список.

Похоже, мне нужен сигнал model.onAdd() вместо использования экземпляра делегата для запуска прокрутки. Есть идеи?


Редактировать 2

И как это НЕ работает?

    ListView {
        id: messageList
        model: textMessageFiltered
        delegate: messageDelegate

        onCountChanged: {
            console.log("This prints properly.")
            messageList.positionViewAtEnd()
        }
    }

Печатается текст "Это печатает правильно", так почему же он не позиционируется? На самом деле, кажется, чтобы сбросить позицию наверх. Так я попробовал positionViewAtBeginning(), но это сделало то же самое.

Я полностью в тупике. Это похоже на ошибку.

2 ответа

Вам нужно установить currentIndex также.

testme.qml

import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0

ApplicationWindow {
    title: qsTr("Hello World")
    width: 300
    height: 240

    ScrollView {
        anchors.fill: parent

        ListView {
            anchors.fill: parent

            id: messageList
            model: messageModel
            delegate: Text { text: mytextrole }
            highlight: Rectangle { color: "red" }
            highlightMoveDuration: 0

            onCountChanged: {
                var newIndex = count - 1 // last index
                positionViewAtEnd()
                currentIndex = newIndex
            }
        }
    }

    ListModel {
        id: messageModel
        ListElement { mytextrole: "Dog"; }
        ListElement { mytextrole: "Cat"; }
    }

    Timer {
        property int counter: 0
        running: true
        interval: 500
        repeat: true

        onTriggered: {
            messageModel.append({"mytextrole": "Line" + (counter++)})
        }
    }
}

Все еще есть некоторые прыжки к первому элементу и прыжки вниз на долю секунды.

В документации есть примечание:

Примечание: методы должны вызываться только после завершения Компонента. Чтобы позиционировать представление при запуске, этот метод должен вызываться Component.onCompleted.

Измени свой ListView.onAdd: в

Component.onCompleted: {
    console.log("This prints just fine!")
    messageList.positionViewAtEnd()
}

И это хорошо работает.

В вашем случае ListView испускает add сигнал до того, как новый делегат будет создан и завершен. ListView все еще работает над чем-то за сценой, поэтому positionViewAtEnd не может работать как ожидалось. А также /* This works. */ потому что он вызывается после завершения нового делегата. Однако не думайте, что это всегда работает. Просто следуйте примечанию, позвоните positionViewAtEnd в Component.onCompleted, в документации.

Другие вопросы по тегам