I am currently struggling to combine two seemingly simple concepts: alternating row colors and a smooth moving highlight item in QML ListViews.
Consider this simple example:
import sys
from PySide6.QtCore import QUrl
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QApplication
if __name__ == '__main__':
app = QApplication([])
engine = QQmlApplicationEngine()
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
app.exec()
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
ApplicationWindow {
id: root
width: 400
height: 500
visible: true
font.family: 'Noto Sans'
ListView {
id: listView
width: root.width
height: root.height
clip: true
focus: true
reuseItems: true
boundsBehavior: Flickable.StopAtBounds
model: [
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia",
"Wikipedia is a free online encyclopedia"
]
delegate: Rectangle {
height: 40
width: parent.width
color: ListView.isCurrentItem ? Material.accent : index % 2 === 0 ? palette.base : palette.alternateBase
MouseArea {
anchors.fill: parent
onClicked: {
listView.currentIndex = index
}
}
RowLayout {
height: parent.height
Label {
text: index
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.preferredWidth: 100
}
Label {
text: modelData
Layout.preferredWidth: 300
}
}
}
}
}
Here, we have a ListView and a delegate with alternating row colors. We can highlight the item by either clicking on it or using the arrow keys:
It's also quite simple to achieve a smooth highlighting (without alternating row colors) by swapping out just a few lines of code:
// ...
highlight: Rectangle {
height: 40
width: parent.width
color: Material.accent
}
delegate: Rectangle {
height: 40
width: parent.width
color: 'transparent'
// ...
}
// ...
In isolation, both of these concepts are easy to apply but when combining them, the struggle gets real 😅
// ...
highlight: Rectangle {
height: 40
width: parent.width
color: Material.accent
}
delegate: Rectangle {
height: 40
width: parent.width
color: ListView.isCurrentItem ? 'transparent' : index % 2 === 0 ? palette.base : palette.alternateBase
// ...
}
// ...
results in
// ...
highlight: Rectangle {
height: 40
width: parent.width
color: Material.accent
z: 1
}
delegate: Rectangle {
height: 40
width: parent.width
color: index % 2 === 0 ? palette.base : palette.alternateBase
// ...
}
// ...
results in
I often find myself believing that this task shouldn't be overly difficult to accomplish, yet I can't shake the feeling that I might be overlooking something. Has anyone else encountered a similar issue and discovered a solution?
Interesting question!
The difficulty is due to the z stacking order of items. Z orders are resolved by subtrees, this means that an item can't stack itself between a sibling and the sibling's children. It will be either above the sibling and all of its descendants, or below all of them.
To achieve what you want, we need to separate the background from the content of your delegate so they aren't part of the same subtree anymore.
For that you can explicitly set the parent of the delegate's background (and its geometry). I also refactored the delegate to use ItemDelegate
to make the code a little clearer:
highlight: Rectangle {
height: 40
width: parent.width
color: Material.accent
}
delegate: ItemDelegate {
id: delegate
height: 40
width: parent.width
background: Rectangle {
parent: delegate.parent
y: delegate.y
height: delegate.height
color: index % 2 === 0 ? palette.base : palette.alternateBase
}
onClicked: listView.currentIndex = index
contentItem: RowLayout {
Label {
text: index
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.preferredWidth: 100
}
Label {
text: "Wikipedia is a free online encyclopedia"
Layout.preferredWidth: 300
}
}
}
One could be more explicit by setting a z
of 0 for the backgrounds, 1 for the highlight and 2 for the delegates but the default order seems to work out here.
Using GammaRay we can even see the items layers: