相关文章推荐
千杯不醉的仙人球  ·  # yyds干货盘点 # ...·  11 月前    · 
烦恼的鸵鸟  ·  【PDD4 ...·  1 年前    · 
一身肌肉的毛豆  ·  jframe关闭事件-掘金·  1 年前    · 

Introduction

There seems to be a pressing need for some quick implementation of TreeView and model in QML. This article will teach you one of the ways in which you could do it. Note that to just implement the model and view, I've used no Desktop Components. All can be done with the old qml offerings. Additional imports you see in the final program is merely for user interaction - they are in no way strictly required. The final program should work without corrections.

Background

The audience is expected to be a little familiar with QML.

For starters, Let's consider two ways of data addition to ListModel :

Eg 1:

<1> Static way:

JavaScript
ListModel { ListElement { role0: " Something" role1: 0 ListElement { role0: " SomethingElse" role1: 1 }

<2> Same thing done dynamically:

JavaScript
ListModel { Component.onCompleted: { append({ " role0" : qsTr( " Something" ), " role1" : 0 }) append({ " role0" : qsTr( " SomethingElse" ), " role1" : 1 }) }

Eg 2: A little more complicated model

<1> Static way:

JavaScript
ListModel { ListElement { role0: " ABC" contents: [ ListElement { someRole0: " aqs" someRole1: 123 ListElement { someRole0: " qwer" someRole1: 12378 ListElement { role0: " ABC" contents: [ ListElement { someRole0: " aqs" someRole1: 123 ListElement { someRole0: " qwer" someRole1: 12378 }

<2> Same thing done dynamically:

ListModel {    Component.onCompleted: { for ( var i = 0 ; i < 2 ; ++i) {          append({ " role0" : qsTr( " ABC" ), " contents" : [                       { " someRole0" : qsTr( " aqs" ), " someRole1" : 123 },                       { " someRole0" : qsTr( " qwer" ), " someRole1" : 12378 } }

Hope you get the idea. To learn more, read about JavaScript objects.

Using the code

Now back to the topic:

There are two key concepts used here for the kind of simple tree view implementation which we'll see using ListModel and ListView .

First, we use a recursive component - a component that has an ability to add to itself at run-time, well, itself. Such a component could look like:

JavaScript
Component { id: objRecursiveComponent Column { Item { id: objData1 Repeater { model: someModel delegate: objRecursiveComponent }

Here objData<n=1,2....> can be viewed as data members for a node and Repeater is there to add further sub-nodes - that is what a tree is like.

If someModel is numeric and > 0 or object list with length > 0 then this would lead to infinite recursion. We must provide a way such that someModel is controllable for every new instantiation of object from objRecursiveComponent . One simple way to imagine is exposing an object list of objects with each object having a normal role-field which objData1 would use and another role-field which is an object-list of similar objects as itself. This 2nd member can then be used as a source model ( someModel above) to Repeater's model member. If the 2nd member is an empty object-list, the recursion stops and no further subnodes get attached/created. Any update (dynamic) of such object-lists will directly affect the tree due to QML bindings, either adding nodes to the tree or removing from it.

So let's build one such tree that allows dynamic addition of nodes anywhere legal. Other operations should be all trivial once this much is clear. Further every node of the tree shall contain two data members - one the name field (for the name of the node) and the other a chain (like the Repeater above) for attaching the sub-nodes (child nodes).

The second concept is the JavaScript object for the model. If you get the examples in the Background section to this article, this should be easy to follow.

JavaScript object for the ListModel should look like:

JavaScript
{ " name" : qsTr( " Node-Name" ), " level" : 0 , " subNode" : []}

level field is just for some added information that will help us indent the child nodes cheaply - of-course I'm not providing any ideal solution anytime, just a little help for getting started.

Here's a ListModel :

JavaScript
ListModel { id: objModel }

To add nodes " Zero ", " One " and " Two " you will simply do:

JavaScript
objModel.append({ " name" : qsTr( " Zero" ), " level" : 0 , " subNode" : []}) objModel.append({ " name" : qsTr( " One" ), " level" : 0 , " subNode" : []}) objModel.append({ " name" : qsTr( " Two" ), " level" : 0 , " subNode" : []})

To add nodes " Three " and " Four " to node " One " you'll write:

JavaScript
objModel.get( 1 ).subNode.append({ " name" : qsTr( " Three" ), " level" : 1 , " subNode" : []}) objModel.get( 1 ).subNode.append({ " name" : qsTr( " Four" ), " level" : 1 , " subNode" : []})

To add " Five " to " Three ":

JavaScript
objModel.get( 1 ).subNode.get( 0 ).subNode.append({ " name" : qsTr( " Five" ), " level" : 2 , " subNode" : []})

Hope all that was clear.

Now let's create a delegate for the ListView . Our simple delegate will have the following basic properties:

  • node color should be yellow if it contains no further child nodes, blue otherwise
  • child nodes should have indentation (otherwise it'll look visually confusing) - for this we will use the level field of the objects in the ListModel
  • nodes with child nodes should be collapsible and expandable

Here's an attempt at such a component:

JavaScript
Component { id: objRecursiveDelegate Column { id: objRecursiveColumn clip: true MouseArea { width: objRow.implicitWidth height: objRow.implicitHeight // for collapsing and expanding onDoubleClicked: { // remember that Repeater is also a child of objRecursiveColumn. Altering // it's visiblity serves no purpose and is wasteful so thus we avoid looping // over it by parent.children.length - 1. i starts from 1 because we don't // want to affect the visiblity of 0th child which is this MouseArea itself. // Also note that making a child invisible will also make all // children/grandchildren of the child invisible too - nice. for ( var i = 1 ; i < parent.children.length - 1 ; ++i) { parent.children[i].visible = !parent.children[i].visible Row { id: objRow // for indentation Item { height: 1 width: model.level * 20 Text { // if collapsed, show + else show -. If no child-nodes then show nothing // (cannot be collapsed or expanded) text: (objRecursiveColumn.children.length > 2 ? objRecursiveColumn.children[ 1 ].visible ? qsTr( " - " ) : qsTr( " + " ) : qsTr( " " )) + model.name color: objRecursiveColumn.children.length > 2 ? " blue" : " yellow" font { bold: true ; pixelSize: 14 } Repeater { model: subNode delegate: objRecursiveDelegate

We have already discussed the trickier parts of it. Rest of it I hope is simple enough to follow with the aid of a few inline comments in the code snippet above. Note that each subNode is itself a ListElement , so when the model of the Repeater is subNode (as in above) the delegate will have access to fields/roles model.name, model.level and model.subNode , all three of which we use in the delegate above.

Thus far we have seen the codes for ListModel , the recursive delegate which we will present to ListView and some codes to add data to our ListModel . That all that's required. To appreciate the concept a little more we'll add user interaction. The user can add nodes to any legal place in the tree. When the user just states a string (e.g., " XYZ ") it'll be added to level 0 as node 0. If user again just states a string (e.g., " ABC ") it will be added to level 0 as node 1. Now that we have 2 nodes at level 0, the user can add child node to any of them. Say it is desired that node 1 (" ABC ") should contain a sub-node (" QWE "). The user will let this intention be known via input: 1,QWE . By this he means that node 1 (which " ABC ") should contain child-node " QWE ". If a subsequent input is 1,RTY then " ABC " will have 2 child-nodes " QWE " and " RTY ". To add to " UIO " to " QWE " he'll mention: 1,0,UIO as input. This is because 1 is " ABC ". Then inside " ABC " 0 is " QWE ". Similarly to add " 123 " to " UIO " the user will mention: 1,0,0,123 . Hope you have got the hang of it.

For this we will add a Button which when clicked will display a Modal TextInput. Here the user can supply input and press Carriage-return. We'll put a regex validator on the textinput though it's optional (I love regex because I use Vim so much - i put it in places not even required just for the heck of it :) ). The actual important stuff we'll do is check if the input is legal. If it is then add the data to the model else display an error on the console. Eg., of an illegal input would be: 1,3,ASDF for the above example as node 1 (" ABC ") has no node 3. It has only 2 nodes " QWE " and " RTY " ie., node 0 and node 1.

Here's the code:

JavaScript
// Assume text is the input user supplies to TextInput. var szSplit = text.split( ' ,' ) if (szSplit.length === 1 ) { objModel.append({ " name" : szSplit[ 0 ], " level" : 0 , " subNode" : []}) else { if (objModel.get( parseInt (szSplit[ 0 ])) === undefined ) { console .log( " Error - Given node does not exist !" ) return var node = objModel.get( parseInt (szSplit[ 0 ])) for ( var i = 1 ; i < szSplit.length - 1 ; ++i) { if (node.subNode.get( parseInt (szSplit[i])) === undefined ) { console .log( " Error - Given node does not exist !" ) return node = node.subNode.get( parseInt (szSplit[i])) node.subNode.append({ " name" : szSplit[i], " level" : i, " subNode" : []})

There is nothing out of the world there. Just splitting on basis of " , " and considering the last string to be the node name data for the text of the delegate objRecursiveDelegate .

Here is a complete listing of the code:

JavaScript
import QtQuick 2 . 0 import QtQuick.Window 2 . 0 import QtQuick.Layouts 1 . 0 import QtQuick.Controls 1 . 0 Rectangle { width: 600 height: 600 color: " black" ListModel { id: objModel Component { id: objRecursiveDelegate Column { id: objRecursiveColumn clip: true MouseArea { width: objRow.implicitWidth height: objRow.implicitHeight onDoubleClicked: { for ( var i = 1 ; i < parent.children.length - 1 ; ++i) { parent.children[i].visible = !parent.children[i].visible Row { id: objRow Item { height: 1 width: model.level * 20 Text { text: (objRecursiveColumn.children.length > 2 ? objRecursiveColumn.children[ 1 ].visible ? qsTr( " - " ) : qsTr( " + " ) : qsTr( " " )) + model.name color: objRecursiveColumn.children.length > 2 ? " blue" : " yellow" font { bold: true ; pixelSize: 14 } Repeater { model: subNode delegate: objRecursiveDelegate ColumnLayout { anchors.fill: parent ListView { Layout.fillHeight: true Layout.fillWidth: true model: objModel delegate: objRecursiveDelegate Window { id: objModalInput modality: Qt.ApplicationModal visible: false height: 30 width: 200 color: " yellow" TextInput { anchors.fill: parent font { bold: true ; pixelSize: 20 } verticalAlignment: TextInput.AlignVCenter horizontalAlignment: TextInput.AlignHCenter validator: RegExpValidator { regExp: /(\d{ 1 ,},)*.{ 1 ,}/ onFocusChanged: { if (focus) { selectAll() text: qsTr( " node0" ) onAccepted: { if (acceptableInput) { objModalInput.close() var szSplit = text.split( ' ,' ) if (szSplit.length === 1 ) { objModel.append({ " name" : szSplit[ 0 ], " level" : 0 , " subNode" : []}) else { if (objModel.get( parseInt (szSplit[ 0 ])) === undefined ) { console .log( " Error - Given node does not exist !" ) return var node = objModel.get( parseInt (szSplit[ 0 ])) for ( var i = 1 ; i < szSplit.length - 1 ; ++i) { if (node.subNode.get( parseInt (szSplit[i])) === undefined ) { console .log( " Error - Given node does not exist !" ) return node = node.subNode.get( parseInt (szSplit[i])) node.subNode.append({ " name" : szSplit[i], " level" : i, " subNode" : []}) Button { text: " add data to tree" onClicked: { objModalInput.show()

Here are possible operations to get you started (to be done in order):

  1. click on the button -> click on the modal window and just press carriage-return
  2. click on the button -> just press carriage-return (this time the modal window will automatically have the focus)
  3. repeat <2> 3 more times
  4. click on the button -> go to the beginning of the text in the modal window (press "Home") -> enter 2, (leave the rest of the text as it is) -> press carriage-return
  5. repeat <2> 3 more times
  6. click on the button -> go to 2, in the text -> enter 1, (the text should now look like: 2,1,node0) -> press carriage-return
  7. repeat <2> 3 more times
  8. Double click on the outermost blue node
  9. Repeat <8>
  10. Do this for other blue nodes

By now you'll have the hang of it.

Now that you know how simple it is to create a TreeView in QML/JavaScript, you can go ahead and tweak this or use this or write something completely from scratch and build complex TreeModel with all signal-slots/drag-drop/drag-drop-insert/shuffle/sort etc., features. I have done it (after brushing up on good algorithms on which I tend to become rusty as everything is ready-made these days) and it works wonderfully.

nice work!!! In one of the last sentences you mentioned that you did some tweaking. Did you also implement some kind of " Drag & Drop " Algorithm - especially for dragging from one tree to another ?
Or have you heard of something like that? I definitely need such thing...
Sign In · View Thread Hey, I'm still wrapping my head around QML, and I am hitting a wall trying to modify your code to make it draw a rectangle around the tree item and all of it's children (essentially a container which groups an item and all it's children - recursively of course). Can you point me in the right direction of how I could modify your example to achieve such a thing?
Sign In · View Thread Answer Re: An extension: Drawing a rectangle around it and all children... Pin
gemmell 5-Oct-13 17:30
gemmell 5-Oct-13 17:30 Figured it out. Just wrap the column in:
Rectangle {
height: objRecursiveColumn.implicitHeight
color: "orange"
width: 100
Sign In · View Thread General Re: An extension: Drawing a rectangle around it and all children... Pin
Spandan_Sharma 5-Oct-13 19:13
Spandan_Sharma 5-Oct-13 19:13 Had posted somewhat advanced manipulations of tree-view which also box the nodes all of which you had asked for. The article is still pending to be published by the mods/admins. Keep checking this link (The title is QML QtQuick TreeView/Model - Part-II ).
And yes, if the articles (this and that) are helpful to you, please give them a rating.

modified 6-Oct-13 1:22am.

Sign In · View Thread Looks good (but haven't actually tried it), any chance you'd share your code which does the fancy drag & drop, sort, shuffle etc? It mystifies me that there is no Tree View in QML. It's a main stay for pretty much every UI toolkit, and in Qt's QML it remains obstinately ignored.
Also, did you know the article is posted in "Web Development » ASP.NET Controls » General" when clearly it is not.
Sign In · View Thread Sure, I'll do that when I get some time.
Also, did you know the article is posted in "Web Development » ASP.NET Controls » General" when clearly it is not. --> I know Smile | :) . When i write articles I do put it in the section that i think justifies them closest, but upon submission the moderators/reviewers (i don't know who) put it somewhere else (in this case, here), but i see they're searchable so I don't mull over the placement much; maybe someone could tell me how get them (re-) placed correctly.
Sign In · View Thread Web01 2.8:2023-03-27:1