QML Application Structuring Approaches

From Qt Wiki
Revision as of 16:34, 14 January 2015 by Maintenance script (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

English

QML Application Structuring Approaches

Introduction

A common approach to deal with a complex application is to decompose it in several smaller units. This leads to better structuring of application and more productive development process as a whole. QML offers two constructs that service this approach – Component and Loader elements. Roughly speaking, the both elements bring similar functionality. But the ways they do this are different. We may refer to the Component approach as static one and the Loader approach as dynamic one. We first re-call Component and Loader definitions, than compare the two approaches and in the main part of this article describe several Loader element use cases. There are two other approaches to structure a QML application also – dynamic creation of objects and module importing. They are beyond the scope of this article.

Component Element Definition

A QML component is a separate, autonomous unit that contains other QML elements. It could be defined in two modes – as an inline unit in a QML file or as a separate QML file. The main point with QML component is that it could be reused many times trough its very convenient interface. For more details about Component syntax and interface consult the documentation. [doc.qt.nokia.com]

How does the Component element support application code structuring and particularly its decomposition? In case of component file it is obvious – the application is split in several components. They are included in the main file and instantiated via their interfaces, for example like this:

QML File

Including of the component means that we get an instance of the component viewed as a template. The basic component is used to generate instances only and they do not depend anymore on it. We may have several instances and they behavior independently, treated like basic QML elements. Note that Component interface allows to re-define component properties – predefined ones as well as defining new ones. This is the case of the color property in the snippet above. We could even just instantiate the component and not alter any property.

We may have many instances of Banner component provided that they have different id.

By now we are considering properties defined at the top level of the component file. What about siblings’/children’ properties in a component file? Let us consider the following case:

ExampleOne.qml – Component file

componentScope.qml – It is using component ExampleOne

As you see if we want to re-define the property color of nameText child we have to make it visible at the top level. To get this we define an alias of the nameText at the top level. This alias is re-defined in componentScope.qml file.

Could we develop more complex structures with components? For instance, could we have nested components? By nested components we mean that a component contains another component. Structures like these are acceptable:

nestedComponents.qml – Main file

Comp1.qml – First level component file

Comp2.qml – Second level component file

The inline definition syntax of the component element is very similar to this one of other QML elements. It starts with a key word Component followed by element block (represented by { } pair). Within the block you may define and nest other QML elements – refer to Nokia documentation [doc.qt.nokia.com].

On the other hand, inline Component offers other kind of structuring. It encapsulates a piece of QML code that could be assigned as a property value of other QML elements. Particularly this mechanism is used in the definition of the Loader element – see the next section. This kind of structuring is sometimes referred as aggregation. An example how inline Component could be utilized is demonstrated in section Loader and Inline Component.

Loader Element Definition

Loader element serves as a handler, which loads dynamically a QML unit formed as a separate QML file or inline component. Dynamically means that the QML unit to be loaded is determined at run-time, for example if a mouse button is clicked. The loaded unit is described as value of source or sourceComponent property of the Loader. The source value is a URL value – the path to QML file to be loaded. The sourceComponent value is id name of an inline component. Where can we bind values to these properties? The rule is: at places where the Loader id name is visible. In the following sections you see many examples of implementation of this rule. For more information on Loader see documentation [doc.qt.nokia.com].

One interesting point is how the loaded QML unit could be accessed. The Loader element has a default property item. It is read-only and has Item type. In other words, loaded QML unit is a QML Item element. We recall that it is not visible and serves to group other QML elements. An illustrating snippet on item property follows:

LoaderItem.qml

Page.qml

We could set up source or sourceComponent properties in the Loader definition. Now the Loader element is processed like any other QML element and its target will be rendered. In this case the behavior is similar to the one of component element. But note that we may change programmatically properties values of source or sourceComponent at a later time.

Component Element vs. Loader Element

Now we are going to compare component file and Loader element. They both offer similar functionality, but differ in ways they are applied.

In case of a component file we have the following process. We declare the component (rather its interface) in a fixed way in the main file for example. QML parser immediately checks if a component file with the name as in component declaration exists. The parser searches in the directory where is placed the file.

The Loader acts a bit differently. When a QML file containing a Loader element is parsed, the Loader constructs itself like any other QML element. The Loader could define the source destination of the file to be loaded or not. In the first case the file is loaded. In the second case this is postponed to the moment we define the source destination of the file in a script element for example. Note that in case of source property we may point to a file located anywhere on our local computer or on the Internet. If we have many QML entities that are not needed permanently, there is no need to keep them in memory – rather load them as they are necessary. This leads to a better structuring of the application and more efficient computer memory management. Be aware that any consequent activation of a Loader destroys the previously loaded items. If we set an empty string as source property value or undefined value to sourceComponent property, the memory used by the Loader is released.

Because the file to be loaded by a Loader could be determined in run-time, the Loader approach is referred to as dynamic one. The component approach is called static one.

Combining loaders, components and other QML elements it is important to understand the hierarchy of elements and how they overlap each other. Let us create a demo example, which will illustrate QML elements rendering hierarchy. We are considering a QML main file that has two Loaders, one component and other standard QML elements. We re-define the z property of elements to control their overlapping.

elementHierarchy.qml – main file

firstLoader.qml – file loaded by newLoad Loader

secondComp.qml – loaded by loadSecond Loader

CompNew.qml – component file

The next picture illustrates how QML elements are displayed and overlapping. As a rule elements are rendered in the order they are defined in the main file. In the snippets above z property is changed for text element textInner (z=3) and for rectangle element second (z=2) and they are rendered at the top element hierarchy.

Levels of elements

You may experiment changing z property.

Loader Element Use Cases

Simple Wizard Template

Our application consists of several pages (3 in the snippet bellow), each of them formatted as a separate QML file. These files could be located in different directories, but for simplicity we suppose they reside in the directory where is located our main file. Each page has its own Loader element and when a Click me button is pressed the Loader loads a next wizard page.

wizardSimple.qml – main file

Page1.qml – first wizard page

Page2.qml – second wizard page

Page3.qml – last wizard page

The above wizard implementation is very simple, but suffers of some drawbacks:
• When a new page is loaded the previously loaded page stays unnecessary into memory.
• We use similar loaders for each page – their IDs are different only.
• The order of pages loading is fixed one – the source property values are set in advance.
• There is no mechanism for exchange of information between the caller and the called page and vise-verse.

More Complex Navigation

Now we will try to release the restrictions of the loading scheme from the previous section. Firstly, following the Simon Judge idea [mobilephonedevelopment.com], we will apply a common Loader for all pages. Secondly, we suppose that the next page to be loaded is not known in advance – it is determined after a condition is evaluated. And finally, we will use signals to pass information between pages and demonstrate how multi-window screens could be constructed.

A common approach to trigger some actions after an event is fired is to define an own signal like that (for more information here [doc.qt.nokia.com]):

signal MySignal(string message, int trigger)

where signal is QML keyword, MySignal is the name of newly defined signal, message is a parameter of type string and trigger is another parameter. The parameters could be of different QML types. For our purposes we want to pass a string that is the name of wizard page to be loaded and an index to identify the application state.

The main idea is as follows:

• When we want to load a page we emit our own signal.
• This signal is caught by a Loader defined somewhere.
• Our signal has a parameter to which we bound the name of the page to be loaded – as an URL value.

Use Case: We will consider an application that consists of a main page (Page1) and two other pages (Page2, Page3). The pages should be loaded sequentially and construct a wizard this way. Some of pages could be loaded checking for a logical condition creating the wizard branches.

Objectives: To demonstrate how a flexible wizard could be implemented based on QML Loader element. Additionally we may complicate the wizard constructing some multi-windows screens.

Design: Pages are loaded one by one. The application starts loading automatically Page1. To load the next page we should click the left mouse button. For Page1 we have two wizard branches depending on a logical value. We introduce a current index for pages (an integer) around which the application logic is build.

Development: For each page we define the same own signal:

signal handlerLoader(string name, int index)

To activate this signal (emit the signal) we have to invoke it as a method in a script block. A MouseArea onClicked script block is used. In invocation the name of the page to be loaded is passed as parameter. Emitted signal is attached (automatically) to the signal handler onHandlerLoader, which in turn is connected to a target pageLoader.item. The connection is carried out by a QML Connections element in the QML file flexibleLoader.qml. The target pageLoader.item is in fact the object loaded by the Loader (id:pageLoader) defined in the file flexibleLoader.qml. Note that the target property of the Connections element is of type Object. When Page2 is loaded we want a second window (NewWindow.qml) to be loaded into the same screen. This is done by a second Loader (id:window), which is defined in the file flexibleLoader.qml. The decision to load this second window is taken in onHandlerLoader block on the base of returned value of trigger parameter. Note that the Loader destroys previously loaded entities when loading new items.

Code:

flexibleLoader.qml – application main file

Page1.qml – page 1

Files Page2.qml and Page3.qml are similar to Page1.qml.

Download Package: Necessary files: flexibleLoader.qml, Page1.qml, Page2.qml, Page3.qml, Help.qml, NewWindow.qml. Get them from here [bit.ly].

Loader and Component Together

Use Case: Assume the page we are loading uses an external component (component file) and from this component we want to load a next page. What is new in this use case comparing with the previous one? The call to the
Loader is not located at the top level now. It is nested in a component. When the Loader is defined in the main application the pages are loaded in its context. This requires the Loader source property to be determined at the top level also. To solve this problem we use cascading definitions of properties. Firstly, have a look at a diagram illustrating the use case:

Loader and Component

Design: It follows the diagram above. The main file componentLoader.qml loads Page1.qml, which in turn loads Page2.qml. The Page2.qml file instantiates a component – Inserted.qml. From within Inserted.qml the Loader in componentLoader.qml is invoked.

Development: The issue here is that the signal handlerLoader has to be activated at the top level of the file Page2.qml. When instantiating component Inserted we define an alias to the top level id: property alias next1:pop. The signal is invoked in Inserted.qml through a qualified name – next1.handlerLoader(“Page3.qml”,0).

Code: Main file componentLoader.qml and files Page1.qml and Page3.qml are similar to these ones of the previous section.

Page2.qml – this file instantiates component file Insert.qml

Inserted.qml – this is component file

Download Package: Necessary files: componentLoader.qml, Page1.qml, Page2.qml, Page3.qml, Inserted.qml. Get them from here [bit.ly].

Loader and Inline Component

Use case: We want to implement an advertising banner composed from images and texts. We have several images combined with explanatory texts and these pairs are displayed cyclically. Additionally a button is needed to be bound to each displayed pair – for example to get more information on advertised topic. Have a look at some screenshots.

First Image

Fourth Image

Objectives: To demonstrate how a loader and an inline component could be used together. Moreover nested components and QML Timer element are highlighted.

Design: Bellow you have a diagram illustrating what and how QML elements are employed.

Diagram

Development: The cycling is implemented through a QML Timer element. Each time the Timer generates an onTriggered signal, the sourceComponent property of the Loader (id:test) is changed to load the next pair image-text. Images are arranged in a JavaScript array – defined in JavaScript function act(). The texts are placed in the JavaScript array externalArray – defined in a separate file arrayExt.js – just to show how this approach works. The pairs image-text are modeled with a inline component – id:varText – which is loaded sequentially by the loader. The necessary button is implemented as an external component – file Button.qml – and which is nested in the component varText. The handler, connected to this button, is illustrated with console.log() function output.

Code: Each cycle forms a state. The states are indentified via the property loop, which gets int values and is used as arrays current index.

bannerComponent.qml

Button.qml

arrayExt.js

Download Package: Necessary files: bannerComponent.qml, Button.qml, arrayExt.js, fall1.jpg, fall2.jpg, fall3.jpg, fall4.jpg. Get them from here [bit.ly].

Note: The download package is organized in subdirectories corresponding to respective sections:
• Component Element Definition – Component
• Loader Element Definition – Loader
• Component Element vs. Loader Element – ComponentvsLoader
• Simple Wizard Template – SimpleWizard
• More Complex Navigation – ComplexNavigation
• Loader and Component Together – LoaderandComponent
• Loader and Inline Component – LoaderInline

Download Package [bit.ly].

Categories: