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.
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:
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:
{
"
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
:
ListModel {
id: objModel
}
To add nodes "
Zero
", "
One
" and "
Two
" you will simply do:
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:
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
":
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:
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
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:
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:
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):
-
click on the button -> click on the modal window and just press carriage-return
-
click on the button -> just press carriage-return (this time the modal window will automatically have the focus)
-
repeat <2> 3 more times
-
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
-
repeat <2> 3 more times
-
click on the button -> go to 2, in the text -> enter 1, (the text should now look like: 2,1,node0) -> press carriage-return
-
repeat <2> 3 more times
-
Double click on the outermost blue node
-
Repeat <8>
-
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
Re: An extension: Drawing a rectangle around it and all children...
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
Re: An extension: Drawing a rectangle around it and all children...
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
. 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