1

I want to have a QML ListView with a header that stays in place when the items are flicked or scrolled.
To achieve this, I have set the headerPositioning to ListView.OverlayHeader and changed the header's z-order to 2 to keep it on top of the items.
When the list view is displayed, I want the current item to be visible ('scrolled into view').
I have encountered the following issue: The current item is scrolled into view, but the presence of the fixed header is ignored, so the item is partially hidden underneath the header.

Here is my QML:

import QtQuick
import QtQuick.Controls
import QtQml.Models

Window {
    id: winMain
    width: 640
    height: 280
    visible: true
    title: qsTr("Hello World")

    ListModel {
        id: components
        ListElement { name: "A" }
        ListElement { name: "B" }
        ListElement { name: "C" }
        ListElement { name: "D" }
        ListElement { name: "E" }
        ListElement { name: "F" }
        ListElement { name: "G" }
        ListElement { name: "H" }
        ListElement { name: "I" }
    }

    ListView {
        id: componentList
        x: 10
        y: 10
        width: parent.width - 20
        height: parent.height
        spacing: 2
        currentIndex: 3
        focus: true
        clip: true
        preferredHighlightBegin: 50
        preferredHighlightEnd: height
        highlightRangeMode: ListView.ApplyRange
        header: Item {
            width: parent.width
            height: 50
            z: 2
            Rectangle {
                width: parent.width
                height: parent.height - componentList.spacing
                color: "#000000"
                Text {
                    anchors.centerIn: parent
                    text: "Components"
                    color: "white"
                    font.pointSize: 16
                }
            }
        }
        headerPositioning: ListView.OverlayHeader
        model: components
        delegate: Component {
            id: delItem
            Rectangle {
                width: componentList.width
                height: 40
                color: ListView.isCurrentItem ? "#FF0000" : "#00FF00"
                Text {
                    text: name
                    color: "white"
                    font.pointSize: 14
                    x: 5
                    anchors.verticalCenter: parent.verticalCenter
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: componentList.currentIndex = index
                }
            }
        }
    }
}

And this is what it looks like initially:

Partially hidden current item

What could I do to improve this?

Update: Based on the suggestions of smr, I added the three following lines to the original code, but found no difference in the behaviour:

        preferredHighlightBegin: 50
        preferredHighlightEnd: height
        highlightRangeMode: ListView.ApplyRange

I left the lines in the above code sample to show what has been tried so far.

8
  • If you don't want your ListView items to overlap with the header, why not move the header outside the ListView? Commented Aug 2 at 3:27
  • I may have to do that, but if possible I would like to use the built in functionality, provided that I have understood the purpose of the value ListView.OverlayHeader correctly: a header that is not subject to scrolling. Is my interpretation wrong? Commented Aug 2 at 15:03
  • Considering the header height is 50, you can set preferredHighlightBegin: ListView.ApplyRange, preferredHighlightBegin: 50, and preferredHighlightEnd: height, and your ListView will try to keep the selected item below the header. Commented Aug 3 at 7:02
  • @smr I have added the lines according to your suggestion, but this does not help. Commented Aug 4 at 8:28
  • The highlightRange I mentioned in the comments is also part of the solution! Feel free to edit the question to clarify it, but don’t include the full solution there!! Commented Aug 4 at 9:09

2 Answers 2

1

I did a few tweaks to your example:

  • Use ListView.view to eliminate the need for id: componentList
  • Use ItemDelegate to eliminate the need to have Rectangle + Text + MouseArea
  • Use highlight to elminate the need for ListView.isCurrentItem binding
  • Use Qt.callLater to defer the initial initialization:
  • Use different values for preferredHighlightBegin and preferredHighlightEnd

But, yeah, the stuff I did use which you already had:

  • Use currentIndex
  • Use headerPositioning: ListView.OverlayHeader

EDIT

Because I found it troublesome to workaround the ListView header, I updated the answer showing that you can use Page like a wrapper component because it has a more intuitive header, background, and footer properties:

Page {
    anchors.fill: parent
    anchors.margins: 10
    background: Rectangle { color: "green" }
    header: Label {
        height: 50
        color: "white"
        text: "Components"
        horizontalAlignment: Qt.AlignHCenter
        verticalAlignment: Qt.AlignVCenter
        font.pointSize: 16
        background: Rectangle { color: "black" }
    }
    footer: Label {
        height: 50
        color: "white"
        text: "Footer"
        horizontalAlignment: Qt.AlignHCenter
        verticalAlignment: Qt.AlignVCenter
        font.pointSize: 16
        background: Rectangle { color: "black" }
    }
    ListView {
        anchors.fill: parent
        spacing: 2
        focus: true
        clip: true
        preferredHighlightBegin: height / 2
        preferredHighlightEnd: height / 2
        highlightRangeMode: ListView.ApplyRange
        model: 100
        delegate: ItemDelegate {
            id: delItem
            width: ListView.view.width
            height: 40
            text: modelData
            palette.text: "white"
            font.pointSize: 14
            onClicked: ListView.view.currentIndex = index
        }
        highlight: Rectangle { color: "red" }
        Component.onCompleted: Qt.callLater( () => { currentIndex = 33 } )
    }
}

You can Try it Online!

Sign up to request clarification or add additional context in comments.

2 Comments

Hi Stephen, thanks for your contribution! The z-value of the header needs to be set to a value > 1, otherwise the items will be displayed on top of it.
Hi HJP, yeah, I found the ListView's header's default z-value to be counter-intuitive. I've updated my answer to show that Page can be used like a component and has, I think, a more intuitive implementation.
0

I have found a workaround. Instead of setting currentIndex directly, it is set in Component.onCompleted. Although this causes some flicker, it is ok for me.

Here is the code:

import QtQuick
import QtQuick.Controls
import QtQml.Models

Window {
    id: winMain
    width: 640
    height: 280
    visible: true
    title: qsTr("Hello World")

    ListModel {
        id: components
        ListElement { name: "A" }
        ListElement { name: "B" }
        ListElement { name: "C" }
        ListElement { name: "D" }
        ListElement { name: "E" }
        ListElement { name: "F" }
        ListElement { name: "G" }
        ListElement { name: "H" }
        ListElement { name: "I" }
    }

    ListView {
        id: componentList
        x: 10
        y: 10
        width: parent.width - 20
        height: parent.height
        spacing: 2
        focus: true
        clip: true
        preferredHighlightBegin: 50
        preferredHighlightEnd: height
        highlightRangeMode: ListView.ApplyRange
        header: Item {
            width: parent.width
            height: 50
            z: 2
            Rectangle {
                width: parent.width
                height: parent.height - componentList.spacing
                color: "#000000"
                Text {
                    anchors.centerIn: parent
                    text: "Components"
                    color: "white"
                    font.pointSize: 16
                }
            }
        }
        headerPositioning: ListView.OverlayHeader
        model: components
        delegate: Component {
            id: delItem
            Rectangle {
                width: componentList.width
                height: 40
                color: ListView.isCurrentItem ? "#FF0000" : "#00FF00"
                Text {
                    text: name
                    color: "white"
                    font.pointSize: 14
                    x: 5
                    anchors.verticalCenter: parent.verticalCenter
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: componentList.currentIndex = index
                }
            }
        }
        Component.onCompleted: {
            componentList.currentIndex = 2
        }
    }
}

Anyhow, I will file a bug report.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.