QML Containers

From Qt Wiki
Jump to navigation Jump to search
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Needs to be updated to QtQuick 2
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

Introduction

In programming we are used to applying some language constructs for data collections like arrays for example. In QML these constructs are lists. They are implemented as ListModel elements and list and variant types. In the text bellow we call these constructs containers. A container holds linearly ordered elements, which are accessed by an index. The indexing uses special methods as in case of ListModel element or well-known square bracket notation in case of list and variant types. This article explores application of QML containers and how they are controlled dynamically.

ListModel Element

It implements a classical list – an ordered collection of data items. Each item is accessible through ListModel element name and an index in the ordering. The index starts from 0. The data items are other QML elements – ListElement. They define properties and assign values to them in usual manner, but with stronger restrictions. In QML documentation the ListElements properties are called roles.

Here is an example that illustrates these definitions:

//This snippet illustrates how ListModel is used as a container for other QML elements
import QtQuick 1.1

Rectangle {id:top; width:500;height:500
 property alias getText:arrangeText //Makes arrangeText elements visible in top
 Rectangle {
 id:dummy
 Component.onCompleted:{
 top.color="lightsteelblue"
 //Demonstrates how ListElement properties are accessed
 top.getText.firstText.text=ourContainer.get(0).test1
 top.getText.secondText.text=ourContainer.get(1).test1
 top.getText.thirdText.text=ourContainer.get(2).test1

top.getText.forthText.text=ourContainer.get(2).moreParameters.get(0).year
 }
 }// end dummy

ListModel {
 id: ourContainer
 ListElement {
 test1:"FirstText as title"
 authorA:"Smith"
 moreParameters:[ListElement{year:"1947"}]
 }
 ListElement {
 test1:"SecondText as abstract"
 authorA:"Kavendish"
 moreParameters:[ListElement{year:"2001"}]
 }
 ListElement {
 test1:"ThirdText as table of content"
 authorA:"Vandey"
 moreParameters:[ListElement{year:"Start date: 1999"}]
 }
 }

Column{id:arrangeText
 property alias firstText:first
 property alias secondText:second
 property alias thirdText:third
 property alias forthText:parameters
 spacing:30
 Text{id:first}
 Text{id:second}
 Text{id:third}
 Text{id:parameters}
 }
}//end rectangle top

What is important here is that we could have several ListElement elements, but each defines the same properties. You could see the similarity with arrays in other programming languages where the array's elements must be of the same type.

With the ListModel element are associated typical operations for lists: insert(), append(), remove(), get() , etc. Consult documentation for their usage.

What could we do with the ListModel container? Firstly, the container should be constructed by the definition and operations eventually. Next, the data items in the container could be accessed and used in other QML elements. Finally, all data items of a ListModel element could be rendered as a collection through views such as the ListView for example. The views are special QML elements servicing as viewers – see here.

The demo snippet above demonstrates how ListModel container is used to hold some data. Note that we not use a ListVeiw for rendering the data. Usually ListModel is paired with ListView element. Our goal is to show how a ListModel serves as a data container supplying data to other QML elements.

The snippet is constructed as follows. Firstly, we define a ListModel element ourContainer and within it three ListElement. The QML snippet file introduces four Text elements – first, second, third, parameters. The functionality of the snippet is displaying the text properties values of the Text elements, which in turn are properties values of the ListElements. For convenience the displayed texts are arranged in columns. The role of custom defined properties in the snippet is to grant access to different QML elements. You remember the access rules for QML elements tree: from bottom to top entities are visible, but the contrary is not true. The rectangle dummy is used to form a script block where we get the data from the container and assign them to respective properties of Text elements.

Nested ListElements

In our example we define a property – moreparameters – which receives values that are other ListElements.

ListElement {
 test1:"ThirdText as table of content"
 authorA:"Vandey"
 moreParameters:[ListElement{year:"Start date: 1999"}]
 }

In other words, we could have nested ListElements. To have access to nested values in the example we use get() method two times:

top.getText.forthText.text=ourContainer.get(2).moreParameters.get(0).year

A property of a ListElement could be of list type, but be aware that elements of that list must be other ListElements.

Long Texts

