How do you simulate a tree view in Qt Quick since it's not included in Controls 1.0?



  • As mentioned in another recent post, I'm new to Qt Quick, so please bear with me.

    Project background:
    A coworker sketched up a GUI design for a new cross-platform application intended to replace a legacy, Windows (MFC) application. He intends to have a couple of nested split views, a tree view, and a couple of swappable editor panes.

    Issue at hand:
    Qt Quick Controls 1.0 doesn't include a stock TreeView (though I believe I read it's planned for 5.2). I need to simulate said TreeView to fulfill the design requirements for this GUI.

    Implementation ideas:
    Perhaps I could utilize a ListView whose ListModel contains ListElement entries that may hold their own nested ListElement entries (and so on). This model could approximate a tree structure that a user might need to navigate in a GUI.

    Concerns:
    I have a severe lack of experience with Qt Quick and feel I am not familiar enough with the available components and now they relate to/interact with each other in order to implement this control quickly.

    Would someone be able to suggest a path forward on implementing such a control? I'm open to other ideas on how to handle the navigation, but I would also like to know how to implement something (seemingly) as simple as this control should be.

    Thanks!

    PS: I'm afraid I don't have any example code to post since I just don't think I'm doing anything correctly right now, but I hope my description is clear.



  • I have a similar requirement for a tree view structure which I'll start having a play with today. I've done collapsible sections in lists so I hope I can extend that to simulate a tree. It is based on https://gist.github.com/elpuri/3753756



  • [quote author="Babalas" date="1375223686"]I have a similar requirement for a tree view structure which I'll start having a play with today. I've done collapsible sections in lists so I hope I can extend that to simulate a tree. It is based on https://gist.github.com/elpuri/3753756[/quote]

    Have you had any success with your implementation? Since I'm inexperienced with Qt/QML/Quick, my main hangup was determining how to provide for child branches (expandable subnodes). How would one address this if the model were something like follows:

    @
    ListModel
    {
    ListElement
    {
    text: "Level 1, Node 1"
    }

    ListElement
    {
        text: "Level 1, Node 2"
        elements:
        [
            ListElement
            {
                text: "Level 2, Node 1"
                elements:
                [
                    ListElement
                    {
                        text: "Level 3, Node 1"
                    }
                ]
            },
            ListElement
            {
                text: "Level 2, Node 2"
            }
        ]
    }
    
    ListElement
    {
        text: "Level 1, Node 3"
    }
    

    }
    @



  • I ended up going with a hybrid between the collapsing sections and a StackView. Then put a breadcrumb trail at the top. So each section acts as the parent and the child nodes end up being a Repeater under that section. Each child has a MouseArea that when clicked pushes itself onto the StackView.
    It isn't a tree view, but in my case it works well enough. Happy to go into more detail if you want



  • I have seen this question pop up quite often so I thought I gave a shot at creating a simple tree view. It is certainly not the full solution as you might expect for 60 lines of QML, but for those of you in desperate need of "simulating" one it might already be sufficient for your use case and it will work fine with the model you suggested above.

    @
    import QtQuick 2.1

    Flickable {
    property var model
    anchors.fill: parent

    contentHeight: content.height
    contentWidth: content.width
    
    Loader {
        id: content
        sourceComponent: treeBranch
        property var elements: model
        property bool isRoot: true
    
        Component {
            id: treeBranch
    
            Item {
                id: root
    
                implicitHeight: column.implicitHeight
                implicitWidth: column.implicitWidth + 4
                Column {
                    id: column
                    x: 2
                    spacing: 2
                    Text { text: !!root.isRoot ? "" : " " }
                    Repeater {
                        model: elements
                        Row {
                            spacing: 2
                            Rectangle {
                                width: 18
                                height: 18
                                opacity: !!model.elements ? 1 : 0
                                Image {
                                    id: expander
                                    source: "expander.png"
                                    opacity: mouse.containsMouse ? 1 : 0.7
                                    anchors.centerIn: parent
                                    rotation: loader.expanded ? 90 : 0
                                    Behavior on rotation {NumberAnimation { duration: 120}}
                                }
                                MouseArea {
                                    id: mouse
                                    anchors.fill: parent
                                    hoverEnabled: true
                                    onClicked: loader.expanded = !loader.expanded
                                }
                            }
                            Text { text: model.text }
                            Loader {
                                id: loader
                                height: expanded ? implicitHeight : 0
                                property bool expanded: false
                                property var elements: model.elements
                                property var text: model.text
                                sourceComponent: (expanded && !!model.elements) ? treeBranch : undefined
                            }
                        }
                    }
                }
            }
        }
    }
    

    }
    @

    You will also need to copy this expander pixmap or make your own:
    !http://i.imgur.com/DcRU8xd.png(expander)!

    If you will use it with the Qt Quick Controls, you can simply replace the Flickable with a ScrollView and remove the content size definition from the root item.

    Using it it should be pretty straight forward:

    @

    import QtQuick 2.1

    Rectangle {

    width: 360
    height: 480
    
    ListModel {
        id: treemodel
        ListElement { text: "Level 1, Node 1" }
        ListElement {
            text: "Level 1, Node 2"
            elements: [
                ListElement { text: "Level 2, Node 1"
                    elements: [
                        ListElement { text: "Level 3, Node 1" }
                    ]
                },
                ListElement { text: "Level 2, Node 2" }
            ]
        }
        ListElement { text: "Level 1, Node 3" }
    }
    
    TreeView {
        anchors.fill: parent
        model: treemodel
    }
    

    }
    @

    Edit: added a screenshot
    !http://i.imgur.com/QDhNfco.png(screenshot)!



  • Similar to what I've ended up with.
    !http://i.imgur.com/AICbtqb.png!
    From top to bottom:

    Toolbar with button and breadcrumb (ListView + model)
    StackView
    Collapsing section for the child nodes (the scenes, who can also have children)
    Repeating collapsing sections for properties
    TableView



  • [quote author="Jens" date="1375874127"]I have seen this question pop up quite often so I thought I gave a shot at creating a simple tree view. It is certainly not the full solution as you might expect for 60 lines of QML, but for those of you in desperate need of "simulating" one it might already be sufficient for your use case and it will work fine with the model you suggested above.
    [/quote]

    Thank you very much, Jens!

    Its work.

    Can you show sample code how create this ListModel in C++ dinamically?

    @
    ListModel {
    id: treemodel
    ListElement { text: "Level 1, Node 1" }
    ListElement {
    text: "Level 1, Node 2"
    elements: [
    ListElement { text: "Level 2, Node 1"
    elements: [
    ListElement { text: "Level 3, Node 1" }
    ]
    },
    ListElement { text: "Level 2, Node 2" }
    ]
    }
    ListElement { text: "Level 1, Node 3" }
    }
    @

    Regards!



  • [quote author="Babalas" date="1375933330"]Similar to what I've ended up with.
    !http://i.imgur.com/AICbtqb.png!
    From top to bottom:

    Toolbar with button and breadcrumb (ListView + model)
    StackView
    Collapsing section for the child nodes (the scenes, who can also have children)
    Repeating collapsing sections for properties
    TableView
    [/quote]

    Is there any chance you could reveal some code for that? Are the collapsing sections also driven by a model?



  • Here is an updated example using qt quick controls:

    @
    import QtQuick 2.1
    import QtQuick.Controls 1.0

    ScrollView {
    id: view

    property var model
    property int rowHeight: 19
    property int columnIndent: 22
    property var currentNode
    property var currentItem
    
    property Component delegate: Label {
        id: label
        text: model.text ? model.text : 0
        color: currentNode === model ? "white" : "black"
    }
    
    frameVisible: true
    implicitWidth: 200
    implicitHeight: 160
    
    contentItem: Loader {
        id: content
    
        onLoaded: item.isRoot = true
        sourceComponent: treeBranch
        property var elements: model
    
        Column {
            anchors.fill: parent
            Repeater {
                model: 1 + Math.max(view.contentItem.height, view.height) / rowHeight
                Rectangle {
                    objectName: "Faen"
                    color: index % 2 ? "#eee" : "white"
                    width: view.width ; height: rowHeight
                }
            }
        }
        Component {
            id: treeBranch
            Item {
                id: root
                property bool isRoot: false
                implicitHeight: column.implicitHeight
                implicitWidth: column.implicitWidth
                Column {
                    id: column
                    x: 2
                    Item { height: isRoot ? 0 : rowHeight; width: 1}
                    Repeater {
                        model: elements
                        Item {
                            id: filler
                            width: Math.max(loader.width + columnIndent, row.width)
                            height: Math.max(row.height, loader.height)
                            property var _model: model
                            Rectangle {
                                id: rowfill
                                x: view.mapToItem(rowfill, 0, 0).x
                                width: view.width
                                height: rowHeight
                                visible: currentNode === model
                                color: "#37f"
                            }
                            MouseArea {
                                anchors.fill: rowfill
                                onPressed: {
                                    currentNode = model
                                    currentItem = loader
                                    forceActiveFocus()
                                }
                            }
                            Row {
                                id: row
                                Item {
                                    width: rowHeight
                                    height: rowHeight
                                    opacity: !!model.elements ? 1 : 0
                                    Image {
                                        id: expander
                                        source: "expander.png"
                                        opacity: mouse.containsMouse ? 1 : 0.7
                                        anchors.centerIn: parent
                                        rotation: loader.expanded ? 90 : 0
                                        Behavior on rotation {NumberAnimation { duration: 120}}
                                    }
                                    MouseArea {
                                        id: mouse
                                        anchors.fill: parent
                                        hoverEnabled: true
                                        onClicked: loader.expanded = !loader.expanded
                                    }
                                }
                                Loader {
                                    property var model: _model
                                    sourceComponent: delegate
                                    anchors.verticalCenter: parent.verticalCenter
                                }
                            }
                            Loader {
                                id: loader
                                x: columnIndent
                                height: expanded ? implicitHeight : 0
                                property var node: model
                                property bool expanded: false
                                property var elements: model.elements
                                property var text: model.text
                                sourceComponent: (expanded && !!model.elements) ? treeBranch : undefined
                            }
                        }
                    }
                }
            }
        }
    }
    

    }
    @

    !http://i.imgur.com/aUihaC5.png(treeview screenshot)!



  • It's wonderful! Thank you, Jens!



  • Unfortunately the example above crashes when I remove any element from the model when the folder in the view is expanded. Tested with Qt 5.2.0 and 5.2.1 on Linux. Test to reproduce:

    @import QtQuick 2.1
    import QtQuick.Controls 1.1
    import QtQuick.Layouts 1.1

    ApplicationWindow {
    id: window
    width: 400
    height: 480
    title: qsTr("Tree")

    Timer {
        interval: 3000
        running: true
        repeat: false
        onTriggered: {
            console.log("Removing  element");
            var obj = tree.model.get(0);
            console.log(obj.elements);
            obj.elements.remove(0);
        }
    }
    
    Component.onCompleted: {
        var obj = tree.model.get(0);
        obj.elements.append({ text: "x1" });
        obj.elements.append({ text: "x2" });
        obj.elements.append({ text: "x3" });
    }
    
    ListModel {
        id: modelTree
    
        ListElement { text: "All"; elements: []; }
    }
    
    TreeView {
        anchors.fill: parent
        id: tree
        model: modelTree
    }
    

    }@

    Compile and run. Open the root element "All". In 3 seconds the application will activate the timer and will try to remove the first subitem "x1". Result: crash.

    @QML debugging is enabled. Only use this in a safe environment.
    Removing element
    QQmlListModel(0x820b588)
    Segmentaion fault@



  • For others finding this thread, there's also:
    http://www.codeproject.com/Articles/664041/QML-QtQuick-TreeView-Model-Part-II

    Another way to do it is to map your C++ tree to a flat list and then make it look like a tree without it being a tree. That way you get all the benefits of the ListView only creating objects as you need them. Though obviously the encapsulation and events are somewhat less useful.

    A fully expanded tree using the above solutions and a large model could get quite slow.



  • [quote author="loran" date="1393348350"]Unfortunately the example above crashes when I remove any element from the model when the folder in the view is expanded. Tested with Qt 5.2.0 and 5.2.1 on Linux. ...
    [/quote]

    With the current snapshot of Qt5.3-RC the crash does not happen any more. I get a message on the console: "TreeView.qml:15: TypeError: Cannot read property 'text' of null"



  • It would be really nice if the model could be a C++ QAbstractItemModel that is set with setContextProperty.



  • I am trying to make this (really nice) view work with a QAbstractItemModel but I'm pretty much stuck -.-

    My first approach was to return the childItems of a node as model. I wrote a constructor in my model (of type QAbstractListModel) that took a QList<Node*> and returned a new model whenever the view wanted the subnodes of a node. This worked really well for read-only access, but is a mess regarding editing the tree, since I guess the models do not communicate to each other and a model has no idea what happens in another model. So if you delete a node in one model and another model wants to access the children of this model the whole thing crashes, because it does not know there is one node missing (that is my guess at least)

    So it seems the only solution for a consistent model is to have only one model that keeps track of every event that happens in the tree. So my second approach was the following: I returned the childnodes of a node as a QList<Node*> and made the data of a node accessible through the Q_PROPERTY macro, e.g. Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged). But now the view does not work anymore because the data of a QObject based model can only be accessed with model.modelData.role and not with model.role like in a QAbstractItemModel. The view would have to deal with two kinds of models and I do not know how to realize this.

    Can maybe anybody give me a hint on how I could make this work, it would be very much appreciated. Thank you.

    edit: I just realized the second approach would not work anyhow since the view would not be updated in a QObject based model.



  • If you want to make your own Qt Quick TreeView in C++, there's a presentation here:
    https://www.qtdeveloperdays.com/sites/default/files/north-america/QtQuickTreeView.pdf



  • If you want to make your own Qt Quick TreeView in C++, there's a presentation here:
    https://www.qtdeveloperdays.com/sites/default/files/north-america/QtQuickTreeView.pdf



  • [quote author="Jens" date="1381914126"]Here is an updated example using qt quick controls:

    ...[/quote]

    A very nice example. It seems like it's not that easy to implement key-events(up, down, ...) for controling this view. I would appreciate it if anyone could come up with an idea.



  • [quote author="Jens" date="1381914126"]Here is an updated example using qt quick controls:

    ...[/quote]

    A very nice example. It seems like it's not that easy to implement key-events(up, down, ...) for controling this view. I would appreciate it if anyone could come up with an idea.


  • Moderators

    If you can wait a bit longer, there will be an official TreeView QML type in the upcoming "Qt 5.5 release":http://blog.qt.io/blog/2015/02/05/licensing-of-new-modules-in-qt-5-5/


  • Moderators

    If you can wait a bit longer, there will be an official TreeView QML type in the upcoming "Qt 5.5 release":http://blog.qt.io/blog/2015/02/05/licensing-of-new-modules-in-qt-5-5/



  • Oh thats sweet, i'll wait until the official release. Thanks for your information.



  • Oh thats sweet, i'll wait until the official release. Thanks for your information.


Log in to reply
 

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