QML Expandable Menu with SubItems



  • I'm pretty new to QML and am trying to make a menu with expandable sections. I have been trying to solve this for a couple days, but am stuck on how best to approach this. If anyone has any suggestions to point me in the right direction, I'd be very grateful.

    Here's a snazzy graphic to demonstrate what I am trying to do. http://pasteboard.co/xPyl3oy.jpg. I've got an expandable component already and was trying to make it work with a ListView and a ListModel.

    I tried to use the ListView with sections, however, unfortunately the Listview delegates are not children of the section delegates (like I need them to to be expandable), but are siblings.

    Item{
        id:root
        width: 1406; height: 536
    
    
        ListModel {
            id: animalsModel
            ListElement { name: "Puss in Boots"; type: "Cats" }
            ListElement { name: "Bengal"; type: "Cats" }
            ListElement { name: "Pug"; type: "Dogs" }
            ListElement { name: "German Shepherd"; type: "Dogs" }
            ListElement { name: "Parrot"; type: "Birds" }
        }
    
        Component {
            id: sectionHeader
    
            Rectangle {
                width: 181
                color:"green"
                height: 50
    
                Text {
                    text: section
                    anchors.centerIn: parent
                }
            }
        }
    
    
        ListView {
            id: listing
            width: 181
            height: parent.height
            model: animalsModel
    
            delegate: listdelegate
    
            section.property: "type"
            section.criteria: ViewSection.FullString
            section.delegate: sectionHeader
        }
    
        Component {
            id: listdelegate
    
            Rectangle {
                id: menuItem
                width: 181
                height: 55
                color: ListView.isCurrentItem?"light blue":"white"
    
    
                Text {
                    id: text
                    text: name
                    anchors.centerIn: parent
                }
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        listing.currentIndex = index
                    }
                }
            }
        }
    }
    
    

    I then found this example: https://gist.github.com/elpuri/3753756

    However, in this one, the SubItems have the same index numbers, (the first subitem of each category is index 0) so I cannot use this for navigation.

    Does anyone have any other ideas? What am I missing?



  • Here's some other examples that might help you out. I'm also curious about this, so I'll have to give it a try in a little while to see if I can do it. I'll try to report any findings.

    Shape-Shifting Delegates
    Expanding Delegates



  • @QMLNewbie
    Try this:

    Item{
            id: root
            width: 1406; height: 536
    
            ListModel {
                id: animalsModel
                ListElement { name: "Puss in Boots"; type: "Cats"; aVisible: false}
                ListElement { name: "Bengal"; type: "Cats"; aVisible: false }
                ListElement { name: "Pug"; type: "Dogs"; aVisible: false }
                ListElement { name: "German Shepherd"; type: "Dogs"; aVisible: false }
                ListElement { name: "Parrot"; type: "Birds"; aVisible: false }
            }
    
            Component {
                id: sectionHeader
    
                Rectangle {
                    width: 181
                    color:"green"
                    height: 50
    
                    Text {
                        text: section
                        anchors.centerIn: parent
                    }
    
                    MouseArea{
                        anchors.fill: parent
                        onClicked: {
                            console.log("clicked");
                            for(var i=0; i<animalsModel.count; i++)
                            {
                                var animal = animalsModel.get(i);
                                if(animal.type === section)
                                    animal.aVisible = true;
                                else
                                    animal.aVisible = false;
                            }
                        }
                    }
                }
            }
    
    
            ListView {
                id: listing
                width: 181
                height: parent.height
                model: animalsModel
    
                delegate: listdelegate
    
                section.property: "type"
                section.criteria: ViewSection.FullString
                section.delegate: sectionHeader
            }
    
            Component {
                id: listdelegate
    
                Rectangle {
                    id: menuItem
                    width: 181
                    //height: 55
                    color: ListView.isCurrentItem ? "lightblue" : "white"
                    visible: aVisible
    
                    onVisibleChanged: {
                        if(visible)
                            height = 55;
                        else
                            height = 0;
                    }
    
                    Behavior on height {
                        NumberAnimation { duration: 1000 }
                    }
    
                    Text {
                        id: text
                        text: name
                        anchors.centerIn: parent
                    }
    
                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            listing.currentIndex = index;
                        }
                    }
                }
            }
        }
    

    It's looking a little bit ugly. But maybe will be helpful.



  • @JordanHarris: Thanks - I had seen the Expanding Delegates example, but not the shape shifting one. The problem is not the expanding mechanism, but how to populate the subItems in those two examples. If they are not all from one ListModel, I don't know how to use them for navigating - as in, currentIndex onClicked goes to this page, the other one goes to that page etc.

    @medyakovvit : That's really great, thank you! It is indeed quite ugly, and I am hesitant about writing to a bool variable in a ListModel, but it does seem to work for now. I'm gonna play around with it for a little bit more.



  • @QMLNewbie
    it would be easier, if we have some Item that wraps items of section. But it's look like we haven't.



  • I attempted this and it was much harder than I expected. The way I attempted it was to modify the gist mentioned in the OP by making the sub item delegate another ListView. The reason I don't want to use sections like you do is because I would want top level items to be selectable, like normal items. Then I would want to be able to expand the sub items by clicking, tapping, or pressing a key (maybe enter or space). Then when it's expended, I'd like the sub ListView to gain focus and have keyboard navigation. I haven't gotten the keyboard and focus done yet, but I could at least make it all look right. I'll keep working on it and post my solution of I can get it working.



  • I've been playing around with @medyakovvit 's example some more, but I am running into one big problem: I need the section delegate's color to change when the section is collapsed. This works fine if you collapse a section by clicking on it's own header. If you click on another section - which opens that section and collapses everything else - the other section delegates do not change color. I can not find any way of accessing them at all. The "section" property is just a string.

    I'm starting to think that this is simply not possible to do this!

    import QtQuick 2.6
    
    Item{
        id: root
        width: 1406; height: 536
        
        ListModel {
            id: animalsModel
            ListElement { name: "Puss in Boots"; type: "Cats"; aVisible: false}
            ListElement { name: "Bengal"; type: "Cats"; aVisible: false }
            ListElement { name: "Pug"; type: "Dogs"; aVisible: false }
            ListElement { name: "German Shepherd"; type: "Dogs"; aVisible: false }
            ListElement { name: "Parrot"; type: "Birds"; aVisible: false }
        }
        
        Component {
            id: sectionHeader
            
            Rectangle {
                id: testRect
                width: 181
                color:"green"
                height: 50
                
                Text {
                    text: section
                    anchors.centerIn: parent
                }
                
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        console.log("clicked");
                        for(var i=0; i<animalsModel.count; i++)
                        {
                            var animal = animalsModel.get(i);
                            if(animal.type === section && animal.aVisible == false) {
                                animal.aVisible = true;
                                testRect.color = "red"
                            }
                            else if (animal.type === section && animal.aVisible == true) {
                                animal.aVisible = false;
                                testRect.color = "green"
                            }
                            
                            else {
                                animal.aVisible = false;
                                //                            testRect.color = "green" //makes everything green
                            }
                        }
                    }
                }
            }
        }
        
        
        ListView {
            id: listing
            width: 181
            height: parent.height
            model: animalsModel
            
            delegate: listdelegate
            
            section.property: "type"
            section.criteria: ViewSection.FullString
            section.delegate: sectionHeader
        }
        
        Component {
            id: listdelegate
            
            Rectangle {
                id: menuItem
                width: 181
                //height: 55
                color: ListView.isCurrentItem ? "lightblue" : "white"
                visible: aVisible
                
                onVisibleChanged: {
                    if(visible)
                        height = 55;
                    else
                        height = 0;
                }
                
                Behavior on height {
                    NumberAnimation { duration: 300 }
                }
                
                Text {
                    id: text
                    text: name
                    anchors.centerIn: parent
                }
                
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        listing.currentIndex = index;
                    }
                }
            }
        }
    }
    


  • @QMLNewbie
    Maybe this workaround is suits you:

    Item{
            id: root
            width: 1406; height: 536
    
            ListModel {
                id: animalsModel
                ListElement { name: "Puss in Boots"; type: "Cats"; aVisible: false}
                ListElement { name: "Bengal"; type: "Cats"; aVisible: false }
                ListElement { name: "Pug"; type: "Dogs"; aVisible: false }
                ListElement { name: "German Shepherd"; type: "Dogs"; aVisible: false }
                ListElement { name: "Parrot"; type: "Birds"; aVisible: false }
            }
    
            Component {
                id: sectionHeader          
    
                Rectangle {
                    id: sectionHeaderRect
                    width: 181
                    color:"green"
                    height: 50
    
                    property var view: ListView.view
                    property var viewCurrentItem: ListView.view.currentItem
    
                    onViewCurrentItemChanged: {
                        if(viewCurrentItem){
                            if(viewCurrentItem.section === section)
                                color = "blue";
                            else
                                color = "green";
                        }
                    }
    
                    Text {
                        id: sectionHeaderText
                        text: section
                        anchors.centerIn: parent
                    }
    
                    MouseArea{
                        anchors.fill: parent
                        onClicked: {
                            var firstInSection = false;
                            for(var i=0; i<animalsModel.count; i++)
                            {
                                var animal = animalsModel.get(i);
                                if(animal.type === section)
                                {
                                    animal.aVisible = true;
                                    if(!firstInSection)
                                    {
                                        firstInSection = true;
                                        sectionHeaderRect.view.currentIndex = i;
                                    }
                                }
                                else
                                    animal.aVisible = false;
                            }
                        }
                    }
                }
            }
    
    
            ListView {
                id: listing
                width: 181
                height: parent.height
                model: animalsModel
    
                delegate: listdelegate
    
                section.property: "type"
                section.criteria: ViewSection.FullString
                section.delegate: sectionHeader
            }
    
            Component {
                id: listdelegate
    
                Rectangle {
                    id: menuItem
                    width: 181
                    //height: 55
                    color: ListView.isCurrentItem ? "lightblue" : "white"
                    property var section: ListView.section
                    visible: aVisible
    
                    onVisibleChanged: {
                        if(visible)
                            height = 55;
                        else
                            height = 0;
                    }
    
                    Behavior on height {
                        NumberAnimation { duration: 1000 }
                    }
    
                    Text {
                        id: text
                        text: name
                        anchors.centerIn: parent
                    }
    
                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            listing.currentIndex = index;
                        }
                    }
                }
            }
        }
    


  • Thanks @medyakovvit - I've spent the morning playing with this, but unfortunately every time you click on a Section Header, the currentIndex of the list changes. Since I am using the currentIndex to navigate between pages, that means the pages jump around every time you open or collapse a section.



  • @QMLNewbie
    Ok, can you describe the behavior of listview one more time?

    1. How many sections can be collapsed at the same time?
    2. Do you need to highlight current section?

    It's look like you need view that behaves like TreeView but with custom appearance?



  • @medyakovvit : Thanks so much for helping me out, really appreciated.

    Only one section can be open at a time. All the other sections should be collapsed. If you click on another section header, that one should open and the current open one should close. However, the currentIndex is independent of that. The currentIndex only changes when you actually click on a subitem. One you click on a subitem, currentIndex and therefore the content page changes.

    So you could have subitem2 in section 1 selected and the corresponding page is open. If you then click on section2, that section opens, but subitem2 from section1 and it's page stay selected until you click on a different item.

    As soon as you click on a section header, I need that section header to be highlighted (you can see in the original graphic here: http://pasteboard.co/xPyl3oy.jpg that an open section has the arrow pointing upwards instead of downwards).

    I'll have a look at TreeView to see if that one would work! =)



  • @QMLNewbie

    One more try:)

    Item{
            id: root
            width: 1406; height: 536
    
            ListModel {
                id: animalsModel
                ListElement { name: "Puss in Boots"; type: "Cats"; aVisible: false}
                ListElement { name: "Bengal"; type: "Cats"; aVisible: false }
                ListElement { name: "Pug"; type: "Dogs"; aVisible: false }
                ListElement { name: "German Shepherd"; type: "Dogs"; aVisible: false }
                ListElement { name: "Parrot"; type: "Birds"; aVisible: false }
            }
    
            Component {
                id: sectionHeader
    
                Rectangle {
                    id: sectionHeaderRect
                    width: 181
                    color:"green"
                    height: 50
    
                    property bool isExpanded: false
                    property string currentExpandedSection: ListView.view.expandedSection
    
                    onCurrentExpandedSectionChanged: {
                        if(currentExpandedSection === section)
                            isExpanded = true;
                        else
                            isExpanded = false;
                    }
    
                    onIsExpandedChanged: {
                        if(isExpanded){
                            color = "blue";
                            ListView.view.expandedSection = section;
                        }
                        else
                            color = "green";
                        for(var i=0; i<animalsModel.count; i++){
                            var animal = animalsModel.get(i);
                            if(section === animal.type)
                                animal.aVisible = sectionHeaderRect.isExpanded;
                        }
                    }
    
                    Text {
                        id: sectionHeaderText
                        text: section
                        anchors.centerIn: parent
                    }
    
                    MouseArea{
                        anchors.fill: parent
                        onClicked: {
                            sectionHeaderRect.isExpanded = !sectionHeaderRect.isExpanded;
                        }
                    }
                }
            }
    
    
            ListView {
                id: listing
                width: 181
                height: parent.height
                model: animalsModel
    
                property string expandedSection: ""
    
                delegate: listdelegate
    
                section.property: "type"
                section.criteria: ViewSection.FullString
                section.delegate: sectionHeader
    
            }
    
            Component {
                id: listdelegate
    
                Rectangle {
                    id: menuItem
                    width: 181
                    //height: 55
                    color: ListView.isCurrentItem ? "lightblue" : "white"
                    visible: aVisible
    
                    onVisibleChanged: {
                        if(visible)
                            height = 55;
                        else
                            height = 0;
                    }
    
                    Behavior on height {
                        NumberAnimation { duration: 1000 }
                    }
    
                    Text {
                        id: text
                        text: name
                        anchors.centerIn: parent
                    }
    
                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            listing.currentIndex = index;
                        }
                    }
                }
            }
        }
    


  • @medyakovvit : EEEEK that's it!! Thank you so much! Took me a while to understand what you have done, but it works perfectly. Super thrilled that I can now move on to other things and don't have to worry about this one anymore, phew.

    Thanks for helping a girl out! =)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.