Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Handle the highlight of ListView



  • Hello,

    I'm trying to Highlight a ListView when the current item change.
    I want the current item to change when I press up or down arrow or when I click on the item from the ListView.

    I achieve to put a highlight on on element but I can't have the current item to change properly or the highlight to follow the currentitem I don't really know what is wrong here.

    Here is my code :

           ListModel {
               id: myModel
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
               ListElement {
                   textButtonR: "Open"
                   textButtonL: "Close"
                   imageSource: "../img/sun.svg"
               }
    
           }
    
           Component{
               id: del
               ManualFunction{
                   height: container.height/4
                   textButtonR: model.textButtonR
                   textButtonL: model.textButtonL
                   imageSource: model.imageSource
               }
    
           }
    
           Component { //The hightlight
               id: highlight
               Rectangle {
                   width: container.width*0.99
                   height: container.height/4
                   anchors.horizontalCenter: parent.horizontalCenter
                   radius: 5
                   color: "#8f9193"
                   y: listView.currentItem.y;
               }
           }
    
           ListView{
               id: listView
               anchors.fill: parent
               clip: true
               anchors.horizontalCenter: parent.horizontalCenter
               model: myModel
               delegate: del
               highlight: highlight
               highlightFollowsCurrentItem: false
               focus: true
               currentIndex: 0
           }
       }
    
       Keys.onDownPressed: { //Listen for down arrow pressed
           if(listView.currentIndex < listView.count){
               listView.currentIndex = listView.currentIndex++
               console.log("Down : " + listView.currentIndex)
           }
       }
       Keys.onUpPressed: {  //Listen for Up arrow pressed
           if(listView.currentIndex > 0){
               listView.currentIndex = listView.currentIndex--
               console.log("Up : " + listView.currentIndex)
           }
       }
    

    thank you in advance for your help





  • @LeLev
    It does not completly works in my case because it make my button totaly unusable. The mouse area overwrite the mouse area of my button.
    And I don't have the up and down arrow management.



  • @DavidM29 i see, unfortunalely i don't know any 'official/elegant' solution to handle this situation :
    What i did is : when the user will click inside the delegate, i will just check if the mouse cursor is inside the button 'real button'
    see :

    import QtQuick 2.10
    import QtQuick.Window 2.10
    import QtQuick.Controls 1.4
    import QtQuick.Controls.Styles 1.4
    import QtQuick.Layouts 1.3
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
       
    
        Rectangle {
            id:background
            color: "grey"
            width: 200; height: 300
            ListModel {
                id: myModel
                ListElement {
                    textButtonR: "Open"
                    textButtonL: "Close"
                    imageSource: "spin.png"
                }
                ListElement {
                    textButtonR: "Open"
                    textButtonL: "Close"
                    imageSource: "spin.png"
                }
                ListElement {
                    textButtonR: "Open"
                    textButtonL: "Close"
                    imageSource: "spin.png"
                }
    
            }
    
    
            //Delegate
            Component {
                id: del
                Rectangle{
                    id: r
                    height: 90
                    width: 200
                    color:'indigo'
                    border.width:ListView.isCurrentItem ? 2 : 0 // you can have a 'real delegate' like  highlightBar  OR simply change the delegate when item is currentItem
                    property string _textButtonR: textButtonR
                    property string _imageSource: imageSource
    
                    Column{
                        anchors.fill: parent
                        Image{
                            source:_imageSource
                            height: 20
                            width: 20
    
                        }
                        Text{
                            text:_textButtonR
                        }
                        Button{
                            id:_btn
                            text: "btn :" + index
    
                        }
    
                    }
    
                    function btnClick(_ind){
                       console.log("btn" +_ind + "click")
                    }
                    function delegateClick(_ind){
                        console.log("delegate click; index " +  _ind)
                        listView.currentIndex=_ind
    
                    }
    
                    MouseArea{
                        anchors.fill: parent
                        propagateComposedEvents: true
                        onClicked: _btn.hovered ? r.btnClick(index) : r.delegateClick(index)  // the test
                    }
                }
    
            }
    
            //highlight
            Component { // 'real'  delegate
                id: highlightBar
                Rectangle {
                    width: listView.currentItem.width/10; height: listView.currentItem.height
                    color: "#FFFF88"
                    y: listView.currentItem.y
                    z:2
                    anchors.right:parent.right
                    Behavior on y { SpringAnimation { spring: 2; damping: 0.1 } }
                }
            }
    
            ListView {
                id: listView
                width: 200;
                height: parent.height
                model: myModel
                delegate: del
                clip: true
                focus:true
                highlight: highlightBar
                highlightFollowsCurrentItem: false
    
            }
        }
    }
    

  • Qt Champions 2018

    @DavidM29 The up and down keys work for me in your initial code.
    About changing the current index when clicking in one of your delegate, have you tried doing it in your delegate ?

    I'm not really sure what is your problem here.



  • @GrecKo hi,
    The problem is about differentiating clicks ?

    -Delegate has mousearea
    -Item has button

    How to know what was clicked ?

          // How to differentiate clicks ?
            Rectangle{
                color: "red"
                height: 100
                width: 100
                Button{
                    id:realBtn
                    onClicked: { 
                    //    console.log("red Button clicked")
                    }
                }
                MouseArea{
                    anchors.fill: parent
                    onClicked: console.log("red Item clicked")
    // onClicked:  realBtn.hovered ? console.log("btn clicked") : console.log("red Item clicked") 
    
                }
            }
    


  • @LeLev
    Your solution seemed right to me but as soon as I tried it did not worked. I believe it is because the Delegate does not have a classic button but a custom that I made from a rectangle and MouseArea. The mouse area don't have the hovered property. So I tried with hoverEnabled but I haven't been able to make it works.

    @GrecKo
    On my side the press of the up and down arrow is not working. When I press a button nothing happening the index is probably not changing or if it change it does not affect the listView



  • @DavidM29 hi,

    @DavidM29 said in Handle the highlight of ListView:

    The mouse area don't have the hovered property

    yes but it has containsMouse : bool, you can emulate the same behavior



  • @LeLev
    The containMouse don't seems to work for me but I found the entered which seems to works fine. But know I need a way to simulate the click in the mouse area.



  • @DavidM29 said in Handle the highlight of ListView:

    The containMouse don't seems to work for me

    hoverEnabled:true must work (i cant test now)



  • @LeLev said in Handle the highlight of ListView:

    hoverEnabled

    Try it as well but the only working is entered which activate when I the mouse entrer in the mouse area which is the same as hovered to me



  • @DavidM29 perfect



  • @LeLev Do you know how could I sent a press event on the mouse area ? Because my Custom button change it style as I press it and I want to keep that.

    By the way I would like to have the up and down arrow working do you have any clue for that ?


  • Qt Champions 2018

    The up and down keys not working is a problem of focus, make sure that the Item where the Keys.on*Pressed has activeFocus.

    For changing your currentItem on click, do you want it to change also when clicking a button in your delegate, or just when clicking in the delegate but not on the buttons?
    Do you want it to happen on click or just on press? (Click is press + release)

    If you want further help, I advise you to send a complete but small self contained example that we could reproduce.

    Sending touch events manually is a rabbit hole you don't want to go down.



  • @GrecKo
    Thank you I'll try to look at my focus at the moment I can't have the keys to work.

    I would like to be able to change the currentItem when I click on the delegate and be able to use the two buttons and if possible as well change the current item when I click on the button of any other item.

    If I still have issue using the up and down keys I will make a complete example.



  • @GrecKo
    I got the up and down key to work. It was indeed because of the focus knonw I'll have to find a way to give the focus to the right element a the good time. Thank you very much.

    I still need to get the button to work now.


  • Qt Champions 2018

    For the click, I see two sensible solutions.

    Have a MouseArea in your delegate that's above your buttons.
    on the onPressed handler, set the currentIndex and reject the mouse event (mouse.accepted = false) so the buttons below can receive it.

    If you want it to work on click and not on press, you would have to position the MouseArea below your buttons and set the currentIndex in the onClicked handlers of your buttons and your MouseArea.



  • @GrecKo

    Thank you for those two ideas I'll try the first. The onPressed is good to me. It kind of tricky but it could works.



  • @GrecKo
    It seems to work quick good. Except that sometime after I change a few time the current index by clicking on a button the program crash completly.

    I got this error message :

    ASSERT: "item->contains(localPos)" in file items\qquickwindow.cpp, line 2549
    Invalid parameter passed to C runtime function.
    Invalid parameter passed to C runtime function.
    

    Here is the code I use :

    The window with the ListView :

        Rectangle{
            id: container
            anchors.fill: parent
            color: "transparent"
    
            ListModel {
                id: myModel
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
                ListElement {
                    libelleBoutonG: "Ouvrir"
                    libelleBoutonD: "Fermer"
                    icone: "../img/sun.svg"
                }
    
            }
    
            Component{
                id: del
                FonctionManuelle{
                    id : wrapper
                    height: container.height/4
                    width: container.width
                    libelleBoutonG: model.libelleBoutonG
                    libelleBoutonD: model.libelleBoutonD
                    icone: model.icone
                    states: State {
                        name: "Current"
                        when: wrapper.ListView.isCurrentItem
                        PropertyChanges { target: wrapper; x: 20 }
                    }
    
                    MouseArea {
                        anchors.fill: parent
                        propagateComposedEvents: true
                        onPressed: {
                            mouse.accepted = false
                            listView.currentIndex = index
                            listView.forceActiveFocus()
                        }
    
                    }
                }
    
            }
            Keys.onDownPressed: { //Listen for down arrow pressed
                listView.incrementCurrentIndex()
    
            }
            Keys.onUpPressed: {  //Listen for Up arrow pressed
                listView.decrementCurrentIndex()
            }
    
            Component {
                id: highlight
                Rectangle {
                    width: container.width*0.99
                    height: container.height/4
                    anchors.horizontalCenter: parent.horizontalCenter
                    radius: 5
                    color: "#8f9193"
                    y: listView.currentItem.y;
                }
            }
    
            ListView{
                id: listView
                anchors.fill: parent
                clip: true
                anchors.horizontalCenter: parent.horizontalCenter
                model: myModel
                delegate: del
                highlight: highlight
                highlightFollowsCurrentItem: false
                focus: true
                //currentIndex: 0
                spacing: 10
    
            }
        }
    

    FonctionManuelle.qml

    Item {
        anchors.horizontalCenter: parent.horizontalCenter
        property string libelleBoutonG : ""
        property string libelleBoutonD : ""
        property alias btnG: op
        property alias btnD: clo
        property string icone: ""
        property bool isFocused: false
    
        Rectangle{
            width: parent.width
            height :parent.height
            color: "transparent"
    
            Rectangle{
                width: parent.width/1.5
                height: parent.height
                anchors.horizontalCenter: parent.horizontalCenter
                radius: height/4
                CustomButton{
                    id: op
                    libelle: libelleBoutonG
                    anchors.right: test.left
                    anchors.rightMargin: parent.width/11
                    anchors.verticalCenter: test.verticalCenter
    
                }
    
                Image {
                    id: test
                    source: icone
                    height: 100
                    width: height*2
                    anchors.centerIn: parent
                    fillMode: Image.PreserveAspectFit
                    sourceSize.width: width
                    sourceSize.height: height
                }
    
                CustomButton{
                    id: clo
                    libelle: libelleBoutonD
                    anchors.left : test.right
                    anchors.leftMargin: parent.width/11
                    anchors.verticalCenter: test.verticalCenter
    
                }
            }
        }
    
    }
    
    

    CustomButton.qml :

    Rectangle{
        property string libelle: ""
        property alias press: press
    
        width: name.width < 100 ? 100 : 250
        height: 30
        border.width: 1
        border.color: "black"
        radius: height/2
        color: press.pressed ? "#e30613" : "white"
    
        Text{
            id: name
            text: qsTr(libelle)
            font.pixelSize: 13
            font.bold: true
            anchors.centerIn: parent
            z:99
        }
    
        MouseArea{
            id: press
            anchors.fill: parent
        }
    }
    

    Do you see anything wrong here ? Can you try to reproduce the bug I have ? It work fine for 10 to 15 click and then crash. Usualy when I decrement the index.

    Could it be this line from the Highlightbar ? :

    y: listView.currentItem.y;
    

    Edit :
    I think I found the reason. It is as soon as I click on a button that require the ListView to scroll. The scroll probably don't have the time to end and the Highlight don't know where it should go. Do you have any idea how can I prevent this behaviour ?
    Like maybe finish the scroll then change the currentItem. but I don't know how to make asynchronous action in C++/QML



  • I found a solution to not use the line :

    y: listView.currentItem.y;
    

    Here is what I have done :

    I remove the previous line and change :

    highlightFollowsCurrentItem: false
    

    to :

    highlightFollowsCurrentItem: true
    

    And everything seems good now.


Log in to reply