Dynamic QML components

If your application contains a relatively simple UI, it's common practice to load all the QML into memory and link it to the scene graph when your app starts. A control is linked to the scene graph once it's added to the node (or a sub-node) that is currently set as the root of the application's scene (set by Application::instance()->setScene()). In some applications, this might not be practical. Applications with a lot of QML can take a long time to start-up if everything is being loaded into memory and linked to the scene graph right away. For applications that contain a lot of QML, creating QML components dynamically might be necessary to speed up loading times.

To solve this problem, you can use the ControlDelegate and ComponentDefinition classes to load QML when it's actually needed, shortening start up time and increasing performance. Each class represents a different way of loading content; ComponentDefinition is the imperative method and ControlDelegate is the declarative method. ControlDelegate works for classes inheriting from the Control class.

If you're looking for a declarative way of dynamically loading objects that aren't subclasses of Control and provide object lifecycle management similar to ControlDelegate, the Delegate class is available.

Using ComponentDefinition

The ComponentDefinition class represents an imperative way to create dynamic components in QML. The first step to using this method is to define your components using a ComponentDefinition object. The content itself can be defined inline (using the default content property) or by loading the content from a QML file specified by URL (using the source property). After you define the ComponentDefinition, you can attach it to any visual object in your QML file using the attachedObjects property.

The next step is to load the object into memory. In many cases, you might want to do this after the main part of the application UI has already been loaded, or in response to a user's action. To create the object, simply call the createObject() function from JavaScript. Once the object is created, all that's left to do is add it where you want it in the scene graph.

In this example, a ComponentDefinition object is created from a QML file called test.qml. When a button is clicked, the component is loaded into memory and added as a child to the root container.

Container {              
    id: rootContainer               
    Label  {                  
        text: "Example Component Definition"              
    }              
    Button  {                  
        text: "Click to create dynamic component"
        onClicked :  {                      
            // Create the component and add it to the Container   
            var createdControl = compDef.createObject();
            rootContainer.add(createdControl);
        }             
    }
    attachedObjects: [                  
        ComponentDefinition {                      
            id: compDef                       
            source: "test.qml"             
        }
    ]         
} 

Using ControlDelegate

The ControlDelegate class represents a declarative way to create QML components dynamically in your application. Unlike ComponentDefinition, you don't need to use JavaScript to instantiate your dynamic components and add them to the scene graph. ControlDelegate is essentially a placeholder or stub that you can insert into the scene graph. Instead of instantiating and adding objects imperatively, you define the ControlDelegate and set the delegateActive property to true to create and display the object. When you no longer need the ControlDelegate, you can flip delegateActive back to false to destroy the object, while still maintaining its position in the scene graph. The content for a ControlDelegate can be defined by a ComponentDefinition or by a QML file specified by a URL (using the source property). It's also important to note that you can use ControlDelegate only to create and display visual controls, unlike ComponentDefinition, which can be used to create any object.

This example demonstrates how you can use ControlDelegate to load pages asynchronously in a NavigationPane. Each Page in the NavigationPane contains a ControlDelegate that points to the actual content for the page. When the application starts, only the content for the first Page is loaded initially. Once the first page is created and displayed, the creationCompleted() signal is emitted, informing the application that the delegateActive flags can be enabled for the remaining pages.

import bb.cascades 1.0

NavigationPane {
    id: navigationPane

    // The first page, which appears at startup  
    Page {
        id: page1
        ControlDelegate {
	        id: controlDelegate1
	        source: "page1.qml"
	        delegateActive: true        
        }
        // Once the first page is created, the second
        // and third pages are loaded asynchronously so that the
        // content is ready to be loaded right away.
        onCreationCompleted: {
            controlDelegate2.delegateActive = true             
            controlDelegate3.delegateActive = true             
        }
        actions: [
            // Action that pushes page 2 onto the stack
            ActionItem {
                ActionBar.placement: ActionBarPlacement.OnBar
                title: "Page 2" 
                onTriggered: {
                    navigationPane.push(page2);
                }
            }
        ]
        // A Back button that pops the pane off the stack
        paneProperties: NavigationPaneProperties {
            backButton: ActionItem {
                title: "Back"
                onTriggered: {
                    navigationPane.pop();
                }
            }
        }
    }   
    attachedObjects: [
        // The second page
        Page {
            id: page2
            ControlDelegate {
                id: controlDelegate2
                source: "page2.qml"
            }
	        actions: [
	            // Action that pushes page 3 onto the stack
	            ActionItem {
	                ActionBar.placement: ActionBarPlacement.OnBar
	                title: "Page 3"	                 
	                onTriggered: {
	                    navigationPane.push(page3);
	                }
	            }
	        ]
	        // A Back button that pops the pane off the stack
	        paneProperties: NavigationPaneProperties {
	            backButton: ActionItem {
	                title: "Back"
	                onTriggered: {
	                    navigationPane.pop();
	                }
	            }
	        }
        },
        // The third page
        Page {
            id: page3
            ControlDelegate {
                id: controlDelegate3
                source: "page3.qml"
            }
	        // A Back button that pops the pane off the stack
	        paneProperties: NavigationPaneProperties {
	            backButton: ActionItem {
	                title: "Back"
	                onTriggered: {
	                    navigationPane.pop();
	                }
	            }
	        }
        }
    ]      
}

To improve the performance even more, you might want to load only the content that is immediately required for the next user interaction. In this scenario, maintaining only the immediately required content is simple, you just need to keep the previous page and next page in memory and linked to the scene graph.

When the first page is the active pane, only the second page needs to be loaded into memory. Once the user navigates to the third page, the content for the first page can be destroyed. Only when the second page is the active pane should all the content be loaded into memory. In an example of this size, loading only the content that is immediately required might not seem to be worth the trouble, but in larger applications, this can save a lot of memory and significantly improve UI performance.

Using Delegate

The Delegate class represents a declarative way to create QML components that are subclasses of BaseObject. The Delegate class exposes an active property that controls the loading and unloading of QML components. When you want to create and display the object, set the active property to true. When the object is no longer needed, set the active property to false and destroy the object.

This example shows the dynamic loading of a page using Delegate. A Page with a Button is defined inline within a Delegate. The id of the Delegate, sheetPageDelegate, is used to control the loading and deleting of the page. The page is loaded when the sheet is opened by setting sheetPageDelegate.active to true and the page is deleted when the sheet is closed by setting sheetPageDelegate.active to false.

Page { 
    attachedObjects: [
        Delegate {
            id: sheetPageDelegate
            Page {
                Container {
                    Button { 
                        text: "Close"
                        onClicked: {
                            sheet.close();
                        }
                    }
                }
            }
        },
        Sheet {
            id: sheet
            content: sheetPageDelegate.object
            onOpenedChanged: {
                if (opened)
                    sheetPageDelegate.active = true;
            }
            onClosed: {
                sheetPageDelegate.active = false;
            }
        }
    ]
    Container {
        Button { 
            text: "Open sheet"
            onClicked: {
                sheet.open();
            }
        }
    }
}

To learn how to use Delegate for dynamically loading tabs, see Dynamic loading of tabs.

Last modified: 2013-12-21



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus