One of the key concepts in QML is the ability to define your own QML components that suit the purposes of your application. The standard QML Elements provide the essential components for creating a QML application; beyond these, you can write your own custom components that can be created and reused, without the use of C++.
Components are the building blocks of a QML project. When writing a QML application, whether large or small, it is best to separate QML code into smaller components that perform specific sets of operations, instead of creating mammoth QML files with large, combined functionality that is more difficult to manage and may contain duplicated code.
A component is a reusable type with a well-defined interface, built entirely in QML. Any snippet of QML code can become a component, by placing the code in a file "<Name>.qml" where <Name> is the new component name, beginning with an uppercase letter. These QML files automatically become available as new QML element types to other QML components and applications in the same directory.
For example, one of the simplest and most common components you can build in QML is a button-type component. Below, we implement this component as a Rectangle with a clickable MouseArea, in a file named Button.qml:
// Button.qml import QtQuick 1.0 Rectangle { width: 100; height: 100 color: "red" MouseArea { anchors.fill: parent onClicked: console.log("Button clicked!") } }
Now this component can be reused by another file within the same directory. Since the file is named Button.qml, the component is referred to as Button:
// application.qml import QtQuick 1.0 Column { Button { width: 50; height: 50 } Button { x: 50; width: 100; height: 50; color: "blue" } Button { width: 50; height: 50; radius: 8 } } |
The root object in Button.qml defines the attributes that are available to users of the Button component. In this case, the root object is a Rectangle, so any properties, methods and signals of Rectangle are made available, allowing application.qml to customize the width, height, radius and color properties of Button objects.
If Button.qml was not in the same directory, application.qml would need to load it as a module from a specific filesystem path or plugin. Also, note the letter case of the component file name is significant on some (notably UNIX) filesystems. It is recommended the file name case matches the case of the QML component name exactly - for example, Box.qml and not BoX.qml - regardless of the platform to which the QML component will be deployed.
To write a useful component, it is generally necessary to provide it with custom attributes that store and communicate specific data. This is achieved by adding the following attributes to your components:
The following sections show how these attributes can be added to QML components.
A property is a value of a QML component that can be read and modified by other objects. For example, a Rectangle component has width, height and color properties. Significantly, properties be used with Property Binding, where a property value is automatically updated using the value of another property.
The syntax for defining a new property is:
[default] property <type> <name>[: defaultValue]
A property declaration can appear anywhere within a QML component definition, but it is customary to place it at the top. A component cannot declare more than one property with the same name. (It is possible to have a property name that is the same as an existing property in a type, but this is not recommended as the existing property becomes hidden and inaccessible.)
Below is an example. The ImageViewer component has defined a string type property named currentImage, and its initial value is "default-image.png". This property is used to set the image displayed in the child Image object. Another file, application.qml, can create an ImageViewer object and read or modify the currentImage value:
// ImageViewer.qml import QtQuick 1.0 Item { id: item width: 200; height: 200 property string currentImage: "default-image.png" Image { source: item.currentImage } } | import QtQuick 1.0 ImageViewer { id: viewer currentImage: "http://qt.nokia.com/logo.png" Text { text: viewer.currentImage } } |
It is optional for a property to have a default value. The default value is a convenient shortcut, and is behaviorally identical to doing it in two steps, like this:
// Use default value
property int myProperty: 10
// Longer, but behaviorally identical
property int myProperty
myProperty: 10
All QML properties are typed. The examples above show properties with int and string types; notice that the type of the property must be declared. The type is used to determine the property behavior, and how the property is defined in C++.
A number of property types are supported by default. These are listed in the table below, with their default values and the corresponding C++ type:
QML Type Name | Default value | C++ Type Name |
---|---|---|
0 | int | |
false | bool | |
0.0 | double | |
0.0 | double | |
"" (empty string) | ||
"" (empty url) | ||
#000000 (black) | ||
undefined | ||
undefined |
QML object types can also be used as property types. This includes custom QML types implemented in C++. Such properties are defined like this:
property Item itemProperty property QtObject objectProperty property MyCustomType customProperty
Such object-type properties default to an undefined value.
It is also possible to store a copy of a JavaScript object using the variant property type. This creates some restrictions on how the property should be used; see the variant type documentation for details.
List properties are created with the list<Type> syntax, and default to an empty list:
property list<Item> listOfItems
Note that list properties cannot be modified like ordinary JavaScript arrays. See the list type documentation for details.
Adding a property to an item automatically adds a value changed signal handler to the item. To connect to this signal, use a signal handler named with the on<Property>Changed syntax, using upper case for the first letter of the property name.
For example, the following onMyNumberChanged signal handler is automatically called whenever the myNumber property changes:
Item { property int myNumber onMyNumberChanged: { console.log("myNumber has changed:", myNumber); } Component.onCompleted: myNumber = 100 }
The optional default attribute for a property marks it as the default property for a type. This allows other items to specify the default property's value as child elements. For example, the Item element's default property is its children property. This allows the children of an Item to be set like this:
Item { Rectangle {} Rectangle {} }
If the children property was not the default property for Item, its value would have to be set like this instead:
Item { children: [ Rectangle {} Rectangle {} ] }
See the TabWidget example for a demonstration of using default properties.
Specifying a default property overrides any existing default property (for example, any default property inherited from a parent item). Using the default attribute twice in the same type block is an error.
Property aliases are a more advanced form of property declaration. Unlike a property definition, which allocates a new, unique storage space for the property, a property alias connects the newly declared property (called the aliasing property) as a direct reference to an existing property (the aliased property). Read operations on the aliasing property act as read operations on the aliased property, and write operations on the aliasing property as write operations on the aliased property.
A property alias declaration looks a lot like an ordinary property definition:
[default] property alias <name>: <alias reference>
As the aliasing property has the same type as the aliased property, an explicit type is omitted, and the special "alias" keyword is used. Instead of a default value, a property alias includes a compulsory alias reference. The alias reference is used to locate the aliased property. While similar to a property binding, the alias reference syntax is highly restricted.
An alias reference takes one of the following forms:
<id>.<property> <id>
where <id> must refer to an object id within the same component as the type declaring the alias, and, optionally, <property> refers to a property on that object.
For example, below is a Button.qml component with a buttonText aliased property which is connected to the child Text object's text property:
// Button.qml import QtQuick 1.0 Item { property alias buttonText: textItem.text width: 200; height: 50 Text { id: textItem } }
The following code would create a Button with a defined text string for the child Text object:
Button { buttonText: "This is a button" }
Here, modifying buttonText directly modifies the textItem.text value; it does not change some other value that then updates textItem.text.
In this case, the use of aliased properties is essential. If buttonText was not an alias, changing its value would not actually change the displayed text at all, as property bindings are not bi-directional: the buttonText value would change when textItem.text changes, but not the other way around.
Aliased properties are also useful for allowing external objects to directly modify and access child objects in a component. For example, here is a modified version of the ImageViewer component shown earlier on this page. The currentImage property has been changed to an alias to the child Image object:
// ImageViewer.qml import QtQuick 1.0 Item { id: item width: 200; height: 200 property alias currentImage: image Image { id: image } } | // application.qml import QtQuick 1.0 ImageViewer { id: viewer currentImage.source: "http://qt.nokia.com/logo.png" currentImage.width: width currentImage.height: height currentImage.fillMode: Image.Tile Text { text: currentImage.source } } |
Instead of being limited to setting the Image source, application.qml can now directly access and modify the child Image object and its properties.
Obviously, exposing child objects in this manner should be done with care, as it allows external objects to modify them freely. However, this use of aliased properties can be quite useful in particular situations, such as for the TabWidget example, where new tab items are actually parented to a child object that displays the current tab.
Aliases are only activated once the component specifying them is completed. The most obvious consequence of this is that the component itself cannot generally use the aliased property directly during creation. For example, this will not work:
// Does NOT work property alias buttonText: textItem.text buttonText: "Some text" // buttonText is not yet defined when this value is set
A second, much less significant, consequence of the delayed activation of aliases is that an alias reference cannot refer to another aliasing property declared within the same component. This will not work:
// Does NOT work
id: root
property alias buttonText: textItem.text
property alias buttonText2: root.buttonText
At the time the component is created, the buttonText value has not yet been assigned, so root.buttonText would refer to an undefined value. (From outside the component, however, aliasing properties appear as regular Qt properties and consequently can be used in alias references.)
It is possible for an aliased property to have the same name as an existing property. For example, the following component has a color alias property, named the same as the built-in Rectangle::color property:
Rectangle { property alias color: childRect.color color: "red" Rectangle { id: childRect } }
Any objects that use this component and refer to its color property will be referring to the alias rather than the ordinary Rectangle::color property. Internally, however, the rectangle can correctly set this property to "red" and refer to the actual defined property rather than the alias.
A QML component can define methods of JavaScript code. These methods can be invoked either internally or by other objects.
The syntax for defining a method is:
function <name>([<parameter name>[, ...]]) { <body> }
This declaration may appear anywhere within a type body, but it is customary to include it at the top. Attempting to declare two methods or signals with the same name in the same type block is an error. However, a new method may reuse the name of an existing method on the type. (This should be done with caution, as the existing method may be hidden and become inaccessible.)
Unlike signals, method parameter types do not have to be declared as they default to the variant type. The body of the method is written in JavaScript and may access the parameters by name.
Here is an example of a component with a say() method that accepts a single text argument:
Rectangle { id: rect width: 100; height: 100 function say(text) { console.log("You said: " + text); } MouseArea { anchors.fill: parent onClicked: rect.say("Mouse clicked") } }
A method can be connected to a signal so that it is automatically invoked whenever the signal is emitted. See Connecting signals to methods and other signals below.
Also see Integrating JavaScript for more information on using JavaScript with QML.
Signals provide a way to notify other objects when an event has occurred. For example, the MouseArea clicked signal notifies other objects that the mouse has been clicked within the area.
The syntax for defining a new signal is:
signal <name>[([<type> <parameter name>[, ...]])]
This declaration may appear anywhere within a type body, but it is customary to include it at the top. Attempting to declare two signals or methods with the same name in the same type block is an error. However, a new signal may reuse the name of an existing signal on the type. (This should be done with caution, as the existing signal may be hidden and become inaccessible.)
Here are three examples of signal declarations:
Item { signal clicked signal hovered() signal performAction(string action, variant actionArgument) }
If the signal has no parameters, the "()" brackets are optional. If parameters are used, the parameter types must be declared, as for the string and variant arguments for the performAction signal above; the allowed parameter types are the same as those listed in the Adding Properties section on this page.
Adding a signal to an item automatically adds a signal handler as well. The signal hander is named on<SignalName>, with the first letter of the signal being upper cased. The above example item would now have the following signal handlers:
To emit a signal, simply invoke it in the same way as a method. Below left, when the MouseArea is clicked, it emits the parent buttonClicked signal by invoking rect.buttonClicked(). The signal is received by application.qml through an onButtonClicked signal handler:
// Button.qml import QtQuick 1.0 Rectangle { id: rect width: 100; height: 100 signal buttonClicked MouseArea { anchors.fill: parent onClicked: rect.buttonClicked() } } | // application.qml import QtQuick 1.0 Button { width: 100; height: 100 onButtonClicked: console.log("Mouse was clicked") } |
If the signal has parameters, they are accessible by parameter name in the signal handler. In the example below, buttonClicked is emitted with xPos and yPos parameters instead:
// Button.qml Rectangle { id: rect width: 100; height: 100 signal buttonClicked(int xPos, int yPos) MouseArea { anchors.fill: parent onClicked: rect.buttonClicked(mouse.x, mouse.y) } } | // application.qml Button { width: 100; height: 100 onButtonClicked: { console.log("Mouse clicked at " + xPos + "," + yPos) } } |
Signal objects have a connect() method that can be used to a connect a signal to a method or another signal. When a signal is connected to a method, the method is automatically invoked whenever the signal is emitted. (In Qt terminology, the method is a slot that is connected to the signal; all methods defined in QML are created as Qt slots.) This enables a signal to be received by a method instead of a signal handler.
For example, the application.qml above could be rewritten as:
Item { id: item width: 200; height: 200 function myMethod() { console.log("Button was clicked!") } Button { id: button anchors.fill: parent Component.onCompleted: buttonClicked.connect(item.myMethod) } }
The myMethod() method will be called whenever the buttonClicked signal is received.
In many cases it is sufficient to receive signals through signal handlers rather than using the connect() function; the above example does not provide any improvements over using a simple onButtonClicked handler. However, if you are creating objects dynamically, or integrating JavaScript code, then you will find the connect() method useful. For example, the component below creates three Button objects dynamically, and connects the buttonClicked signal of each object to the myMethod() function:
Item { id: item width: 300; height: 100 function myMethod() { console.log("Button was clicked!") } Row { id: row } Component.onCompleted: { var component = Qt.createComponent("Button.qml") for (var i=0; i<3; i++) { var button = component.createObject(row) button.border.width = 1 button.buttonClicked.connect(myMethod) } } }
In the same way, you could connect a signal to methods defined in a dynamically created object, or connect a signal to a JavaScript method.
There is also a corresponding disconnect() method for removing connected signals. The following code removes the connection created in application.qml above:
// application.qml Item { ... function removeSignal() { button.clicked.disconnect(item.myMethod) } }
The connect() method can also connect a signal to other signals. This has the effect of "forwarding" a signal: it is automatically emitted whenever the relevant signal is emitted. For example, the MouseArea onClicked handler in Button.qml above could have been replaced with a call to connect():
MouseArea { anchors.fill: parent Component.onCompleted: clicked.connect(item.buttonClicked) }
Whenever the MouseArea clicked signal is emitted, the rect.buttonClicked signal will automatically be emitted as well.