QML Dynamic Objects

From Qt Wiki
Revision as of 08:47, 25 February 2015 by Maintenance script (talk | contribs)
Jump to navigation Jump to search

English [toc align_right="yes" depth="2"]

QML Dynamic Objects

Introduction

QML has a rich set of programmatic techniques for controlling visualization, for example making objects appear and disappear from the scene. These techniques can be classified in three groups.

  1. The techniques from the first group control a QML object property to hide and/or change how and object is rendered. For example, the opacity property is often used by developers to visually reflect the state of an item. We can use properties visible, z, and color:transparent in similar ways. Using these techniques objects are neither created nor destroyed, therefore there is little impact on memory usage.
  2. The second group applies transformations to objects: Move an object off the screen (assign negative values to x,y coordinates) or change the object's shape. For instance, we may control object visualization through width and height properties. Again, minimal memory impact.
  3. The third group includes mechanisms based on dynamic loading of QML objects. These are QML constructs Loader and dynamic creation of objects. These techniques will impact memory usage.

There is no "right" and "wrong" here. Hidden objects continue to consume memory, but on the other hand they're easily accessible through their IDs.

This article discusses "dynamic creation of objects":http://doc.qt.nokia.com/4.7/qdeclarativedynamicobjects.html. More information about Loader utilization is "here":http://developer.qt.nokia.com/wiki/QML-Application-Structuring-Approaches.

Before we dig into the details, have a look at some examples of how these groups behave in the real world:

First and second group Overlapping

Third group Loader

The next snippet illustrates how width and height properties could be used to control visibility:

dynamicWidth.qml

import QtQuick 1.1
Item {
 Rectangle {id:top
 // width: 360
 // height: 360
 color:"red"
 }
 Rectangle {id:control
 width:30;height:30;color:"blue"
 MouseArea {
 anchors.fill: parent
 onClicked: {
 top.width=100; top.height=100;
 }
 }
 } //end Rectangle control
}// end Item

You guessed already that top rectangle is constructed when QML file is loaded, but not visualized. It is because its width and height properties are not defined. We determine them later in MouseArea script block.

Creation of Dynamic QML Objects

The process of dynamic creating QML objects consists of two steps. Firstly, an object is created and secondly a newly created object is loaded. The process is performed in a JavaScript context only. In other words, we have some JavaScript methods, which could be invoked in a script block.

The dynamic object is normally formatted as a QML file or we may want to get it as a string. Our considerations are limited to the second case. For this case steps of creating and loading are combined in one step. The signature of the respective JavaScript method is as "follows":http://doc.qt.nokia.com/4.7/qdeclarativedynamicobjects.html#creating-an-object-from-a-string-of-qml:

Qt.createQmlObject(QML string, parent ID, filename)<code>

The first argument in the method invocation is a string formatted as a QML file, for example:

‘Import QtQuick 1.1; Rectangle {width:100; height:100; color:”blue”}’

We use single quotes for the QML string to avoid conflict with double quotes in color property definition.

The second argument is the ID of the QML item in which context the new object is created and loaded. That means that newly created object has as parent this context item. In invocation the context item ID is supplied as a variable name (no quotes are needed).

The third argument is a string used as a file name in error reporting in the Qt Creator IDE. For example if an error is encountered in loaded QML string it is reported as one in the file with filename name. In invocation filename as a string has to be surrounded by double quotes.

The function createQmlObject() is a method of the global "QML Qt object":http://doc.qt.nokia.com/4.7/qml-qt.html#createQmlObject-method.

Formally it is described as:

object Qt::createQmlObject ( string qml, object parent, string filepath)<code>

If you are tempted to use a ''Loader'' element from the QML string it will probably not work. It is because loading of an external file needs some time, but the method ''createQmlObject()'' returns not waiting for such operations. Here is a complete example of ''createQmlObject()'' method utilization:

''dynamicObject.qml''

import QtQuick 1.1

Item {
id: container
width: 500; height: 100;
Rectangle {id:second; width:50;height:50;x:70;color:'green';
Component.onCompleted:{var dynamicObject = Qt.createQmlObject(
'import QtQuick 1.1;Rectangle{id:sample;width:40; height:40;color:"blue";}',
container,'firstObject')
}
} //end second
MouseArea {id:mouse1
anchors.fill: parent;
onClicked:{
var anotherObject =Qt.createQmlObject(
'import QtQuick 1.1;Rectangle{id:example;width:40; height:40;x:80;y:40;color:"pink";}',
container,'secondObject')
}//end onClicked
}//end mouse1

}

Practically it is inconvenient to supply a long QML string in ''createQmlObject()'' call. We could define a string property and assign to it QML string as property value. This is demonstrated in the following example:

''dynamicObjectTop.qml''

Item {

id: container
width: 500; height: 200;
property string test:"import QtQuick 1.1;
Rectangle {id:abc; color:quot;redquot;; width: 40; height: 20;x:100;y:50}"
Rectangle {id:second; width:50;height:50;x:70;color:'green';
Component.onCompleted:{
var dynamicObject = Qt.createQmlObject(test,container,'firstObject');
}
} //end second

}

== Communication with Dynamic Objects ==

The dynamically created QML object is not accessible through its ''ID''. We need other mechanisms for communication with dynamic objects and these are signals and QML signal objects ''connect ()'' method. By communication we mean exchange of information between the caller (main QML file) and the callee (dynamically created QML file) and in both directions.

The next snippet shows that a property defined at the caller top level is visible (directly accessible) in the QML dynamic object:

