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.
-
@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
orspace
). 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?- How many sections can be collapsed at the same time?
- 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! =)
-
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! =)