If you have a long text as a ListElement property value you may prefer to hold it in a separate place not in the corresponding ListModel definition. One solution is to use JavaScript arrays – see here. Another solution is to define an empty ListModel, which is used as a template and the respective property value (long text) is defined dynamically in a script block.

There are two approaches. The first uses the setProperty() method of the ListModel element. Here is a code fragment as example:

Rectangle {id:top; width:500;height:500
 Rectangle {
 id:dummy
 Component.onCompleted:{
 ourContainer.setProperty(0,"test1",
 "New property value\n Second line\n Third line")
 }
 }// end dummy
 ListModel {
 id: ourContainer
 ListElement {
 test1:""
 }
 }
 Text{id:first
 text:ourContainer.get(0).test1
 }
}//end rectangle top

The property value of the List Element, which holds the long text, initially is an empty string. Later this value is changed dynamically in a separate script block. What is the role of the dummy rectangle above – just to construct a separate place where to put the long text.

The second approach defines an empty ListModel, which has no ListElements at all. Dynamically we use the set() method of the ListModel to add a long text. Have a look at a code sample:

Rectangle {id:top; width:500;height:500
 function longTextFragment() {
 var back= "First line - defined in the function body\n"+
 "Second line - defined in the function body"
 return back
 }
 ListModel {
 id: ourContainer
 }
 Rectangle {
 id:dummy
 Component.onCompleted:{
 ourContainer.set(0,{"test1":longTextFragment()})
 console.log("Test the new entry in ourContainer\n",ourContainer.get(0).test1)
 first.text=ourContainer.get(0).test1
 }
 }// end dummy
 Text{id:first
 }
}//end rectangle top

The property values of a ListElement could be JavaScript expressions and particularly a return value from a JavaScript function call. In the snippet above the function is named longTextFragment() and the long text is placed and formatted in the function body.

The full demo code for the above use cases you could find here.

list and variant Data Types as Containers

QML offers two data types – list and variant – which could be used as arrays. They are very similar, but too different semantically at the same time. The documentation on list and variant data types is here. Consult the properties usage document also.

The list data type collects QML elements, which are of the type as the type in the list type definition or inherited from that type. The list data are defined as a custom property like that:

property list<Rectangle> exampleDefinition: [Rectangle{width:50;height:50},
 Rectangle{},Rectangle{id:last;}
 ]

The initializing part of the definition (right side) is optional. We could define just a list data name and at a later moment assign elements to the list.

property list<Item> sample

 Rectangle{id:rect1;width:40;height:40}
 Rectangle {id:rect2;width:100;height:100}
 Rectangle {id:dummy
 Component.onCompleted:{sample=[rect1,rect2];
 console.log("rect1 width = ",sample[0].width)
 }
 }

All elements in a list data definition have to be of the same type. Basic QML data types as int, string, etc. could not be elements of a list data.

The variant data type uses the same square brackets to form a list of entities. They could be basic QML types (but not QML elements types) or JavaScript objects. It is important to note that variant data type list entities could be of different types.


function fun(){
 return "Return value from fun()"
}

property string text1:"Some text"
property string text2:"Another text"

property variant anotherArrayType:["This is a string",text1+text2, 30*2,fun()]


