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-fu.

Спасибо!


Изменить 1

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

Кажется, мне нужен сигнал model.onAdd() вместо использования экземпляра делегата для запуска прокрутки. Любые идеи?


Изменить 2

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

    ListView {
        id: messageList
        model: textMessageFiltered
        delegate: messageDelegate

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

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

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

Ответ 1

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

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

Измените ListView.onAdd: на

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

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

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

Ответ 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++)})
        }
    }
}

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