13

What I need to do: I need to create a chat window using a ListView in QML that stores the chat-messages. I set listView.positionViewAtEnd() in order to follow the last messages. I disable positionViewAtEnd when I scroll upwards such that I can read the past messages without jumping at the end every time I receive a new message.

The problem: After scrolling up, every time I receive a new message it jumps at the beginning of list. To solve that I manage to store the contentY of the list and reset it every time onCountChanged handler is called (see the code below):

ListView {
    id: messagesList
    model: contact? contact.messages: []
    delegate: delegate
    anchors.fill: parent
    anchors.bottomMargin: 20
    height: parent.height
    anchors.margins: 10

    property int currentContentY

    onMovementEnded: {
       currentContentY = contentY
    }

    onCountChanged: {
        contentY = currentContentY
    }
    onContentYChanged: {
        console.log(".....contentY: " + contentY)
    }
}   

The problem is that even though I set the last contentY I had, before the model was changed, the list still jumps a bit (several pixels, not at the end or beginning) and it doesn't jump always. And when I go to the top of the list and print the contentY I get negative values. Theoretically, contentY at the beginning of the list should be 0.

Can somebody tell me what is going wrong? Or maybe suggest another solution to create my message list?

Than you in advance! :)

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
Ispas Claudiu
  • 1,890
  • 2
  • 28
  • 54
  • How do you populate/modify `contact.messages` ? – GrecKo Jun 25 '15 at 07:37
  • It's a list exposed from c++ using qqmllistproperty. When it is modified from c++ (after a message is added) I emit a signal and the listview from qml knows to update its model – Ispas Claudiu Jun 25 '15 at 07:39
  • Is that `MovementEnded` handler called consistently? Never used `QQmlListProperty` as a model but it seems to work [quite fine](http://stackoverflow.com/a/28534291/2538363). Anyhow, a subclass of `QAbstractListModel` would do the job here perfectly fine. – BaCaRoZzo Jun 25 '15 at 08:49
  • Yes. MovementEnded is called everytime the scroll ended. The documentation states : "If a C++ model class is used, it must be a subclass of QAbstractItemModel or a simple list." so my model is a subclass of qabstractitemmodel. – Ispas Claudiu Jun 25 '15 at 08:59
  • 2
    `QAbstractItemModel` or any subclass is fine. Since `ListView` does show a list using `QAbstractListModel` is better. At least if you don't have to move ina tree structure. Which does not seem your case. – BaCaRoZzo Jun 25 '15 at 13:41
  • I resolved the same problem, but based on Qt Widgets. I connected to model data changed signal, and scroll to previous item. – synacker Jun 25 '15 at 14:47
  • 1
    Thanks @BaCaRoZzo for your suggestion. I used QAbstractListModel and now it works. – Ispas Claudiu Jun 26 '15 at 14:33
  • Uhm...maybe a not correctly implemented method of `QAbstractItemModel` resulted in the wrong behaviour? Quite strage. Anyhow, I'm glad you've solved your issue. :) Check out the code for the other answer, it can still be usuful if the behaviour of the `ListView` does not hurt your expectations. – BaCaRoZzo Jun 26 '15 at 15:00

2 Answers2

2

One possible solution schould be insert ListView into Flickable and disable interactive flag for ListView

    Flickable {
        id: fparent
        anchors.fill: parent
        anchors.bottomMargin: 20
        anchors.margins: 10

        interactive: true
        clip: true
        flickableDirection: Flickable.VerticalFlick
        contentHeight: messagesList.height

        ListView {
            id: messagesList
            width: parent.width
            height: childrenRect.height
            clip: true
            model: contact? contact.messages: []
            delegate: delegate
            interactive: false
            onCountChanged: {
                fparents.returnToBounds();
            }
        }
    }
Jarik
  • 21
  • 2
0

Why not use onCountChanged slot in order to set the ListView at the end ?

onCountChanged: {
   messagesList.positionViewAtEnd()
}
Cristea Bogdan
  • 116
  • 1
  • 11
  • 1
    I used that, but I don't want to jump at the end if I receive any new message when I am not at the end (scrolled upwards, when I am in the middle of the list) – Ispas Claudiu Jun 26 '15 at 09:17
  • In this case you can disable positionViewAtEnd() based on some event and enable it on another event. I find your requirements rather contradictory. – Cristea Bogdan Jun 26 '15 at 11:29
  • 1
    This is not the answer. And why is my question contradictory? Take the skype example. I want to do create something similar. positionViewAtEnd solves the problem only when I'm at the bottom of the list! I When I'm positioned in the middle of the list I don't want to jump at the end, I want to stay there. – Ispas Claudiu Jun 26 '15 at 13:29
  • You might use onMovementStarted slot to disable positionViewAtEnd(), then activated it in onMovementEnded to enable it. If you stop in the middle not sure how long you want to stay there, maybe you could also use a timer, once the movement ended wait for a few seconds before enabling positionViewAtEnd(). – Cristea Bogdan Jun 26 '15 at 14:38