Tutorial: Custom QML components

In this tutorial, we'll learn about joining QML components together by creating a PostcardView component from an ImageView, a  TextField, and some containers.

After we create our custom component, we'll see how to use it in an application and modify its appearance and behavior.

You will learn to:

  • Create a custom QML component
  • Use a custom component
  • Rotate and scale a component
  • Use JavaScript in QML

Before you begin

You should have the following things ready:

  • The BlackBerry 10 Native SDK
  • A device or simulator running BlackBerry 10
Screen showing the end result of the tutorial.

Set up your project

Create a project

The first thing we must do is create a Cascades project using the Standard empty project template. For more information on creating a project, see Managing projects.

Create a new QML file

Next, we need to create a new QML file for our PostcardView custom control:

  1. In your project, right-click the assets folder and select New > Other.
  2. Expand BlackBerry, click QML File, and click Next.
  3. In the File Name field, provide a name for your custom control (for example, PostcardView).
  4. In the Template drop-down list, select Container.
  5. Click Finish.

Add the image assets

The last part of the setup is adding our image resources to the project. The .zip file included as a part of this tutorial contains images of four different locations, a default image, and an overlay image.

To import images into your project:

  1. Download the images.zip file.
  2. Extract the images folder to the project's assets folder in your workspace. For example, C:\your_workspace\project_name\assets.
  3. In the Project Explorer view, refresh your project to display the imported images.
Screen showing the Cascades postcard app project images.

Create the PostcardView control

If you're copying QML code from the tutorial and pasting it directly into your project, you'll notice that errors will sometimes occur. This tutorial is designed so that you can copy and paste each snippet one after another, so the closing braces for some components will not be included until you get to the end of the tutorial.

Now that the project is set up, let's start creating our custom component. In the assets folder, double-click PostcardView.qml to open the file in the editor. Go ahead and remove all the existing code from the PostcardView.qml file since we will build it from scratch. The PostcardView component is pretty simple; it consists of two  ImageView controls and a  Label, all within a  Container. One of the ImageView controls is the postcard image and the other is a static overlay that provides a border, a drop shadow, and a glossy finish for the postcard.

First, create a Container that uses an  AbsoluteLayout as its layout. We're using AbsoluteLayout because we want to position our text directly on top of our postcard image.

import bb.cascades 1.0
 
// The root container for the custom component
Container {
    layout: AbsoluteLayout {}

Next, let's add our two images. The image that is added first is the default postcard image and the second image that we add is the overlay. Since the postcard image is added to the container first, the overlay image is displayed in front of it on the screen.

The postcard image needs an id property. This will be necessary when we change the image that is being displayed. The layoutProperties for the postcard image are used to offset the image so that it is displayed in the center of the overlay.

// The postcard image
ImageView {
    id: imageView
    imageSource: "asset:///images/default.png"
    layoutProperties: AbsoluteLayoutProperties {
        positionX: 14
        positionY: 14
    }
}
// The overlay image that sits on top of the postcard image
ImageView {
    imageSource: "asset:///images/overlay.png"
}

The last part of our custom component is a  TextArea that we'll use to add some text on top of our image. The TextArea needs an id property so that we can dynamically change the text that is being displayed.

Along with the id property, there are other properties that must be set. The positioning and size of the text area are set using the layoutPropertiespreferredWidth, and preferredHeight properties. To make our greeting stand out on our postcard, we set the textStyle to bold and white. We also set the editable property to false so that the end user can't modify the text.

    // The text displayed on the postcard
    TextArea {
        id: textArea
        text: "Greetings from... "
        preferredWidth: 400
        preferredHeight: 400
        backgroundVisible: false
        editable: false
         
        textStyle {
            fontWeight: FontWeight.Bold
            color: Color.create ("#FFFFFF")
        }
         
        layoutProperties: AbsoluteLayoutProperties {
            positionY: 20
            positionX: 25
        }
    }
} // End of the root container

Add the custom component to the app

Now that we've created our custom component, all we need to do is add it to our application. In the Project Explorer view, double-click the main.qml file to open it in the editor.

To start, create a  Page component at the root of the QML document. Page is a subclass of AbstractPane, which is the required root component for QML documents in Cascades.

Within the Page component, create a  Container with a  DockLayout and bind it to the content property for the page. Within the container, create an instance of the PostcardView component and give it an id. Finally, set the layout properties so that the container is centered within its parent's DockLayout.

import bb.cascades 1.0
 
Page {
    // The root container
    content: Container {
        layout: DockLayout {} 
        background: Color.create("#262626");
         
        // The custom component
        PostcardView {
            id: postCard
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center     
        }
    }
}

Just build and run the application, and your custom component will be displayed in the center of the screen.

Next we'll add a  TextArea that allows the user to type the greeting text that is displayed, and a  Button that updates the postcard.

Screen showing the postcard component.

Add a TextArea and a Button

First, open the main.qml file in the editor and remove the root Container and its contents. When you're done, main.qml should look like this:

import bb.cascades 1.0
 
Page {
    content:
     
}

Before we can add the  TextArea and the  Button, we must add some containers to help us position the various components.

We need a root container that fills the screen, and two child containers that divide the screen horizontally. The container on the top displays the TextArea and the Button, while the one on the bottom displays the custom component.

The first container we create is the root container. This container uses a  StackLayout, since we want the content containers to be positioned one above the other.

Screen showing the layout of the postcard QML component.
import bb.cascades 1.0
 
Page {
    // The root container
    content: Container {
        layout: StackLayout {}
        background: Color.create("#262626");

Next, we create the top container that displays the TextArea and the Button. This container also uses a StackLayout, since the TextArea will be positioned above the Button. The container's layoutProperties are set so that it fills its parent's layout horizontally.

// The top container. This container holds a TextArea and a Button.
Container {
    // Padding to create some space between the edge of the
    // container and its child controls
    leftPadding: 50
    rightPadding: 50
    topPadding: 50
    layout: StackLayout {}
    horizontalAlignment: HorizontalAlignment.Fill

Within this container, we create the TextArea and Button.

The TextArea has a bottomMargin property, which inserts some space between it and the Button, and it has a text property that contains a simple greeting message. The Button has a  clicked() signal that we need to capture when the user clicks the button. We'll define the behavior for that signal handler later on.

    // The text area that contains the greeting text that is
    // displayed on the post card           
    TextArea {
        id: greetingPhrase
        bottomMargin: 10
        text: "Greetings from..."
    } 
    // The button used to generate a new postcard            
    Button {
        text: "Create"                
    }
}

Next, we add back in the Container and PostcardView controls that we previously created, with some modifications. Instead of specifying a preferred width and height, we must set the horizontal and vertical alignment to position the Container within its parent's StackLayout. We also set the spaceQuota property to 1.0 so that the Container expands to fill any remaining space in its parent container.

        // The bottom container. This container has the custom component.
        Container {
            layout: DockLayout {}
            horizontalAlignment: HorizontalAlignment.Fill
            verticalAlignment: VerticalAlignment.Fill            
            layoutProperties: StackLayoutProperties {
                spaceQuota: 1.0
            }   
            PostcardView {
                id: postCard
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
            }
        } // End of the bottom container
    } // End of the root container
} // End of the page

Now, build and run the application again to see the new layout and components. At this point however, there's no functionality attached to the button.

In the next part of the tutorial, we'll add some JavaScript logic that randomly selects the postcard image that is displayed each time the button is pressed.

Lastly, we'll play with the rotation and scale properties from VisualNode to see how we can modify the appearance of the PostcardView component.

Screen showing the postcard component.

Generate a new postcard

First, open the PostcardView.qml file in the editor.

Before we can generate new postcards, we need to access some of the inner properties of PostcardView. Even though PostcardView contains a  TextArea with a text property and an ImageView with an image property, they are not accessible from outside the component because they are not defined at the root of PostcardView.

To expose these properties, you must define new properties at the root of the component, and bind them to the inner properties that you want to expose using an alias. Now, when you use the PostcardView control, you can easily access its text and image properties.

// The root container for the custom component
Container {
    property alias image: imageView.imageSource
    property alias text: textArea.text
     
    ...

Next, we need to create a JavaScript function that generates a new postcard when the user presses the button on the screen. This function must contain logic that selects an image at random, and then rotates and scales the postcard to produce a unique effect each time a new postcard is generated.

In main.qml, at the root of the  Page, create a function called createPostcard().

import bb.cascades 1.0
 
Page {
    // The root container for the application
    Container {
        // The content for the root container
    }
     
    // Generates a new postcard
    function createPostcard () {
        // TODO: Add functionality 
    }
}

Within createPostcard() the first thing we'll do is add some logic that randomly selects an image from one of the four locations. We'll create a switch statement to handle each of the four options. In addition to setting the image property for the PostcardView, this statement also sets the text property by combining the greeting phrase with the city name that goes with the image.

// Choose a random number between 1 and 4.
var r = Math.ceil (Math.random () * 4)
switch (r) {
    case 1:
        // Set postcard image and message to Malmo.
        postCard.text = greetingPhrase.text + " Malmö!"
        postCard.image = "asset:///images/malmo.png"
        break
    case 2:
        // Set postcard image and message to Marseille.
        postCard.text = greetingPhrase.text + " Marseille!"
        postCard.image = "asset:///images/marseille.png"
        break
    case 3:
        // Set postcard image and message to Rome.
        postCard.text = greetingPhrase.text + " Rome!"
        postCard.image = "asset:///images/rome.png"
        break
    case 4:
        // Set postcard image and message to Waterloo.
        postCard.text = greetingPhrase.text + " Waterloo!"
        postCard.image = "asset:///images/waterloo.png"
        break
    default:
        // Use the fallback image
        postCard.text = greetingPhrase.text
        postCard.image = "asset:///images/default.png"
} // Ends the switch statement

Next, we must rotate and scale our PostcardView component. The properties for handling visual effects such as rotate and scale are a part of VisualNode, which is inherited by all Cascades classes that have a visual component.

    // Rotate the whole component to a random number between 
    // -10 and 10 degrees.
    postCard.rotationZ = (Math.random () * 20) - 10
     
    // Scale the whole component to a random number between 
    // 0.7 and 1.2
    var s = Math.random () * 0.5 + 0.7
    postCard.scaleX = s
    postCard.scaleY = s

Lastly, just call the createPostcard() function from the onClicked signal handler on the button.

// The button used to generate a new postcard
Button 
{
    ...
    ...
     
    // Invoke createPostcard() when the button is clicked
    onClicked: { 
        createPostcard () 
    }
}

Congratulations! Build and run the application one last time and you're done.

Once you're done, try to:

  • Create a new custom component and add it to the application.
  • Add a slider that can manipulate the size of the postcard.
  • Add functionality that changes the color of the text in the TextArea to blue, if the word "blue" is typed into the field.
Screen showing the app that we built in this tutorial.

Last modified: 2014-11-17



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

comments powered by Disqus