[[Image:http://i1072.photobucket.com/albums/w362/vabo123/property.jpg|property]]

''dynamicObjectProperty.qml''

import QtQuick 1.1 Item {

id: container
width: 500; height: 200;
property bool caller:false //This property is visible in the dynamic object
property string test:"import QtQuick 1.1;
Rectangle {id:abc; color:quot;redquot;; width: 40; height: 20;x:100;y:50
Component.onCompleted:
{caller=true; console.log('New value to the property caller was assigned')}
}"
Rectangle {id:second; width:50;height:50;x:70;color:'green';
Component.onCompleted:{
var dynamicObject = Qt.createQmlObject(test,container,'firstObject');
console.log("The value of caller was changed in the dynamic object")
}
} //end second

}

In case we want to pass some information from QML string to the main file we define a signal, then connect it to a JavaScript method defined in the main QML file and finally emit the signal (in QML string):

[[Image:http://i1072.photobucket.com/albums/w362/vabo123/signal.jpg|signal]]

''dynamicObjectConnect.qml''

import QtQuick 1.1 Item {

id: container
width: 500; height: 200;
signal forward //This signal activates fun2() defined in QML string
onForward:console.log("forward signal is emitted in main QML")
Rectangle {id:dummy
Component.onCompleted:{
forward();}
}
function fun1(argument1){
console.log("A function fun1()in the main QML file is invoked")
console.log("Returned parameter from the QML string = ", argument1)
}
property string test:"import QtQuick 1.1;
Rectangle {id:abc;
property string fromCallee:'This value is send signal argument'
//Signal send when emitted activates fun1() defined in main QML
signal send(string pass);
onSend:{console.log('Signal send has been emitted');}
color:quot;redquot;; width: 40; height: 20;x:100;y:50
Component.onCompleted:{
send.connect(fun1);send(fromCallee);forward.connect(fun2);
}
function fun2(){console.log('The function fun2() is activated from main QML')}
}
"
Rectangle {id:second; width:50;height:50;x:70;color:"green";
Component.onCompleted:{
var dynamicObject = Qt.createQmlObject(test,container,'firstObject');
}
} //end second

}

In the QML string we define the signal ''send'', which invokes function ''fun1()'' defined in main QML. A parameter is passed also. And vice versa: in the main QML the signal ''forward'' is defined and emitted. It calls function ''fun2()'' defined in QML string.

== Dynamic Objects Behavior  Nested Objects ==

Let us create an application with the following functionality:
1. The user enters some text in a QML ''TextInput'' element;
2. The entered text is displayed in a window;
3. The user is asked to confirm the entered text in a dialog window;

[[Image:http://i1072.photobucket.com/albums/w362/vabo123/input.jpg|input]]

The new point here is that from a dynamic object we want to create and load a next dynamic object.

''dynamicUseCase.qml''

import QtQuick 1.1 Item {

id: container
width: 550; height: 500;
property string test:"import QtQuick 1.1;
Rectangle {id:abc;
color:quot;lightsteelbluequot;; width: 200; height: 100;x:origin.x;y:origin.y+50;
Text {id:data;
width:abc.width; height:abc.height;
// width:200;height:40;
text:input.text
// 'Your input is echoed here. Multiline text is supported.';
wrapMode:Text.WordWrap;
}
MouseArea {id:callNested;
anchors.fill:parent;
onClicked:{var nextDynamic=Qt.createQmlObject(another,
container,'NextObject');
}
}
property string another:quot;import QtQuick 1.0;
Rectangle{id:nested;x:abc.x+220;y:abc.y;width:180;
height:40;color:'pink';
Text {id:dialog;
width:nested.width; height:nested.height;
text:'Do you accept entered data?'
}
}
quot;
}"
Rectangle{id:origin
width:300;height:30;y:100;x:100
color:"lightblue"
}
TextInput {
id:input
x:origin.x+5;y:origin.y+5;
color:"red"
text:"Enter your text here"
onAccepted:{
var dynamicObject = Qt.createQmlObject(test,container,"First Dynamic Object");
// release(dynamicObject);
}
}

/*' //This section experiments with destroy() method //Uncomment this section and call to release()in onAccepted script block

function release(objectToDelete){

console.log("A dynamic object is deleted");
objectToDelete.destroy(9000);
}

'*/ }

Some fragments of the above code need more explanation as they implement several rules of QML dynamic objects. In the first place, a dynamic QML object (rectangle abc) could contain a nested QML string – rectangle nested. As you see, the nested objects are in the same name space and this is used when we design the application layout – it is applied bounding of properties values. Moreover, the nested objects are executed in the context of the main QML file and they use properties from the main application space.

To invoke the first dynamic object (rectangle abc) we use onAccepted signal generated after you enter the text in the QML TextInput field and hit <Enter>. The entered text is displayed in the rectangle abc. When you click on this rectangle, following onClicked signal, the nested object (rectangle nested) is rendered.

Destroying Dynamic Objects

Analyzing the above code samples you probably asked yourself already how one could close dynamically created QML objects. To release (delete) a dynamically created QML object you could use destroy() method from within a JavaScript context. The method is called as member of newly created dynamic object. For example, considering our last code snippet, we call: dynamicObject.destroy(). The destroy() method has a parameter that supplies the time delay before the dynamic object will be destroyed. It is measured in milliseconds with a default value of zero. In the file dynamicUseCase.qml you have an commented section (at the file bottom) that experiments with destroy() method. Uncomment this section and call to release() function in onAccepted script block.

Notes: 1. The .qml files used in the discussions above could be downloaded "here":http://bit.ly/1a3zHkt. 2. The code samples were tested in a desktop environment – Windows 7, Qt Creator 2.4.1 (based on Qt 4.7.4).