Rectangle {id:dummy1
 Component.onCompleted:{
 console.log(anotherArrayType[1],anotherArrayType[2],anotherArrayType[3])
}

The both sequences – list and variant – are immutable. That means that once defined elements in a list or variant data could not be changed. For the variant data there is a tricky approach to go around this limitation.

The complete code of examples in this section could be downloaded here.

QML Containers and Texts

Let us consider a typical use case for program systems. We have a set of text messages and need to store them in a container. In different situations we get a message from the container and display it.

The simplest solution is to define a variant list and hold the messages in it as strings:

property variant someMessages:["This is the first message[[Image:",
   "This is the second message|]]",
 "This is the third message!"]

The complete snippet is here.

If we want to be able to format text messages we could use a l_ist_ data type to hold QML Text elements:

property list<Item> someMessages:[Text{text:"First message";color:"white"},
 Text{text:"Second Message";color:"red";font{bold:true}},
 Text{text:"Third message";font{italic:true;pixelSize:16}}
 ]

The complete code for this use case is here.

As the both variant and list containers just hold data we need a solution for rendering them. The process has two steps: firstly we get the data and then render them. The proposed solution is to use a QML loader and it is illustrated in the diagram bellow.

Diagram

The loader – id:light – loads a template VirtualText.qml. It is a QML component file. The template is an empty structure and contains a QML Text element that corresponds to QML Text elements of the variant (list) containers. The goal of the template is to render Text elements stored in containers. Firstly the loader light loads and renders the empty template:

light.source="VirtualText.qml";

After loading, the text property of the loaded item is changed to text property of corresponding element in the variant (list) container:

light.item.accessText=someMessages[counter]

The property counter serves as container index and a loop is organized to accept several clicks. The property accessText defined in VirtualText.qml grants access to text property of virtual Text element.

The demo snippets play as follows – see screenshots bellow. We have two areas on the screen. One where the texts are displayed and another which accepts mouse clicks. The first mouse click displays the first container text element and so on. After the list is exhausted the program quits.

Screenshot - text1

Screenshot - text3

QML Containers and Images

This use case is very similar to the previous one about text messages. Now the container should hold images and its elements are QML Image elements:

property list<Item> someImages:[Image{id:first;source:"balls.jpg"},
 Image{id:second;source:"funny_balls.jpg"},
 Image{id:third;source:"four_balls.jpg"}]

The same techniques as in the previous section are used to define a template (VirtualImage.qml) and a loader, which loads images sequentially. Bellow you see some screenshots.

Screenshot - image1

Screenshot - image2

The functionality that is implemented is similar to "Loader and Inline Component" use case analyzed here.

The full code is available here.

QML Containers and JavaScript Objects

The QML variant type container could have elements, which are JavaScript objects defined in an external JavaScript file. Suppose we have the following JavaScript file:

jsfile.js

var externalObject="External JS Object"
var array=[1,2,3]
function func() {
 console.log("The function 'func()'is invoked")
 return "Returned value from function 'func()'"
}
function funSecond(){
 return "Returned value from function 'funSecond()'"
}

Then we may have the following variant type container definition in the main QML file:

property variant jsVariable:[Extended.externalObject,Extended.func(),
 Extended.array,Extended.funSecond()]

To test the above constructs we define a rectangle in the main file that forms a script block:

Rectangle{id:dummy
 Component.onCompleted:{
 console.log("Value of 'externalObject' variable: ",jsVariable[0])
 console.log("Function 'func()' is called: ",jsVariable[1])
 var pix=jsVariable[1]
 var funSecondValue;
 console.log("Value of 'pix' variable that is return value of 'func()': ",pix)
 //Here we have an array of arrays
 console.log("Value of the second element of 'array' object: ",jsVariable[2][1])
 //We have an array of functions and could decide which one to call
 if(test==true){
 funSecondValue=jsVariable[3];
 console.log(funSecondValue);
 }
 }
 }

The discussed constructs are similar to ones in other programming languages and known as "array of functions", 'array of arrays".

The complete example could be downloaded here.

QML Containers and Components

We are considering the situation where QML component files are used. For simplicity suppose we have two components defined as files ComponentTest1.qml and ComponentTest2.qml.

The question now is: could we arrange these components in a list or variant type container? The answer is positive one and we have two solutions.

Firstly, let us consider the following definition:

 property list<Item> componentList:[ComponentTest1{},ComponentTest2{}]

We see that elements of the componentList container are instances of the components – curly brackets are applied. The components are accessible through indexing and for example

componentList[0].width

gives the value of the width property of the ComponentTest1.

Secondly, we may use variant type to store the names of components like this:

property variant componentUrl:["ComponentTest1.qml", "ComponentTest2.qml"]

Further, a loader could be used to load elements of the componentUrl container:

Loader{id:light; source: componentUrl[0]}

The complete snippet is here.

Note: All code snippets analyzed above are included in a downloadable package, which contains:

Subdirectory - - Files

listContainer - - Listcontainer.qml

listContainerSet - - listContainerProperty.qml, listContainerSet.qml

containerText - - containerText.qml, VirtualText.qml

containerFormattedText - - containerFormattedText.qml, VirtualText.qml

containerImage - - containerImage.qml, VirtualImage.qml, balls.jpg, four_balls.jpg, funny_balls.jpg

containerJavaScript - - containerJavaScript.qml, jsfile.js

containerComponent - - containerComponent.qml, ComponentTest1.qml, ComponentTest2.qml