Today, there are a lot of tools available for user interface development. In the past two chapters, you've seen a pretty typical approach taken by a platform vendor: provide robust APIs in a commonly known programming language (Qt with C++) to enable developers to create their products. This approach is not without its drawbacks. The cost of learning an entire new API set can be high for some, and even with an API as all-encompassing as Qt, there's still a lot of rote programming (think new
and delete
) you must do as you develop your application. Surely there's a better way.
To further streamline your development efforts—especially for new applications—Nokia provides Qt Quick, a declarative programming environment consisting of Qt Meta-object Language (QML), common components, and bindings to JavaScript and C++. In this chapter we show you what Qt Quick is, how to use QML, and how to connect QML applications to existing or new C++ and JavaScript. To give you hands-on examples along the way, we take the Shake applications in two directions: first an entirely QML-based implementation to show you how easy it is to write user interfaces using QML, and one that uses a QML interface with the C++ worker thread, XML parsing, and model to show you how to connect C++ code to QML. When you're through with this chapter, you'll be in position to create your own Qt Quick prototypes and full-fledged applications.
Qt Quick takes a radically different approach to user interface development than you've seen previously in C++ with Qt. More like HTML than C++, Qt Quick uses QML, a JavaScript-like language to define your user interface. QML is a declarative language—instead of writing imperative statements that do things, you write declarations of your user interface objects. While at the top level both environments are inherently object-oriented, how you work at the level of individual statements is very different. In C++ with Qt, we might draw a new rectangle using pseudocode like this:
QRect rect(0, 0, 32, 32); QPainter painter; painter.setBrush(QBrush(Qt::red)); painter.drawRect(rect);
In QML, we'd simply write:
import QtQuick 1.0 Rectangle { height:200 width: 200 color: "red" }
The QML example consists of a single object, of type Rectangle
. This specific rectangle overrides three of Rectangle
's default properties: height
, width
, and color
. The height
and width
properties are each set to the integer value 32
, and color
is set to the string "red."
Under the hood, the Qt Declarative module includes both a parser for QML and a renderer that renders QML to the screen.
QML can contain scripts, too—here's a button that changes its label to "Hello World" when it's clicked:
import QtQuick 1.0 Item { width: 200 height: 100 Text { id: label text: "Click Me" color: "black" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter font { family: "Helvetica"; pixelSize: 12; bold: true } } MouseArea { anchors.fill: parent onClicked: { label.text = "Hello World" } } }
Here, we have a QML Item
, the base element for all visible items in QML. It contains a Text
object and a MouseArea
that spans the entire size of the Item
. We control the layout of the Text
object and MouseArea
using the anchors
property, which indicates first that the text should be centered horizontally and vertically, and second that the MouseArea
should fill its parent. The Text
object is simply a label, with the initial text "Click Me"
in black Helvetica bold font. The MouseArea
contains a single bit of script that sets the object whose id
is label—the Text
item—to the string "Hello World"
You've already seen two examples of QML; now let's examine the nuts-and-bolts of the language.
As you've seen, QML is declarative. Instead of saying how, you simply say what. With syntax based on JavaScript, QML gives you a concise syntax to specify a tree of objects with properties. Properties may be references to other objects, strings, or numbers, making it easy to edit QML using your favorite text editor—or by using Qt Creator's excellent support for the language. Let's look at the first Rectangle
example again:
import QtQuick 1.0 Rectangle { height:200 width: 200 color: "red" }
The first line simply instructs the QML interpreter to include the definitions provided by QtQuick 1.0; you can provide your own QML files to import as well, or import JavaScript to provide better separation between your user interface and business logic.
This QML defines a single object, a Rectangle
. All QML objects are specified first by their type and then the properties of the object as name-value pairs separated by a single colon. Type names are capitalized, just like class names in C++. Here, we've written the properties one at a time, but we can put them on the same line and separate them with a semicolon, like this:
import QtQuick 1.0 Rectangle { height:200; width: 200; color: "red" }
Which you use is mostly a matter of personal preference for readability; we find that closely related properties requiring little explanation—say, the dimensions of an object—can be snuggled together on a single line. More important definitions, or those that require additional thought, should probably be placed on their own line and include a comment, like this:
import QtQuick 1.0 Rectangle { height:200; width: 200 // Required by Sandy's UX documentation as of 5 November. color: "red" }
Comments are written with standard C++ and JavaScript syntax, using either /*
and */
for a block comment, or //
to indicate that everything that follows until the beginning of the next line is a comment.
Values assigned to properties can be computed, too, using JavaScript syntax. For example, to create a rectangle whose width is twice its height, I might write:
import QtQuick 1.0 Rectangle { id: myRectangle height: 200 width: myRectangle.height * 2 color: "red" }
Here, the expression includes a reference to the rectangle itself, now named using its id
property. You can refer to other objects by their ID, too. The namespace is a tree identical to the objects you define, and a path to a specific object is simply the IDs of the objects along the path separated by periods. Of course, when referencing another property of the same object, you could just write width: height * 2
.
A powerful feature of QML is that when you refer to another object in an expression like this, QML creates a binding: if the value of the referent changes, the QML runtime automatically recomputes the expression, updating the visual appearance if necessary. If later in our QML expression we change the value of myRectangle.height
to 64
, the Rectangle
object will automatically change its width to 128
and re-draw, changing the appearance of the UI.
Properties are strongly typed; a property of one type may not be assigned a value of a different type. The QML type system includes the following basic types:
The action
type, which has the properties of a QAction
(see Chapter 5) instance.
The bool
type, which may be true
or false
.
The color
type, which is a standard color name in quotes.
A date
, in the format YYYY-MM-DD
.
An enumeration
, which can be any one of a set of named values.
A font
, which encapsulates the properties of a QFont
.
An int
, representing an integer.
A list
, consisting of a list of objects.
A point
, with attributes for its x
and y
coordinates.
A real
, representing a real number.
A rect
, bearing attributes for its x
, y
, width
, and height
attributes.
A size
, with attributes for width
and height
.
A string
, which is a free-form collection of characters between quotes.
A time
, specified as HH:MM:SS
.
A url
, which is a string that corresponds to the standard Uniform Resource Locator syntax.
A vector3d
, consisting of x
, y
, and z
attributes.
You can introduce properties to your own object using the property declaration with a type, like this:
import QtQuick 1.0 Rectangle { id: window property bool loading: feedModel.status == XmlListModel.Loading ... }
Here, we define the new property loading
, whose value is dynamically computed based on the status
property of another object.
Finally, it's worth noting that QML supports lists, collections of objects indicated between square brackets, like this:
Item { transitions: [ Transition {...}, Transition {...} ] }
Here, the transitions
property consists of two Transition
objects, each with their own (here elided) properties.
Many Qt objects emit signals, and QML objects are no exception. You've already encountered one, MouseArea's pressed
signal:
MouseArea { onPressed: { label.text = "Hello World" } }
All signal handlers begin with on
, and the remainder of a signal handler's name is the name of the signal; hence, onPressed
is the signal handler for the MouseArea
's pressed
signal.
Some signals include an optional parameter, which is given a name and accessed as a variable in the script for the handler. For example, the onPressed
signal handler has a mouse
parameter, which you're free to use to determine characteristics of the mouse press, like this:
MouseArea { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: if (mouse.button == Qt.RightButton) console.log("Right mouse button pressed") else if (mouse.button == Qt.LeftButton) console.log("Left mouse button pressed"); }
In this script you also see the console
object used; like the JavaScript console object, you can use its log method to log strings to the console for print-style debugging.
Nokia is working on a mixed-mode debugger for QML and C++ that will let you place breakpoints and inspect properties in both QML and C++. Until it's available, console logging is your best bet for debugging. Console log output appears on the application's standard output, so you can view it in the Application Output pane of Qt Creator or on the command line where you commenced execution of your application.
Speaking of JavaScript, you can import JavaScript into your QML, too. For example, in writing a game, we might want to encapsulate all of our game logic in a single file gamelogic.js. To include this file in our QML, we'd simply use an import directive at the top of the file:
import "gamelogic.js" as Gamelogic
This creates a top-level object named Gamelogic
that has properties and methods for each of the fields and functions defined in the file. For example, if our gamelogic.js file defines a method startGame
, we might create a start button in QML that begins the game with a declaration such as:
import QtQuick 1.0 import "gamelogic.js" as Gamelogic Item { Id: start width: 60 height: 32 Text { id: startLabel text: "Start" color: "black" font { family: "Helvetica"; pixelSize: 12; bold: true } } MouseArea { onClicked: { Gamelogic.startGame() } } }
You can also do the reverse. You can access properties of any QML object in JavaScript by referencing it as an object by its ID. For example, localization code written in JavaScript to set the text of the start button in gamelogic.js might read:
function localizeToEo() { ... startLabel.text = "Starti " // "Start" in Esperanto ... }
With everything being declarations, you might wonder how dynamic behavior like animation gets represented in QML. While you can implement dynamic behavior in scripts, you can also provide animations across properties using the animation-on-property syntax, like this:
import QtQuick 1.0 Rectangle { width: 64; height: 64 color: "blue" PropertyAnimation on x { from: 0; to: 64; duration: 1000; loops: Animation.Infinite } PropertyAnimation on y { from: 0; to: 64; duration: 1000; loops: Animation.Infinite } }
This creates a blue rectangle that moves from the origin of the canvas to the position (64, 64), over a second.
There are other animation types that follow the same idea, transitioning from one value to another. For example, ColorAnimation
animates changes in color values using QML's color
type over Qt RGBA values, while RotationAnimation
animates on the rotation of an object around its origin in degrees.
Sometimes you want to link a default animation to when a property changes; for example, you may want the rectangle to follow the mouse and animate to where the mouse is clicked. You can do this by adding Behavior
elements and adding a MouseArea
, like this:
import QtQuick 1.0 Item { width: 400; height: 400 Rectangle { id: rect width: 64; height: 64 color: "blue" Behavior on x { PropertyAnimation { duration: 500 } } Behavior on y { PropertyAnimation { duration: 500 } } } MouseArea { anchors.fill: parent onClicked: { rect.x = mouse.x; rect.y = mouse.y } } }
Here, the Behavior
declarations indicate that when x
or y
changes, the value should be animated over 500 milliseconds. We'll have more to say about anchors later, in the section "Creating the User Interface," later in this chapter.
Animations can be eased, that is, varied over time, according to one of various mathematical curves specified by the easing's type. For example, an animation may accelerate from its start, reach a maximum speed, then slow down to finally stop at its destination. The Easing
property of animations has a number of attributes that control how the value should be varied. Its attributes include:
type
, indicating the mathematical function that the values follow as the animation is computed.
amplitude
, indicating a relative scale for the easing.
overshoot
, indicating how far past the final bound the animation should occur before returning to the final bound.
period
, indicating the degree of repetition between the overshoot value and the final value for some easing curves.
Qt defines a large number of easing curves, including linear, quadratic, cubic, and sinusoidal curves. We might want to add a bit of bounce to our rectangle animation by changing the PropertyAnimation
s like this:
Behavior on x { PropertyAnimation { duration: 500 easing.type: Easing.InOutElastic easing.amplitude: 2.0 easing.period: 1.5 } } Behavior on y { PropertyAnimation { duration: 500 easing.type: Easing.InOutElastic easing.amplitude: 2.0 easing.period: 1.5 } }
Qt Quick elements can be broadly divided into two classes: things that are visible and things that aren't. Visible elements inherit from Item
, and include the following:
BorderImage
, an image broken into nine tiles and can be used, for example, to create a resizable button that selectively scales only the middle area to retain an undistorted border.
Image
, an element that displays an image from a specific source.
ListView
, which provides a list of items provided by a model.
Loader
, a region that loads its QML from its source
attribute (specified as a URL).
Repeater
, which lets you repeat an item-based component using content from a model.
Text
, a region that displays formatted text.
TextEdit
and TextInput
, regions that permit the entry of multiple or single lines of text, respectively.
WebView
, which allows you to add web content to a Qt Quick view.
Each of these items can be created just as you saw us create Rectangle
objects in the previous sections. Several items can coexist in a single layout; the QML for a user interface for a web browser in QML with a URL bar might look something like this:
import QtQuick 1.0 Rectangle { id: window width: 800 height: 480 TextInput { id: url anchors.left: window.left anchors.right: go.right anchors.top: window.top text: "http://qt.nokia.com/" } Rectangle { id: go anchors.left: url.right anchors.right: window.right anchors.top: window.top anchors.bottom: url.bottom Image { source: "go.svg" } } WebView { id: content anchors.left: window.left anchors.right: window.right anchors.top: url.bottom anchors.bottom: window.bottom source: "http://qt.nokia.com/" } }
Here we've placed several items, using their anchor properties. Other visible items control the layout of their children and can be used to provide other means of item positioning, including:
Column
, a region that positions its child items so they are vertically aligned.
Flow
, a region that arranges its children side by side, wrapping as necessary,
Grid
, a region that positions its child objects in a grid.
PathView
is a cousin to Repeater
, and lays out its model-provided items along a path.
Row
, a region that arranges its children horizontally,
We might modify the layout in the previous QML to better encapsulate the URL navigation line and "Go" button by placing them in a row, like this:
... Row { id: navigation anchors.left: window.left anchors.right: window.right anchors.top: window.top TextInput { id: url text: "http://qt.nokia.com/" } Rectangle { id: go anchors.right: navigation.right width: 32 Image { source: "go.svg" } } }
Finally, some visible items don't actually draw anything, but instead accept user events for processing:
Flickable
, an item that appears to rotate around an axis as if it's being flipped over.
GestureArea
, used to enable simple gesture handling, such as panning, pinching, swiping, tapping, and so forth.
MouseArea
, a region that enables simple mouse event handling.
Each of these has signal handlers; for example, MouseArea
has them for common mouse events including press, release, entry, and exit, while GestureArea
has signal handlers for tap, tap-and-hold, pan, pinch, and swipe gestures.
Because changing the position, orientation, and scale of items is something you often want to do in user interfaces, Qt Quick defines the Translate
, Rotation
and Scale
elements (subclasses of the Transform
element), which you can assign to the transform
property of a visible item. For example, the following specifies a rectangle rotated around its center by 45 degrees:
Rectangle { width: 100; height: 100 color: "blue" transform: Rotation { origin.x: 50; origin.y: 50; angle: 45} }
Note that when specifying a transform, the origin is relative to the object's position, not the center. In the previous example, the point (50, 50) is at the object's center, not offset to the lower–right-hand corner of the object.
Some visible elements, like the ListView
, need a model of one or more items from which to draw their content. Models include the ListModel
, a list of ListItem
items, as well as the more flexible XmlListModel
element, which draws its list items from an XML document using XPath expressions. (We use the XmlListModel
element in the next section to represent the list of earthquakes from the USGS.)
A full list of the supported Qt Quick elements is available at http://doc.qt.nokia.com/qdeclarativeelements.html
.
Qt Quick is undergoing heavy development and extension as we write this (Qt 4.7 has just been released), and this quick survey of the elements available to Qt Quick is probably already out of date. To keep with the latest information about Qt Quick, see http://doc.qt.nokia.com/qtquick.html
.
It's time to build a larger example: our Shake demonstration application, this time entirely in QML. In this section we'll build on the basics you've already learned, and introduce the powerful XmlListModel
Qt Quick element that lets you fetch RSS feeds and parse out data from them using only XPath queries. Figure 6-1 shows our sample UI.
Before we begin, it's worth noting that the UI is completely different than that of a standard Qt application—here, pictured in the Qt Simulator with an N900 skin. If you're looking to create an application that closely resembles native applications with a look and feel identical to the native experience, QML may not be your first choice, because its presentation is a trifle more basic. As we write this, it doesn't have the necessary UI primitives or styles to match the native MeeGo or Symbian UI (this will soon be introduced by the Qt Quick Components). On the other hand, if you want to establish your own look and feel, or if you're writing a game or other application where it's okay to deviate from the native device UI, QML is an excellent choice.
Our application returns to the split-screen UI you first saw in the prototype in Chapter 4, with a few refinements. First, the event list on the left has a shaded background, and doesn't occupy precisely half the screen. Moreover, list items are formatted neatly, with an event's magnitude and region on separate lines. The basic functionality still remains, although for the brevity of this example, we don't include geolocation integration as we demonstrated in Chapter 6. It's easy to add through the Qt Mobility QML plug-ins (available since Qt Mobility 1.1), though, or you can do it through C++, which we will describe in the section "Mixing C++ with QML" later in the chapter.
Before we begin discussing the main user interface, you'll want to create a new QML project. To do this, launch Qt Creator and select "Create Project..." and then choose "Qt Quick UI" from the New Project dialog, as you see in Figure 6-2.
If all you want to do is run the application, you can do so using the qmlviewer
command, which takes as its argument the name of a QML file to execute, like this:
qmlviewer main.qml
This works only on your development workstation; to display QML on the device, you'll use the wizard provided in Qt Creator (for versions after Qt Creator 2.1 beta), as we show you in the section "Displaying QML within a C++ Application" later in this chapter.
The user interface consists of two pieces: the list view, a ListView
element, and the item view, a Text
element. Listing 6-1 shows main.qml
, the QML that defines the entire user interface (and the application's data model, as you'll see as we go along).
Example 6.1. The main UI for the QML version of Shake
import QtQuick 1.0 Rectangle { property bool loading: feedModel.status == XmlListModel.Loading id: window width: 800 height: 480 Rectangle { id: listView
anchors.left: window.left anchors.top: window.top; width: window.width/3 height: window.height color: "#efefef" ListView { id: events property string text: window.loading ? "Loading data... please wait" : "<b><center>" + feedModel.get(0).title.replace(","," ").replace(","," ") + "</center></b><br/>" + feedModel.get(0).summary focus: true anchors.fill: parent model: feedModel delegate: QuakeListDelegate {} highlight: Rectangle { color: "steelblue" } highlightMoveSpeed: 9999999 } } Text { id: itemView anchors.left: listView.right anchors.top: window.top; width: window.width - listView.width height: window.height wrapMode: Text.Wrap text: events.text } XmlListModel { id: feedModel source: "http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml" namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';" query: "/feed/entry" XmlRole { name: "title"; query: "title/string()" } XmlRole { name: "summary"; query: "summary/string()" } } }
The top level of the UI is a single rectangle, sized to fit the MeeGo device screen at 800×480 characters with the ID window
. It has a single property, a Boolean value loading
, which is true
while the XmlListModel
is loading the XML from the USGS.
Inside the main rectangle is a smaller rectangle containing a ListView
, and the Text
element that shows the details of a single earthquake event. We place the ListView
in its own rectangle and position this and the Text
element to be adjacent to each other, spanning the entire height of the containing rectangle using their anchor
properties. They permit you to anchor item borders by referring to the borders of adjacent items.
The ListView
itself has a property, the text to show for the current element. When the application starts, it simply shows a canned string indicating that the application is loading data; because QML maintains bindings between all the properties, as the list model's status
changes, so does the window's loading
property, and so does the text
property of the ListView
. The JavaScript expression for the text
property creates a bit of HTML to present a rich-text version of the earthquake data that reiterates the quake's magnitude, location, and detail data the USGS provides.
A ListView
doesn't draw its own items; instead, it relies on a delegate, a separate item that draws contents once for each item in the ListView
's model. Listing 6-2 shows the QuakeListDelegate.qml
(put it into the same directory as your main.qml
file), our item for displaying a single item of the list.
Example 6.2. The delegate responsible for drawing a single list item
import QtQuick 1.0 Item { id: delegate width: delegate.ListView.view.width; height: 60 Text { text: title.replace(","," ").replace(","," ") color: delegate.ListView.isCurrentItem ? "white" : "black" font { family: "Helvetica"; pixelSize: 16; bold: true } anchors { left: parent.left; leftMargin: 15 verticalCenter: parent.verticalCenter } } Rectangle { width: delegate.width; height: 1; color: "#cccccc" anchors.bottom: delegate.bottom visible: delegate.ListView.isCurrentItem ? false : true } Rectangle { width: delegate.width; height: 1; color: "white" visible: delegate.ListView.isCurrentItem ? false : true } MouseArea { anchors.fill: delegate onClicked: { delegate.ListView.view.currentIndex = index delegate.ListView.view.text = "<b><center>" + title.replace(","," ").replace(","," ") + "</center></b><br/>" + summary } } }
The delegate has a single Text
item that displays the title of an earthquake report as a series of three lines. It's in black for all items but the currently focused item, which is white and drawn over the highlight rectangle at the end of the listing. After the Text
item is a dividing line one pixel tall. It provides separation between this and subsequent items. A MouseArea
in the item filling the entire region handles clicks by setting the ListView
's text property to the full text description of the event.
When the XmlListModel
finishes loading or you click on an item, the QML runtime updates the ListView
's text property. The itemView
, a single Text
element, displays this by setting its text
property to shadow the text
property of the event list itself.
The XmlListModel
is a specific list model that handles both the fetching of an XML feed and parsing the feed into roles defined by XPath queries. The XmlListModel
does the work of the WorkerThread
in the previous chapters' examples, fetching the RSS feed and parsing it to provide title and summary attributes from the source XML available from the USGS. The fetch begins when the XmlListModel
is created, and the status
is updated after the load completes.
The ListView
draws each item using the delegate you saw in Listing 6-2, obtaining the fields in each list item using the title
and summary
attributes extracted from a specific feed entry based on the entry's index. You can also fetch a specific XmlListModel's
item using the get
method and passing an index, as we do when we draw the 0th element after the loading completes.
The XmlListModel
highlights a key feature of QML we've only hinted at: content can be fetched not just from the local device, but also over the Internet. Any element with a source
property can present data from any URL, letting you freely mix local and remote resources in your Qt Quick applications. In fact, you can do this with whole Qt Quick items. The Loader
element has a source
property and at runtime replaces itself with the contents at the URL of its source
element. That allows a Qt Quick application to load other QML from the Web.
While QML is arguably a powerful environment, there are still uses for C++ in Qt development. For example, interfacing with platform enablers like QtDBus on MeeGo still requires some C++ work, even if your UI is entirely written in QML. Fortunately, it's easy to bind QML with QObject
subclasses written in C++ using Qt's meta-object features, which we touched on in Chapter 4.
As you'll see in the section "Mingling QObjects with QML" later in this chapter, any QObject
can be added to QML's object tree, exposing Qt properties as QML properties and slots as methods.
Other times you may just want to introduce a QML interface as a visible component of your application, either as all or part of your UI. The Qt Declarative library, on which Qt Quick is based, provides a collection of classes that let you do just this. The most obvious example is that when you want to ship a QML application on a mobile device, you'll need to create a QDeclarativeView
in which to render your QML application.
Displaying QML in a Qt application is easy. Simply create an instance of QDeclarativeView
and add it to your widget hierarchy. Then, set its source to the URL of the entry point to your QML application. For example, a player application for the previous section's QML is as simple as what you see in Listing 6-3.
Example 6.3. Rendering QML in an application's main window
#include <QApplication> #include <QMainWindow> #include <QDeclarativeView> int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow window(); QDeclarativeView* view = new QDeclarativeView(); window.setCentralWidget(view); view->setSource(QUrl::fromLocalFile ("main.qml")); window.showMaximized(); return app.exec(); }
QDeclarativeView
acts as a QWidget
, so you can just set it as the central widget of the application's main window and give it some QML to render. In fact, if you choose a Qt Quick application from Qt Creator's "New Project," the resulting project includes an entry point (main function
) whose body is very similar to what you see in Listing 6-3.
Through the rootContext
method, the QDeclarativeView
exposes a QDeclarativeContext
, which provides an interface to QML's context within the QML engine that the QDeclarativeView
uses to render its content. Using the QDeclarativeContent
, you inject new QObject
values to the context tree, providing the name that the QML content will use to access the QObject
. When you do this, the QObject
's properties become QML properties of the object in the QML context, and slots become methods that QML can invoke on the object.
As an example, let's imagine we wanted to reuse the model and network code from the previous chapter's example with the QML user interface we presented in Listing 6-1 and 6-2. In practice, this probably isn't a good idea, because the XmlListModel
does what we need and requires less code, but this example will show you how you can introduce a model from C++ to QML and use it with QML's ListView
.
The only change we need to make to Listing 6-1 is to remove the XmlListModel
from the QML entirely; we'll replace it with our QuakeListModel
using the code you see in Listing 6-4.
Example 6.4. Introducing a QObject into the QML context
static const char* kUrl = "http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"; int main(int argc, char *argv[]) { qRegisterMetaType<QModelIndex>("QModelIndex"); QApplication app(argc, argv); QMainWindow window(); QuakeListModel* model = new QuakeListModel(&window); WorkerThread* worker = new WorkerThread(&window, *model); worker->fetch(kUrl); QDeclarativeView* view = new QDeclarativeView(); // The only thing we show is the declarative view. window.setCentralWidget(view); window.showMaximized(); view->rootContext()->setContextProperty("feedModel", model); view->setSource(QUrl::fromLocalFile("main.qml")); return app.exec(); }
Listing 6-4 introduces a QtDeclarativeView
to the main window, but only after it creates an instance of the QuakeListModel
and WorkerThread
to fetch the earthquake feed from the USGS server. While the thread is working, the code inserts the QuakeListModel
instance into the declarative view's context using the line of code
view->rootContext()->setContextProperty("feedModel", model);
This assigns the model to the QML entity feedModel
.
The QuakeListModel
we presented previously doesn't provide status notifications as the worker thread does its work; we need to add a Qt property that indicates the feed status the worker thread will update as it fetches and parses the data. Listing 6-5 shows the modified interface to the QuakeListModel
.
Example 6.5. Adding the status property to the QuakeListModel
.
class QuakeListModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(int status READ status WRITE setStatus NOTIFY statusChanged) public: QuakeListModel(QObject* parent = 0); enum { ... // Role enum elided for brevity }; bool setData(int row, const QuakeEvent& value);
int status(); void setStatus(int status); signals: void statusChanged(); ... // remainder of class follows };
The status
property uses the status
and setStatus
methods as its implementation, and setStatus
must also emit statusChanged
to provide QML's binding something to hook on to while watching for status changes. These methods (Listing 6-6) are trivial.
Example 6.6. Changes to the QuakeListModel
implementation
int QuakeListModel::status() { return mStatus; } void QuakeListModel::setStatus(int status) { if (status != mStatus) { mStatus = status; emit statusChanged(); } } QuakeListModel::QuakeListModel(QObject* parent) : QStandardItemModel(parent) { QHash<int, QByteArray> roles; roles[Qt::DisplayRole] = "title"; roles[QuakeListModel::Description] = "summary"; setRoleNames(roles); }
Listing 6-6 also shows a key change to the QuakeListModel
's notion of its roles; for each named QML role, such as title
, we need to provide the corresponding Qt::Role
enumeration value. The QML context uses these when resolving the attributes referenced in specific list items while drawing the delegate for the list view. We do this when we construct the model by creating a QHash
that links the QML attribute names to the Qt::Role
enumeration values.
Next, the worker thread needs to update the model's status
property throughout the HTTP transaction; for example, the beginning of the fetch
method needs to look like this:
void WorkerThread::fetch(const QString& url) { // Don't try to re-start if we're running if (isRunning()) { this->cancel(); } mEventModel.setStatus(2); // XmlListModel.loading // Configure the access point, do the fetch, etc. // See Chapter 4 for details. ... }
The model's status also needs to be set at the end of run
to signal the end of the transaction for success or error conditions, of course.
In this chapter, we've shown you how to use Qt Quick, Nokia's declarative environment for creating user interfaces using QML, JavaScript, and C++. By using QML entities like Rectangle
, MouseArea
, Item
, Text
, and ListView
, you learned how to specify user interfaces by their contents, instead of C++'s imperative declarations in method definitions. You saw how QML uses properties and runtime binding to share data between user interface objects, automatically updating each object in its context tree as necessary. The process uses JavaScript to let you create programmatic linkages between one object's properties and another. We showed how that extended to both the JavaScript and C++ runtimes, letting you add JavaScript and C++ objects to your QML-based application. We also showed how to display QML content in a C++ application.
In the next chapter, we switch gears, and discuss Nokia's support for Web technologies, including HTML5, which lets you deploy existing or new web-based applications on Nokia's products. Take a walk to clear your head, and we'll be ready when you return!