QT QuickAppDevIntro
QT QuickAppDevIntro
Release 1.0
Nokia, Qt Learning
CONTENTS
Introduction 1.1 Who should read this tutorial and why 1.2 The journey is the target . . . . . . . 1.3 Downloads . . . . . . . . . . . . . . 1.4 Help us help you . . . . . . . . . . . 1.5 Related material . . . . . . . . . . . 1.6 License . . . . . . . . . . . . . . . . Setting up your work environment 2.1 Installing the tools . . . . . . . . 2.2 Creating Qt Quick applications . 2.3 Qt Quick UI project type . . . . . 2.4 Qt Quick Application project type 2.5 Tracing what is going on . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
1 1 1 2 3 3 4 5 5 5 6 7 9 11 11 12 12 14 17 17 19 20 23 27 29 29 30 31 32 33 37 i
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
Qt Quick core principles for application development 3.1 Qt Quick compared to classical Qt . . . . . . . . . 3.2 Declarative vs imperative programming . . . . . . 3.3 Four cornerstones . . . . . . . . . . . . . . . . . 3.4 Moving from a concept to a real application . . . . Elements as building blocks 4.1 Composing a basic UI with nested elements . 4.2 Ordering elements on the screen . . . . . . . 4.3 Arranging application elements on the screen 4.4 Properties . . . . . . . . . . . . . . . . . . . 4.5 Other visual composition elements . . . . . Loading and Displaying Content 5.1 Accessing and loading content 5.2 Basic image parameters . . . 5.3 Basic text parameters . . . . . 5.4 Get ready for translation . . . 5.5 Static clock application code . Using JavaScript
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
JavaScript is not JavaScript . . . . . . Getting to know more about JavaScript Adding logic to make the clock tick . . Importing JavaScript les . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
37 38 38 42
Acquire and Visualize Data 45 7.1 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 7.2 Repeater and views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Components and Modules 8.1 Creating components and collecting modules 8.2 Dening interfaces and default behavior . . . 8.3 Handling scope . . . . . . . . . . . . . . . . 8.4 A new application made of two . . . . . . . 8.5 Further readings . . . . . . . . . . . . . . . Interactive UI with Multiple Top-Level Windows 9.1 A button . . . . . . . . . . . . . . . . . . . 9.2 A simple dialog . . . . . . . . . . . . . . . . 9.3 A checkbox . . . . . . . . . . . . . . . . . . 9.4 Handling keyboard input and navigation . . . 55 55 56 57 60 64 65 65 68 71 72 79 79 83 86 87
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
10 UI dynamics and dynamic UI 10.1 Using states . . . . . . . . . . . 10.2 Adding Animations . . . . . . 10.3 Supporting the landscape mode 10.4 Finalizing the main item . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
11 Doing More, Learning More 93 11.1 Porting to Qt5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 11.2 Porting to a mobile device . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 11.3 Enhancements and new features . . . . . . . . . . . . . . . . . . . . . . . . . 94 12 Lesson Learned and Further Reading 13 Annex. JavaScript Language Overview 13.1 Introduction . . . . . . . . . . . . 13.2 The Type System . . . . . . . . . . 13.3 Expressions . . . . . . . . . . . . . 13.4 Branching . . . . . . . . . . . . . 13.5 Repetitions and Iterators . . . . . . 13.6 Labeled Loops, Break and Continue 13.7 Objects and Functions . . . . . . . 13.8 Prototype based Inheritance . . . . 13.9 Scopes, Closures and Encapsulation 13.10 Namespaces . . . . . . . . . . . . 13.11 Common Methods . . . . . . . . . 13.12 Exceptions . . . . . . . . . . . . . 13.13 Resources . . . . . . . . . . . . . . ii 97 99 99 100 101 101 101 102 102 103 106 107 108 108 109
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
CHAPTER
ONE
INTRODUCTION
1.1 Who should read this tutorial and why
This tutorial explains the basics of developing applications with Qt Quick by walking the reader through the code of complete applications. The tutorial extends the standard Qt Quick documentation provided in Qt and relies on it as a source of more detailed information about Qt Quick APIs and fundamental concepts. This tutorial is thought to precede in-depth content as a prerequisite for those of you who are new to Qt Quick. Even though it starts with the basics, you still have to be familiar with principles of programming. Some basic knowledge of JavaScript is highly recommended. After completing this tutorial, you should be able to write your own Qt Quick applications and start discovering more by reading advanced materials or analyzing the code of complex applications.
Introduction to Application Development with Qt Quick, Release 1.0 Right after Hello World, we will make a simple application that shows the current time and date in the nostalgic format of an old LED clock. The next step will be another application which reads, parses and displays weather forecasts from the Internet. Later we will combine these two applications as components in a larger application: a clock with integrated weather forecasting. Please note that some advanced aspects are left out of scope of this tutorial to keep it as short as possible. We will mention some of them in the last chapter. One piece of advice that we have for you is to remember the old developer saying: Documentation is your friend1 ...
1.3 Downloads
A .zip le that contains the source code of each chapter is provided so you can compare to your own code: Source code2 The guide is available in the following formats: PDF3 ePub4 Qt Help5 . What is ePub? ePub is a content container for ebook readers. Further details can be found in this Wikipedia articlea .
a
http://en.wikipedia.org/wiki/EPUB#Software_reading_systems
Installing additional Qt Help les in Qt Assistant and Qt Creator In Qt Assistant, go to the Preferences Dialoga , in the Documentation tab (in a collapsible menu for Mac users), click on the Add button in order to add this guide in .qch format. Do the same in Qt Creator under the Options dialog. In the Help section, you can add this guide under the Documentation tab.
a
http://qt-project.org/doc/qt-4.8/assistant-details.html#preferences-dialog
The provided code has been developed with Qt 4.8.x and Qt Quick 1.1 running on Linux and Windows. Please refer to the section Porting to Qt5 (page 93) for details how to get examples applications running with Qt5.
1 2
Chapter 1. Introduction
Introduction to Application Development with Qt Quick, Release 1.0 Demos and Examples14 Example Applications for Qt Quick15
1.6 License
Copyright (c) 2011-2012, Nokia Corporation and/or its subsidiary(-ies). All rights reserved. This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-ShareAlike 2.5. The full license document is available from http://creativecommons.org/licenses/bysa/2.5/legalcode . Nokia, the Nokia logo, Qt, and the Qt logo are trademarks of Nokia Corporation and/or its subsidiaries in Finland and other countries. All other company, product, or service names may be trademarks or service marks of others and are the property of their respective owners. The use of the word partner does not imply a partnership relationship between Nokia or any other company.
Whats Next?
The next chapter covers how to set up the development environment and run your rst Qt Quick application.
14 15
http://qt-project.org/wiki/Category:Learning::Demos_and_Examples http://qt-project.org/wiki/qml_examples_directory
Chapter 1. Introduction
CHAPTER
TWO
http://qt.nokia.com/products/qt-sdk/ http://qt-project.org/wiki/Category:Tools::QtCreator
You can execute it by pressing CTRL-R or selecting Build Run in QtCreator. This will show the following on the screen:
But wait! There is no compilation step! How does the application run?
Qt Quick is script-based, just like Python or Perl. A script needs to be interpreted and executed by an engine. This engine in Qt Quick is called Qt Declarative UI Runtime3 . Qt Quick applications can use this engine in two different ways: 1. passing a qml le as command line option of the engine application called qmlviewer4 2. integrate Qt Declarative UI Runtime5 in own C++ code and load qml les Our hello_qt_quick_ui uses the rst way. The qmlviewer6 application located in the bin folder of a Qt installation. It contains code which uses Qt Declarative UI Runtime7 to load a qml le specied in the command line. A project of the Qt Quick UI type is setup in such a way that qmlviewer8 application is registered in the Run conguration in QtCreator as an application which should be launched to load the main qml le when you press CTRL-R or select Build Run. No compilation step is required. The qmlviewer9 application also provides debugging interfaces and many other cool goodies. Check its documentation page or call qmlviewer --help to see what is available. This project type and the way it is integrated in Qt Creator is very simple and handy for discovering the functionality of Qt Quick. We will use it most of the time. Nevertheless, in the next section we will learn about the Qt Quick Application project type to understand how you can use Qt Quick in a standard Qt application developed in C++. Before we start with it, a short remark about the name Qt Quick UI given to a project type with qml les only. This underlines the major purpose of Qt Quick to serve a scrip-base programming environment for application UI. Complex application logic and heavy processing should stay on the C++ side and expose its APIs to Qt Quick.
main.cpp - the main le of your application starting your own qmlviewer implemented in the QmlApplicationViewer C++ class qml- a folder where the Hello World qml le resides. Add other qml les here qmlapplicationviewer - a folder with implementation of the QmlApplicationViewer C++ class which initializes and starts Qt Declarative UI Runtime10 with the main qml le Your application now has its own qmlviewer-like module along with the same Hello World qml le. This is a Qt C++ application which provides the basic functionality of qmlviewer plus some additional code for integration on non-desktop platforms . If you run this project, Qt SDK will rst compile and build you application line any other C++ application and then start it. QmlApplicationViewer will load and execute Qt Quick code the same way as qmlviewer:
Your new QmlApplicationViewer is another possible instance of Qt Declarative UI Runtime11 . This way of handling Qt Quick applications opens many interesting possibilities for integrating Qt Quick code into classical C++ applications and even making C++ code available as new items in Qt Quick. This is a more advanced technique beyond the scope of this tutorial. You can read more about this in Qt Declarative UI Runtime12 documentation as well as in the Extending QML Functionality using C++13 article in Qt documentation.
10 11
This code produces the following in the QML Viewer tab in Qt Creator:
I was "QDeclarativeRectangle(0x23705d0)"! Bye for now!
Whats Next?
By now you should have a working development environment and a simple project with a Hello World application which you can manipulate, run and check what happens. In next chapter, we will discuss core concepts of Qt Quick and take a look what is available to do more than a simple Hello World example.
14 15
http://doc.qt.nokia.com/qtcreator/creator-debugging-qml.html http://doc.qt.nokia.com/qtcreator/creator-debugging-helpers.html
10
CHAPTER
THREE
11
http://en.wikipedia.org/wiki/Declarative_programming http://en.wikipedia.org/wiki/Imperative_programming
12
} } }
Concerning point 1: Our application consists of three elements ordered in a tree alike structure. Rectangle is a root element of that tree. It contains a Text element and a MouseArea element. They are siblings and have Rectangle as a parent. Concerning point 2: We assign new values to the properties width and height of Rectangle to set its initial size. Concerning point 3: We bind the text content of the Text element to the dimensions of its parent element (Rectangle). The application does not show anything spectacular after its start:
but if you resize its window with the mouse the text will change and the console output will keep posting the current dimensions of Rectangle:
13
If it was just a traditional assignment the text would not change and keep its rst value. This is a very powerful technique which is used very intensively in any Qt Quick application. Concerning point 4: An on notication is generated each time a property change including changes of width and height. We add a handler for notication onHeightChanged. Each time dimensions change, the size of the Rectangle is printed on the console. It is also possible to send a signal when you need to notify other elements about something. Our simple code segment does not use signals, but we will talk about them later. Generally, the forth cornerstone is very similar to properties and signals-and-slots in classical Qt. Lets take a look on the concept of our application and analyze how we proceed implementing it with Qt Quick. We will take a closer look on these four cornerstones and learn about other important details.
gives three main components: a clock, weather forecast, and possibly settings. The clock and weather forecast will be shown on the same screen, whereas settings will pop-up and dismiss. The clock element will look something like this:
This just shows the current time and date which can be seen as elements on their own as well. A weather forecast usually displays information about the weather for the current day and the actual forecast section covering a few days in the future. This information is repeating for all days showing just weather conditions and temperatures. We plan to get the weather data from the Internet. This will make weather elements tightly linked to the weather data. We should keep this in mind when selecting Qt Quick elements to use. A root element containing all weather related elements can look something like this:
The clock and weather parts are quite independent and it makes sense to develop them separately and use them as components in the nal application:
15
This element can be developed separately as well. We just need to take care that there is a way to transfer the settings data from and into the application core. Additionally, we will probably need some basic UI features such as text input elds, check boxes and simple buttons. Visual appearance is important! We will spend some extra time exploring possibilities in Qt Quick and enhance our application.
Whats Next?
In the next chapter we will explore how to use Qt Quick elements to compose an UI.
16
CHAPTER
FOUR
17
Though the application is not any closer to being useful in real life, it would be interesting to further analyze what happens on the screen. One Rectangle4 is the root item. Two others contain a Text5 with different text. All items are rendered as a stack according their order in the tree: the root rectangle rst then the grey rectangle with its text the green rectangle comes on top and covers a part of the previous text the Text6 element contained in the green rectangle comes as the last one on top of all others.
4 5
18
We could add more elements, all of them will be added to this stack and drawn on the upper left corner which is the (0,0) position. This is the default rendering position, if you do not specify x and y properties. This default behavior would make in this example appear too overlapped on the screen. We add anchors.verticalCenter: parent.verticalCenter to move the last text a bit below the text in the gray rectangle. Anchoring allows positioning of elements relatively to each other. We will talk about this a bit later.
http://qt-project.org/doc/qt-4.8/qml-item.html#z-prop
19
} } }
If you noticed, the text elements are not clipped by the boundaries of their parents - rectangles. This is the default behavior for performance reasons. If needed, this can be changed by setting the clip property to true: clip: true. clip is a property of Item8 and available in all visual elements inheriting from it. If we set clip to true in our application it will look like this:
http://qt-project.org/doc/qt-4.8/qml-item.html
20
We already saw some use of anchoring in previous examples. Lets take a closer look on this and use it place elements so that they will start getting closer to a clock. We just take two Text9 elements and place them inside our root element. Anchoring uses so called anchoring lines which are provided as properties. When you anchor items, you just bind an anchoring line of one item to an anchoring line of another one. You basically stitch items to each other. Additionally, you can also set anchor margins. Margins are zero but default and dene a distance between anchored items. We place timeText and dateText centered inside of root and add margins of 10 pixels: (static_clock/static_clock.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Rectangle { id: root width: 80 height: 80 color: "lightgrey" Text { id: timeText anchors.top: root.top anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 10 text: "13:45" } Text { id: dateText anchors.bottom: root.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 10 text: "23.02.2012" } MouseArea { anchors.fill: root onClicked: { Qt.quit(); } } }
The top edge of timeText stitches to the top edge of the root in a distance of 10 pixels. dateText does the same at the bottom. If you run this application and resize the application window with the mouse, you will see that both text items keep their anchored positions at the edges. Just the space between them changes.
9
http://qt-project.org/doc/qt-4.8/qml-text.html
21
The item MouseArea10 lls the entire surface of root and even reacts on mouse clicks received over the both text items. We will take a look on processing of mouse events later. Anchoring is fairly simple, but you need to pay a bit more attention if you have many elements on the screen. Otherwise you might lose orientation of where your elements are located. A useful approach is to anchor to the dominant elements and keep anchoring in a hierarchical order. A detailed description of anchoring is available in this article11 in Qt documentation. Another approach to handle positioning of many elements which should be placed in a regular order is using Positioners12 . Positioners13 do all the ordering for you and have some additional features. Lets try migrate our application to use positioners. It will make the code shorter even with our two items. We now place both Text14 elements inside of Column15 positioner. In order to keep them apart from each other we dene 20 pixels spacing and anchor the Column16 in the center of root: (static_clock1/static_clock1.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Rectangle { id: root width: 80 height: 80 color: "lightgrey" Column { id: clockText anchors.centerIn: root spacing: 20 Text { id: timeText anchors.horizontalCenter: parent.horizontalCenter text: "13:45" } Text { id: dateText anchors.horizontalCenter: parent.horizontalCenter
10 11
22
If you now resize the application window, you will notice that its content stays centered, since we centered the positioner in root. Qt Quick provides other Positioner17 elements, such as Row18 , Grid19 or Flow20 . We will use some of them in the course of this guide.
4.4 Properties
In previous sections, we already used item properties by assigning or binding various values in order to change an item. Properties play an essential role and have a much broader use. In this section we will take a look at some other use cases and use the knowledge in our application. Lets take a look on this simple code segment:
Rectangle { id: myButton color: "white" }
The element myButton automatically contains all default properties of Rectangle21 , including all default values. Implementing a button, you might need to add some other properties on top of default ones, e.g. a border color or even a generic pen color which will be use to paint both the label text and the border color. Implementing other items, you might need to keep some values from the application logic. How to add new properties? You can dene a new property in the following syntax:
17 18
4.4. Properties
23
A new property for the border color in our Button would then look like this:
property string borderColor: "white"
We used the type string here. It is one of a range of types available, see this article22 about property types in Qt documentation. In some cases, it makes more sense to dene an alias for an existing property instead of dening a new one. This can be done using the following syntax:
[default] property alias <name>: <alias reference>
In fact, in the case of our button inheriting from Rectangle23 , we should dene the new property as a new alias since the Rectangle24 element already has a property dening the the color of the boarder:
Rectangle { id: myButton property alias borderColor: myButton.border.color ... }
The optional default keyword in the syntax outlined above are used to create default properties. They hold the child elements of an item. This is a more advanced use which is out of the scope of this guide. Please refer to this25 and this article26 in Qt documentation. As mentioned before a property change generates notication signals which can have a handler with some code to be executed upon that property change. Handlers are assigned to on<property_name>Changed properties which are automatically created for all items properties, including custom properties you have added. Lets make a small experiment with a rectangle to see this in action:
import QtQuick 1.1 Rectangle { width: 100; height: 100 onWidthChanged: console.log("Rectangle width: ", width) onHeightChanged: console.log("Rectangle height: ", height) }
The code above creates a window with an empty white rectangle. If you drag a corner to resize its desktop window, you will see something like this in the console:
Rectangle width: 640 Rectangle height: 460 Rectangle width: 100
22 23
24
height: 100 height: 101 height: 102 width: 104 height: 109 width: 105 height: 110 height: 111
The code of handler reacting upon changes can be much more sophisticated and use JavaScript. We will talk about using JavaScript in Qt Quick in the section Using JavaScript (page 37) Property binding is another fundamental aspect. It makes a property to follow changes of another property. This works across items, and is actually used across items most of the time. Binding occurs each time you use : and have a value on the right side which can change:
id1.text: id2.text width: 16*height/9 // text in the id1 follows changes of the text in id2 // this keeps an item being the 16:9 aspect ratio :-)
The latter is interesting since you can also do some calculations or even bind to a function returning a value. The following example combines what we just learned about properties in one small application: (property_binding/property_binding.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Rectangle { id: root property int adaptiveSize: root.width/10 width: 100 height: 100 Text { id: text1 anchors.centerIn: parent text: "initial text" onTextChanged: { console.log ("text 1 is changed: " + text1.text) } } Text { id: text2 anchors.top: text1.bottom anchors.horizontalCenter: text1.horizontalCenter text: text1.text font.pixelSize: adaptiveSize onTextChanged: { console.log ("text 2 is changed: " + text2.text)
4.4. Properties
25
} } MouseArea { anchors.fill: parent onClicked: { text1.text = "new text" console.log ("text 1 is now: " + text1.text) console.log ("text 2 is now: " + text2.text) } } }
Since the text2.text is bound to text1.text, the property change in text1 on a mouse click is automatically passed to text2:
The mouse click was received by the MouseArea27 element and processed by the JavaScript code in the onClicked handler. We will take a closer look at this in one of the following chapters. If you resize the window the pixel font size of the text in text2 is dynamically scaled to be 1/10 of the width of the root:
27
http://qt-project.org/doc/qt-4.8/qml-mousearea.qml
26
Later on, we will use properties to dene custom parameters in our application. These parameters will dene the appearance of our application as well as keep a few settings.
http://qt-project.org/doc/qt-4.8/qdeclarativeelements.html http://qt-project.org/wiki/Developer-Guides/
27
Whats Next?
In the next chapter we will learn how to load external content and create the required UI appearance.
28
CHAPTER
FIVE
29
Our new font, LED_REAL.TTF, is stored in the content folder, which is located in the same folder as our application. If for any reason it will not load, an error message will be posted on the console and the application will continue using a default font.
We need to change a few image properties. The image should cover the entire surface of the top-level element (its parent). In many cases, lling a background is done with small images which just design a pattern. Scaling them to the size of background would change the pattern, and the background would not be the same as planned. In these cases, you set fillMode to Tile as shown in the code above. Dealing with image sizes Most of the visual properties of an image can be changed via its parent (Itema ) properties. Other Imagesb properties help handle critical image aspect - its size. If you deal with large images, you should set the sourceSize property to a size which you really need, otherwise the image will load in its full size. Is worth to notice the the difference between paintedHeight and height and between paintedWidht and width. The painted property pairs describe the size of the area taken be the image when it is painted on the screen, whereas the another pair describes the loaded size of the image. Both can be different if the image is scaled. If you change the scale property inherited from Itemc , be aware of the impact of the fillMode property on the actual scaling result. The smooth property smoothens the edges of scaled images, but also incurs a performance cost. See Imaged documentation for more details.
a b
30
http://qt-project.org/doc/qt-4.8/declarative-text-fonts-fonts-qml-fonts-qml-availablefonts-qml.html
Other Text5 properties allow a broad variation of the visual appearance of a text. In our application, we will just use the color, style and size-related properties. The customized text element for displaying time now looks like this:
Text { id: timeText text: root.currentTime font.pixelSize: root.height*root.timeTextProportion font.family: ledFont.name font.bold: true color: root.textColor style: Text.Raised styleColor: "black" }
Our plan is to implement as much exibility as possible in the size and layout of all elements since we want to run our application on different screen sizes. This is why the code above binds the pixelSize of timeText to the height of the root element. This will scale the text whenever the height of root changes. There are ways to make it better, but the current version of Qt Quick unfortunately does not provide font metrics data in the font properties. If it were available, we could make the text size adapt to the width changes as well. In order to make the timeText element appear more attractive, we use some tricks for the visual appearance by setting style to Text.Raised and styleColor to "black". This detaches the text from the background so it seems like its hovering over it. The Text6 element also provides basic layout controls (e.g. you can set how the text should be wrapped via wrapMode). We will use this property later. The most important thing to note about text formatting is that Text7 supports rich text8 markup. This makes it possible to display one block of text in various formatting, colors, sizes etc. The appearance of our application is now less boring:
4 5
31
The full source code is listed in the last section of this chapter.
The application continues to run the same way as before using qsTr(). You need to use the Qt translation tool chain to see translation in action. See the QML Internationalization10 article for
9 10
http://qt-project.org/doc/qt-4.8/scripting.html#produce-translations http://qt-project.org/doc/qt-4.8/qdeclarativei18n.html
32
more details how to generate and integrate translation les. When translation les are available, there are three options for loading them in the current version of Qt Quick: by using the qmlviewer command line option -translate by placing them in the i18n sub-folder (see this example11 ) by modifying the default behavior of the qmlviewer and loading QML and translation les in your own way (this is beyond of scope for this guide) If your plan is to make a version of an application for right-to-left languages, e.g. Arabic, please take a look at the QML Right-to-left User Interfaces12 article. Switching languages after the application has started Currently, there are no standard APIs to load a new language after a Qt Quick application has already started. It is still possible with some additional code. See this article on the Qt developer Wiki about possible workarounds: How to do dynamic translation in QMLa
a
http://developer.qt.nokia.com/wiki/How_to_do_dynamic_translation_in_QML/
If you are interested in more details, check the further readings about internationalization in Qt: Qt documentation: Writing Source Code for Translation13 Qt documentation: Internationalization with Qt14 Qt developer Wiki: Qt Internationalization article15
33
// There are three borders, text and date. // 3*borderProportion+timeTextProportion+dateTextProportion has to be 1.0 property real borderProportion: 0.1 property real timeTextProportion: 0.5 property real dateTextProportion: 0.2 property string textColor: "red" height:240 width:400 Image { id: background source: "../content/resources/light_background.png" fillMode: "Tile" anchors.fill: parent onStatusChanged: if (background.status == Image.Error) console.log (qsTr("Background image \"") + source + qsTr("\" cannot be loaded")) } FontLoader { id: ledFont source: "../content/resources/font/LED_REAL.TTF" onStatusChanged: if (ledFont.status == FontLoader.Error) console.log (qsTr("Font \"") + source + qsTr("\" cannot be loaded")) } Column { id: clockText anchors.centerIn: parent spacing: root.height*root.borderProportion Text { id: timeText text: root.currentTime font.pixelSize: root.height*root.timeTextProportion font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.bold: true color: root.textColor style: Text.Raised styleColor: "black" } Text { id: dateText text: currentDate color: textColor anchors.horizontalCenter: parent.horizontalCenter font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.pixelSize: root.height*root.dateTextProportion visible: showDate style: Text.Raised styleColor: "black" }
34
} }
Whats Next?
Due to the tight relationship between JavaScript and Qt Quick we already used quite some script code in our examples. In the next chapter we will take a look on scripting in Qt Quick. A small script code will make our clock tick.
35
36
CHAPTER
SIX
USING JAVASCRIPT
Weve already used JavaScript a lot in our code, but we have only scratched the surface. JavaScript can be used in many more sophisticated and powerful ways in a Qt Quick application. In fact, Qt Quick is implemented as a JavaScript extension. JavaScript can be used almost anywhere as long as the code returns the value of the expected type. Moreover, using JavaScript is the standard way of writing parts of the application code which deal with application logic and calculations. There are two important topics we need to talk about before we continue developing our application.
http://en.wikipedia.org/wiki/Javascript
37
38
Introduction to Application Development with Qt Quick, Release 1.0 The :Qt.formatDateTime9 function is a part of QML Global Object10 which provides many other useful utilities in addition to the standard set dened by the ECMAScript Reference11 . It is worth it to take a closer look at its documentation. The getFormattedDateTime() function is used in another function which creates actual values for the Text12 elements in our clock:
function updateTime() { root.currentTime = "<big>" + getFormattedDateTime(Style.timeFormat) + "</big>" + (showSeconds ? "<sup><small> " + getFormattedDateTime("ss") + "</small></sup>" : ""); root.currentDate = getFormattedDateTime(Style.dateFormat); }
Note: We use rich text formatting in the text value of the time as discussed in the previous section We also use the conditional operator (also called the ternary operator) on the value of showSeconds. showSeconds is a custom property which denes if seconds should be shown on the clock. Using the conditional operator in Qt Quick is a very convenient way to bind a property (or any other variable) to a value depending on a condition just in one line. The updateTime() function needs a trigger so that the currentTime and currentDate properties will be constantly updated. We use the Timer13 item for this:
Timer { id: updateTimer running: Qt.application.active && visible == true repeat: true triggeredOnStart: true onTriggered: { updateTime() // refresh the interval to update the time each second or minute. // consider the delta in order to update on a full minute interval = 1000*(showSeconds? 1 : (60 - getFormattedDateTime("ss"))) } }
Our timer implements some interesting aspects. In order to optimize battery consumption, we bind the timers running property to two other properties, which stops the timer, thereby reducing CPU activity. It stops if our clock element becomes invisible (when used as a component in another application) or if our application becomes inactive (running in the background or iconied). We also assign a value to the interval property while not loading, but when the timer is triggered. This is needed to catch the delta time to the full minute when seconds are not used.
http://qt-project.org/doc/qt-4.8/qml-qt.html#formatDateTime-method http://qt-project.org/doc/qt-4.8/qdeclarativeglobalobject.html 11 http://qt-project.org/doc/qt-4.8/ecmascript.html 12 http://qt-project.org/doc/qt-4.8/qml-text.html 13 http://qt-project.org/doc/qt-4.8/qml-timer.html
10 9
39
This ensures that we update the clock exactly on the minute. The full code of our application including all enhancements as discussed above looks like this: (NightClock/NightClock.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Rectangle { id: root property bool showDate: true property bool showSeconds: true property string currentTime property string currentDate // the sizes are in proportion to the hight of the clock. // There are three borders, text and date. // 3*borderProportion+timeTextProportion+dateTextProportion has to be 1.0 property real borderProportion: 0.1 property real timeTextProportion: 0.5 property real dateTextProportion: 0.2 property string textColor: "red" property string timeFormat: "hh :mm" property string dateFormat: "dd/MM/yy" height:120 width:250 color: "transparent" // returns formated time and date function getFormattedDateTime(format) { var date = new Date return Qt.formatDateTime(date, format) } function updateTime() { root.currentTime = "<big>" + getFormattedDateTime(timeFormat) + "</big>" + (showSeconds ? "<sup><small> " + getFormattedDateTime("ss") + "</small></sup>" : ""); root.currentDate = getFormattedDateTime(dateFormat); } Image { id: background source: "../content/resources/background.png" fillMode: "Tile" anchors.fill: parent onStatusChanged: if (background.status == Image.Error) console.log (qsTr("Background image \"") + source + qsTr("\" cannot be loaded")) } FontLoader {
40
id: ledFont // unfortunately, the font will not load on a Symbian device, // and the default font will be used: // http://bugreports.qt.nokia.com/browse/QTBUG-6611 // The bug should be fixed in 4.8 source: "../content/resources/font/LED_REAL.TTF" onStatusChanged: if (ledFont.status == FontLoader.Error) console.log("Font \"" + source + "\" cannot be loaded") } Timer { id: updateTimer running: Qt.application.active && visible == true repeat: true triggeredOnStart: true onTriggered: { updateTime() // refresh the interval to update the time each second or minute. // consider the delta in order to update on a full minute interval = 1000*(showSeconds? 1 : (60 - getFormattedDateTime("ss"))) } } // trigger an update if the showSeconds setting has changed onShowSecondsChanged: { updateTime() } Column { id: clockText anchors.centerIn: parent spacing: root.height*root.borderProportion Text { id: timeText textFormat: Text.RichText text: root.currentTime font.pixelSize: root.height*root.timeTextProportion font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.bold: true color: root.textColor style: Text.Raised styleColor: "black" } Text { id: dateText text: root.currentDate color: root.textColor anchors.horizontalCenter: parent.horizontalCenter font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.pixelSize: root.height*root.dateTextProportion visible: root.showDate style: Text.Raised styleColor: "black" } }
41
Note: If the application logic is complex, consider implementing it in C++ and importing it into Qt Quick. See the Extending QML Functionalities using C++14 in the Qt documentation for more details. When you import a JavaScript le, it is used like a library and has the scope of the QML le importing it. In some cases you might need a stateless library or a set of global variables shared by multiple QML les. You can use the .pragma library declaration for this. See Integrating JavaScript15 article in Qt Documentation for more details about this. We move the JavaScript functions of our clock into the logic.js le imported as Logic. We also move all style properties into the style.js le imported as Style. This considerably simplies the code and allows sharing the style with other components which we will develop later. The full code of our application importing JavaScript les as discussed above looks like this: (components/NightClock.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 import "../js/style.js" as Style import "../js/logic.js" as Logic
14 15
http://qt-project.org/doc/qt-4.8/qml-extending.html http://qt-project.org/doc/qt-4.8/qdeclarativejavascript.html
42
Rectangle { id: root property property property property property bool showDate: true bool showSeconds: true string currentTime string currentDate string textColor: "green"
function updateTime() { root.currentTime = "<big>" + Logic.getFormattedDateTime(Style.timeFormat) + "</b (showSeconds ? "<sup><small> " + Logic.getFormattedDateTime("ss") + "</s root.currentDate = Logic.getFormattedDateTime(Style.dateFormat); } FontLoader { id: ledFont // unfortunately, the font will not load on a Symbian device, // and the default font will be used: // http://bugreports.qt.nokia.com/browse/QTBUG-6611 // The bug should be fixed in 4.8 source: "../content/resources/font/LED_REAL.TTF" onStatusChanged: if (ledFont.status == FontLoader.Error) console.log("Font \"" + source + "\" cannot be loaded") } Timer { id: updateTimer running: Qt.application.active && visible == true repeat: true triggeredOnStart: true onTriggered: { updateTime() // refresh the interval to update the time each second or minute. // consider the delta in order to update on a full minute interval = 1000*(showSeconds? 1 : (60 - Logic.getFormattedDateTime("ss"))) } } // trigger an update if the showSeconds setting has changed onShowSecondsChanged: { updateTime() } Column { id: clockText anchors.centerIn: parent spacing: root.height*Style.borderProportion Text { id: timeText textFormat: Text.RichText text: root.currentTime
43
font.pixelSize: root.height*Style.timeTextProportion font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.bold: true color: root.textColor style: Text.Raised styleColor: "black" } Text { id: dateText text: root.currentDate color: root.textColor anchors.horizontalCenter: parent.horizontalCenter font.family: ledFont.name // use "Series 60 ZDigi" on Symbian instead font.pixelSize: root.height*Style.dateTextProportion visible: root.showDate style: Text.Raised styleColor: "black" } } }
More advanced use of JavaScript If you are interested in a more advanced used of JavaScript in the development with Qt Quick consider reading Programming with Qt Quick for Symbian and MeeGo Harmattan Devices and Qt Quick Application Developer Guide for Desktop available under this linka .
a
http://qt-project.org/wiki/Developer-Guides/
Whats Next?
In the next chapter, we will start developing the weather forecast application and learn how to retrieve and represent data in Qt Quick
44
CHAPTER
SEVEN
7.1 Models
Qt Quick models are very simple since they are based on the concept of lists. The three kinds of models that are used the most are: an int value (useful to display something multiple times) a JavaScript array of objects list models, e.g. ListModel1 and XmlListModel2 element See the Models and Data Handling section on the QML Elements3 page in Qt Documentation for a full list of model related items. There are also some advanced approaches toward using models which are discussed in the QML Data Models4 article in Qt Documentation. We will use XmlListModel5 and take a look on a few examples where an int and an array are used as models. Our weather forecast application will use Google weather APIs to get the data. Note: Google weather APIs are not announced as a regular internet service.
1 2
45
With these APIs, you can make a query on the web and receive weather data in XML as a response. Since this is a very common way of data provisioning in general, Qt Quick provides a dedicated model for this: XmlListModel6 . XmlListModel7 uses XPath and XQuery (see this article in Wikipedia8 ) to read the data delivered as XML. XmlListModel9 uses XmlRole10 to create model items for selected XML tree nodes. Lets see how this works. The query URL is formatted like this:
http://www.google.com/ig/api?weather=[LOCATION]&hl=[LANGUAGE]
It returns the current weather conditions and a forecast for the next four days. If LOCATION is set as Munich and LANGUAGE is set as English, it looks like this:
http://www.google.com/ig/api?weather=Munich&hl=en
<?xml version="1.0" ?> <xml_api_reply version="1"> <weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" sec <forecast_information> <city data="Munich, Bavaria" /> <postal_code data="Munich" /> <latitude_e6 data="" /> <longitude_e6 data="" /> <forecast_date data="2012-02-22" /> <current_date_time data="1970-01-01 00:00:00 +0000" /> <unit_system data="US" /> </forecast_information> <current_conditions> <condition data="Clear" /> <temp_f data="39" /> <temp_c data="4" /> <humidity data="Humidity: 56%" /> <icon data="/ig/images/weather/sunny.gif" /> <wind_condition data="Wind: E at 8 mph" /> </current_conditions> <forecast_conditions> <day_of_week data="Wed" /> <low data="27" /> <high data="43" /> <icon data="/ig/images/weather/sunny.gif" /> <condition data="Clear" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Thu" />
http://qt-project.org/doc/qt-4.8/qml-xmllistmodel.html http://qt-project.org/doc/qt-4.8/qml-xmllistmodel.html 8 http://en.wikipedia.org/wiki/XPath 9 http://qt-project.org/doc/qt-4.8/qml-xmllistmodel.html 10 http://qt-project.org/doc/qt-4.8/qml-xmlrole.html
7 6
46
<low data="36" /> <high data="43" /> <icon data="/ig/images/weather/chance_of_rain.gif" /> <condition data="Chance of Rain" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Fri" /> <low data="36" /> <high data="54" /> <icon data="/ig/images/weather/sunny.gif" /> <condition data="Clear" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Sat" /> <low data="34" /> <high data="48" /> <icon data="/ig/images/weather/chance_of_rain.gif" /> <condition data="Chance of Rain" /> </forecast_conditions> </weather> </xml_api_reply>
A model which queries and processes this data looks like this:
import QtQuick 1.1 Item { id: root property string location: "Munich" property string baseURL: "http://www.google.com" property string dataURL: "/ig/api?weather=" // some other values: "de", "es", "fi", "fr", "it", "ru" property string language: "en" XmlListModel { id: weatherModelCurrent source: baseURL + dataURL + location + "&hl=" + language query: "/xml_api_reply/weather/current_conditions" XmlRole XmlRole XmlRole XmlRole XmlRole } XmlListModel { id: weatherModelForecast source: baseURL + dataURL + location + "&hl=" + language query: "/xml_api_reply/weather/forecast_conditions" XmlRole XmlRole XmlRole XmlRole { { { { name: name: name: name: "day_of_week"; query: "day_of_week/@data/string()" } "low"; query: "low/@data/string()" } "high"; query: "high/@data/string()" } "icon_url"; query: "icon/@data/string()" } { { { { { name: name: name: name: name: "condition"; query: "condition/@data/string()" } "temp_f"; query: "temp_f/@data/string()" } "humidity"; query: "humidity/@data/string()" } "icon_url"; query: "icon/@data/string()" } "wind_condition"; query: "wind_condition/@data/string()" }
7.1. Models
47
If you take a closer look at the code inside the XmlRole11 elements, you will notice that they basically create model items with property-value pairs by mapping them to the specied nodes in the XML tree starting at the node specied in query. Like Image12 and Font13 , XmlListModel14 provides status and progress properties, which can be used to track the loading progress and catch the errors. Additionally, there is a reload() method which forces the model to query the URL again and load updated data. We will use this later to make sure that the weather forecast is always up-to-date.
The best use case for views is when a large number of model items has to be displayed. Views provide a built-in scrolling or icking functionality to support ergonomic representation of
11 12
48
large data sets. There are also some performance reasons for this since views only load the items which become visible and not the entire set of them. Advanced use of views Views provide rich functionality and can be used to create pretty sophisticated UIs. If you are interested, consider reading the The Qt Quick Carousel Tutoriala
a
http://qt-project.org/wiki/Qt_Quick_Carousel
If you have a small amount of model items which have to be placed one after the other in a certain order, it makes more sense to use a Repeater19 . A Repeater20 creates specied elements for each item in the model. These elements have to be placed on the screen by a positioner, e.g. Column21 , Grid22 etc. The above example can be modied to use the Repeater23 :
import QtQuick 1.1 Column { Repeater { model: ["one", "two", "three", "four", "five"] // or just a number, e.g 10 Text { text: "Index: " + model.index + ", Data: " + model.modelData } } }
Note that all items are now visible even though the size of the containing element Column is not specied. Repeater24 calculates the size of the elements and Column25 resizes accordingly. Check the Presenting Data with Views article26 in Qt Documentation for more details. We will nish our application by adding two visualization elements, each of which uses its own delegate. We need separate delegates since current weather conditions data and the forecast data have different structures which we would like to present in different ways. But what should be used as visualization elements? It is possible with either a view a or with a Repeater27 . The weatherModelForecast items are displayed by a GridView28 and look like this:
19 20
http://qt-project.org/doc/qt-4.8/qml-repeater.html http://qt-project.org/doc/qt-4.8/qml-repeater.html 21 http://qt-project.org/doc/qt-4.8/qml-column.html 22 http://qt-project.org/doc/qt-4.8/qml-grid.hrml 23 http://qt-project.org/doc/qt-4.8/qml-repeater.html 24 http://qt-project.org/doc/qt-4.8/qml-repeater.html 25 http://qt-project.org/doc/qt-4.8/qml-column.html 26 http://qt-project.org/doc/qt-4.8/qml-views.html 27 http://qt-project.org/doc/qt-4.8/qml-repeater.html 28 http://qt-project.org/doc/qt-4.8/qml-gridview.html
49
The weatherModelCurrent contains just one item. Due to this, a Repeater30 is totally sufcient for displaying it and we keep this approach. The full source code of the application:
29 30
http://qt-project.org/doc/qt-4.8/qml-repeater.html http://qt-project.org/doc/qt-4.8/qml-repeater.html
50
51
id: forecastConditionDelegate Column { spacing: 2 Text { text: model.day_of_week; font.bold: true } Text { text: model.condition } Image { source: baseURL + model.icon_url } Text { text: qsTr("Lows: ") + model.low + " F / " + f2C (model.low) + " C"} Text { text: qsTr("Highs: ") + model.high + " F / " + f2C (model.high) + " C"} } } Column { id: allWeather anchors.centerIn: parent anchors.margins: 10 spacing: 10 Repeater { id: currentReportList model: weatherModelCurrent delegate: currentConditionDelegate } /* we can use a GridView...*/ GridView { id: forecastReportList width: 220 height: 220 cellWidth: 110; cellHeight: 110 model: weatherModelForecast delegate: forecastConditionDelegate } /**/ /* ..a Repeater Repeater { id: forecastReportList model: weatherModelForecast delegate: forecastConditionDelegate } */ } MouseArea { anchors.fill: parent onClicked: Qt.quit() } }
52
A note about accessing the le system We do not need this feature in our application, but it is important to mention. In the current version, Qt Quick does not provide direct access to the local le system unless you hardcode a name of the le you would like to load. A FolderListModel C++ plug-in is provided as a lab project in Qt 4.7.4 and higher, see FolderListModel - a C++ model plugin articlea in Qt Documentation. An earlier version of this plugin is used to develop a text editor in a getting started tutorialb .
a b
http://qt-project.org/doc/qt-4.8/src-imports-folderlistmodel.html http://qt-project.org/doc/qt-4.8/gettingstartedqml.html
Whats Next?
In the next chapter, we will start to combine the clock and the weather forecast features in one application. We will make components based on the code we have developed so far and use these components to compose the nal application.
53
54
CHAPTER
EIGHT
http://qt-project.org/doc/qt-4.8/qdeclarativedocuments.html
55
Introduction to Application Development with Qt Quick, Release 1.0 Please read Writing QML Components: Properties, Methods and Signals2 in Qt 4.7 documentation for more details about components (because of a known bug, this document is not available in 4.8.0). If you move the directory with modules, you have to change the path in all QML documents using them. It is also possible to provision modules for global use by any application. See the QML Modules3 article for more details about this. Note: Components can also be developed as plug-ins written in C++. This topic goes beyond the scope of this guide, but you can read this article4 if you are interested. In some cases, you will need to dene in-line components, e.g. when you pass a reference to a component to another element in the same QML le. This is used more frequently for delegates in views. See the main items code of the nal application in the A new application made of two (page 60) section. If you experience problems while importing modules or separate components, set the environment variable QML_IMPORT_TRACE to 1 (see Debugging QML5 ). Lets take a look at how this is used in our application. We move NightClock.qml to a new folder called components. This folder also contains two new components: Weather and WeatherModelItem. As mentioned above, we also add a qmldir le to describe a new module: (components/qmldir in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
Configure 1.0 Configure.qml NightClock 1.0 NightClock.qml WeatherModelItem 1.0 WeatherModelItem.qml Weather 1.0 Weather.qml
Weather and WeatherModelItem include the code from the previous sections in a reworked and extended form. We will take a closer look at the changes in the next section.
56
bind that existing property to another value, and use existing signals and handlers. You can also extend that component by declaring additional properties, new signals, handlers and JavaScript functions. Since all these steps are optional, a component has to have a default appearance and behavior if loaded as is, e.g.:
import QtQuick 1.1 import "components" 1.0 Item { id: root NightClock { id: clock } }
This is the same as executing NightClock like a stand-alone Qt Quick application. Lets try this out and create a new application called clock-n-weather which will use three components based on the code we developed before: NightClock - the digital clock WeatherModelItem - a new weather model which combines the forecast and current weather Weather - the delegate for drawing the weather data for one day Most of the code of these components should be known to you from previous sections. NightClock remained unchanged. We are just binding a few properties (e.g. showDate, showSeconds) to values from the root item and add new values to customize NightClock:
... NightClock { id: clock height: 80 width: 160 showDate: root.showDate showSeconds: root.showSeconds textColor: Style.onlineClockTextColor } ...
Properties showDate and showSeconds are actually conguration parameters which we keep as property values of the root element. In a later section we will add a Configure component which will manage these as well as a few other values.
57
alias forecastModel: forecast alias currentModel: current string location: "Munich" bool forceOffline: false string baseURL: "http://www.google.com" string dataURL: "/ig/api?weather=" string source: baseURL + dataURL + location.split( ).join(%20) int interval: 5 bool modelDataError: false string statusMessage: ""
XmlListModel { id: forecast source: root.source query: "/xml_api_reply/weather/forecast_conditions" XmlRole XmlRole XmlRole XmlRole XmlRole { { { { { name: name: name: name: name: "day_of_week"; query: "day_of_week/@data/string()" } "low"; query: "low/@data/string()" } "high"; query: "high/@data/string()" } "condition"; query: "condition/@data/string()" } "temp_c"; query: "temp_c/@data/string()" }
onStatusChanged: { root.modelDataError = false if (status == XmlListModel.Error) { root.state = "Offline" root.statusMessage = "Error occured: " + errorString() root.modelDataError = true //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Ready) { // check if the loaded model is not empty, and post a message if (get(0) === undefined) { root.state = "Offline" root.statusMessage = "Invalid location \"" + root.location + "\"" root.modelDataError = true } else { root.state = "Live Weather" root.statusMessage = "Live current weather is available" } //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Loading) { root.state = "Loading" root.statusMessage = "Forecast data is loading..." //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Null) { root.state = "Loading" root.statusMessage = "Forecast data is empty..." //console.log("Weather Clock: " + root.statusMessage) } else { root.modelDataError = false console.log("Weather Clock: unknown XmlListModel status:" + status)
58
} } } XmlListModel { id: current source: root.source query: "/xml_api_reply/weather/current_conditions" XmlRole { name: "condition"; query: "condition/@data/string()" } XmlRole { name: "temp_c"; query: "temp_c/@data/string()" }
onStatusChanged: { root.modelDataError = false if (status == XmlListModel.Error) { root.state = "Offline" root.statusMessage = "Error occured: " + errorString() root.modelDataError = true //console.log("Weather Clock: Error reading current: " + root.statusMess } else if (status == XmlListModel.Ready) { // check if the loaded model is not empty, and post a message if (get(0) === undefined) { root.state = "Offline" root.statusMessage = "Invalid location \"" + root.location + "\"" root.modelDataError = true } else { root.state = "Live Weather" root.statusMessage = "Live current weather is available" } //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Loading) { root.state = "Loading" root.statusMessage = "Current weather is loading..." //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Null) { root.state = "Loading" root.statusMessage = "Current weather is empty..." //console.log("Weather Clock: " + root.statusMessage) } else { root.modelDataError = true console.log("Weather Clock: unknown XmlListModel status:" + status) } } } Timer { // note that this interval is not accurate to a second on a full minute // since we omit adjustment on seconds like in the clock interval // to simplify the code interval: root.interval*60000 running: Qt.application.active && !root.forceOffline repeat: true onTriggered: { current.reload() forecast.reload() } }
59
As you see, it contains two models hosted in one item. We would need to access them separately later when using them in views. If the imported WeatherModelItem has the weatherModelItem id, you might suggest to access them as weatherModelItem.forecast and weatherModelItem.current. Unfortunately, this will not work. The problem is that the child items of an imported component are by default not visible for the importing item. A way to solve this problem is to use alias properties to export their id:
property alias forecastModel: forecast property alias currentModel: current
This provides a solution, since properties in the root item of an imported component are visible. Our models will be accessible as weatherModelItem.forecastModel and weatherModelItem.forecastModel. Scope and visibility of items, their properties and JavaScript objects are a very important aspect in Qt Quick. We strongly advise reading the QML Scope6 article in Qt Documentation. The article referenced above also explains how Qt Quick scope mechanism resolves situations with name conicts. It is important to keep those rules in mind. A good praxis in the daily work is to always qualify the properties you bind to. This will make your applications code easier for others to understand and avoid unexpected side effects. Doing this, you should write for example:
Item { id: myItem ... enable: otherItem.visible }
instead of just:
Item { id: myItem ... enable: visible }
http://qt-project.org/doc/qt-4.8/qdeclarativescope.html
60
// to simplify the code interval: root.interval*60000 running: Qt.application.active && !root.forceOffline repeat: true onTriggered: { current.reload() forecast.reload() } }
This timer is similar to the timer in the clock application and updates the weather data in the modes each root.interval seconds. root.interval is a conguration parameter dened as a property and bound to according value in the parent item. We also have an updated delegate component for drawing weather conditions. The major change is to use local weather icons instead of loading them from the Internet. This has many advantages such as saving the bandwidth (if the application will be running on a mobile device) or just a different look-n-feel which better meets our expectations and is less dependent on external content. We use a very nice set of weather icons from the KDE7 project. We rename them to match weather condition descriptions and add just a few statements in JavaScript to load them from the local le system:
Image { id: icon anchors.fill: parent smooth: true fillMode: Image.PreserveAspectCrop source: "../content/resources/weather_icons/" + conditionText.toLowerCase().split( ).join(_) + ".png" onStatusChanged: if (status == Image.Error) { // we set the icon to an empty image // if we failed to find one source = "" console.log("no icon found for the weather condition: \"" + conditionText + "\"") } }
Notice that the Weather component can also be started as a stand alone Qt Quick application if needed. It will use the default property values in this case. This is useful for testing the component under various conditions. It looks like this:
The main item of the complete application using components looks like this:
7
http://www.kde.org
61
string defaultLocation: "Munich" int defaultInterval: 60 // in seconds bool showSeconds: true bool showDate: true
width: 360 height: 640 Image { id: background source: "../content/resources/background.png" fillMode: "Tile" anchors.fill: parent onStatusChanged: if (background.status == Image.Error) console.log("Background image \"" + source + "\" cannot be loaded") } WeatherModelItem { id: weatherModelItem location: root.defaultLocation interval: root.defaultInterval } Component { id: weatherCurrentDelegate Weather { id: currentWeatherItem labelText: root.defaultLocation conditionText: model.condition tempText: model.temp_c + "C" } } Component { id: weatherForecastDelegate Weather { id: forecastWeatherItem labelText: model.day_of_week conditionText: model.condition tempText: Logic.f2C (model.high) + "C / " + Logic.f2C (model.low) + "C" } }
62
Column { id: clockAndWeatherScreen anchors.centerIn: root NightClock { id: clock height: 80 width: 160 showDate: root.showDate showSeconds: root.showSeconds textColor: Style.onlineClockTextColor } Repeater { id: currentWeatherView model: weatherModelItem.currentModel delegate: weatherCurrentDelegate } GridView { id: forecastWeatherView width: 300 height: 300 cellWidth: 150; cellHeight: 150 model: weatherModelItem.forecastModel delegate: weatherForecastDelegate } } MouseArea { anchors.fill: parent onClicked: Qt.quit() } }
We load WeatherModelItem as weatherModelItem and dene two delegates based on the Weather component. Then we have a Column8 with our NightClock component, a Repeater9 with the current weather condition data and a GridView10 with the forecast. Thats it! This is how it looks on the screen:
http://qt-project.org/doc/qt-4.8/qml-column.html http://qt-project.org/doc/qt-4.8/qml-repeater.html 10 http://qt-project.org/doc/qt-4.8/qml-gridview.html
9 8
63
In the next chapter, we will focus on the interaction with the user. We will learn how Qt Quick supports this and how we can create simple UI components which suit our needs.
11 12
64
CHAPTER
NINE
9.1 A button
The rst thing we will do is create a button component. It will be used to quit the application, open the conguration window, close it etc. Our button should have basic visual parameters and send a signal when it is clicked. The button should also give some visual response that it has received user input. Certainly, a button can have many more features. There may be dozens of approaches for implementing a button, but well just describe one which suits our needs. Our button will simply be a click-sensitive rectangle with rounded corners. In previous sections, we saw that an element can receive mouse events if we include a MouseArea1 element and let it ll the entire surface of that element. We are going to use this approach for the button. Additionally, our button has to emit a signal notifying relevant parts of the application that it has been clicked. We will to use Qt Quick signals to implement this. Lets take a look at how they work rst. We already got in touch with a related Qt Quick functionality when we saw that it is possible to implement a handler which reacts on property changes, e.g. the status property of Image2 :
Image { id: background source: "./content/resources/light_background.png" fillMode: "Tile" anchors.fill: parent onStatusChanged: if (background.status == Image.Error) console.log (qsTr("Background image \"") +
1 2
http://qt-project.org/doc/qt-4.8/qml-mousearea.html http://qt-project.org/doc/qt-4.8/qml-image.html
65
Signals are very similar to the property notication changes. Signal handlers work the same, whereas they process a signal explicitly emitted in an item instead of a property change. Signal handlers can also receive signal parameters, which is not the case in property change handlers. Emitting a signal is just like calling a function. This is how it works in the the code of the new Button component: (src/utils/Button.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 import "../js/style.js" as Style Rectangle { id: root property string text: "Button" color: "transparent" width: label.width + 15 height: label.height + 10 border.width: Style.borderWidth border.color: pressedColor(Style.penColor) radius: Style.borderRadius signal clicked (variant mouse) signal pressedAtXY (string coordinates) function pressedColor (color) { return mouseArea.pressed ? Qt.darker(color, 5.0) : color } function logPresses (mouse) { pressedAtXY (mouse.x + "," + mouse.y) } Component.onCompleted: { mouseArea.clicked.connect(root.clicked) } Text { id: label anchors.centerIn: parent color: pressedColor(Style.penColor) text: parent.text font.pixelSize: Style.textPixelSize } MouseArea { id: mouseArea anchors.fill: parent
66
Connections { onPressed: logPresses(mouse) } // this works as well instead of using Connections // onPressed: logPresses(mouse) } }
Button denes two signals: clicked and pressedAtXY. We actually only use clicked in our application. pressedAtXY has been added for demonstration purposes. Both signals are emitted in different ways. pressedAtXY is called from a JavaScript function called as an onPressed handler. clicked is connected directly to the clicked signal of the mouseArea item. Both ways have their own use cases. A direct signal-to-signal connection allows simple signal forwarding. This is what is needed in our Button, which should behave like a MouseArea when processing mouse events. In some other cases, you might have the need to add some additional processing before emitting a signal, like in the logPresses function. A very important point to note here is the naming of signal parameters. If you take a look at the code of mouseArea above, you might wonder where the mouse parameter comes from. We did not declare it in our application. It actually belongs to the denition of clicked signal in the MouseArea3 element located lnside the Qt Quick system code. The same happens with our pressedAtXY signal which denes a coordinates parameter. All items using Button and processing the pressedAtXY signal have to access its parameter under the exact same name, e.g.:
Button { id: toggleStatesButton ... onPressedAtXY: { console.log ("pressed at: " + coordinates) } }
We do this even though mouse is of the MouseEvent4 type (according to the documentation of MouseArea5 ). In the current version of Qt Quick, signal parameters can only be of basic types6 . This should not cause you any concern because they will be converted into appropriate types when they arrive. For more details about using signals in Qt Quick, see the QML Signal and Handler Event System7 article in Qt documentation. You should also check the documentation of MouseArea8 as well as the QML Mouse Events9 article to discover more possibilities such as getting other
3 4
9.1. A button
67
mouse events, tracing hovering and implementing drag-drop. As any proper button, our Button should provide some visual feedback if it has been clicked. We do this by tweaking its colors a bit. We have a small JavaScript function which modies a color value to a new, pressed value. It makes a color darker in our particular case:
function pressedColor (color) { return mouseArea.pressed ? Qt.darker(color, 5.0) : color }
We are going to toggle the color of the button border and of its label text. We bind the return value of this function to the border of the button:
border.color: pressedColor(Style.penColor)
That was it! This is how our Button looks when unpressed:
and pressed:
68
height: 40 color: Style.backgroundColor border.color: Style.penColor border.width: Style.borderWidth radius: Style.borderRadius visible: true function show(text) { root.message = text; root.visible = true; } function hide() { root.visible = false; }
Text { id: messageText anchors.top: parent.top anchors.topMargin: Style.baseMargin anchors.left: parent.left anchors.right: parent.right horizontalAlignment: Text.AlignHCenter wrapMode: "WordWrap" text: root.message font.pixelSize: Style.textPixelSize color: Style.penColor onPaintedHeightChanged: { root.height = messageText.paintedHeight + okButton.height + 3*Style.baseMarg } } Button { id: okButton text: qsTr("OK") anchors.top: messageText.bottom anchors.topMargin: Style.baseMargin anchors.horizontalCenter: parent.horizontalCenter onClicked: root.hide() } }
The Dialog is used by adding it as a child item to another element where it will pop-up from:
Item { id: root ... Dialog { id: errorDialog width: root.width anchors.centerIn: parent z: root.z+1 visible: false }
69
... Button { id: exitButton ... onClicked: { ... errorDialog.show (qsTr("The location cannot be empty")); ... } } ... }
When loaded, the Dialog initially stays invisible. It goes on top of the its parent (root in the code segment above). z: root.z+1 does this trick. We bind its z property to a value which is always higher than the value of root.z. Later, we call show with a message to be displayed. show makes the Dialog visible and stores the message text to be displayed. When the user clicks the OK button, the Dialog hides itself again. Note: The TabWidget Example10 in Qt documentation shows another approach toward dynamically showing and hiding elements on top of others. Our Dialog has a few other features which are useful to know. In order to use the screen space efciently, it copies the width from its parent. We also set the messageText property wrapMode to the WordWrap value. When the Dialog opens with a long message text, the message wraps it to the Dialog width. The messageText element changes the height the root of Dialog when its height has changed due to wrapping:
Rectangle { id: root ... Text { id: messageText ... onPaintedHeightChanged: { root.height = messageText.paintedHeight + okButton.height + 3*Style.baseMargin } ... }
http://qt-project.org/doc/qt-4.8/declarative-ui-components-tabwidget.html
70
9.3 A checkbox
We can use the Text Input11 element to get text or digit based user input, but we need something else for on-off type of settings. Usually, this is done with checkbox UI elements. There is no checkbox element in Qt Quick, and we are going to make it from scratch. It is not a problem at all, it is actually very simple. This is the whole code of our new CheckBox component: (src/utils/CheckBox.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Item { id: root property bool checked: true // we should pre-set the size to get it working perperly in a positioner width: checkBox.width height: checkBox.height Image { id: checkBox source: root.checked ? "../content/resources/checkbox.png" : "../content/resources/draw-rectangle.png" Keys.onPressed: { if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter || event.key == Qt.Key_Space) root.checked = !root.checked; } MouseArea { anchors.fill: parent onClicked: { root.checked = !root.checked; } } } //onValueChanged: console.log ("value: " + root.value) }
11
http://qt-project.org/doc/qt-4.8/qml-textinput.html
9.3. A checkbox
71
Introduction to Application Development with Qt Quick, Release 1.0 Our CheckBox is based on Item12 . It extends it just by one boolean property called checked. If the box is checked, checked is true. Otherwise it is false. The entire visual implementation of the CheckBox consists of two images which are ipped back and forth. This is done by binding the source property of the checkBox Image13 item to a checkbox image or to an image of a normal rectangle depending on the checked property. This is how our CheckBox looks on the screen checked and unchecked:
Further on, there is a section of code which includes the handling of the keyboard navigation. This topic will be discussed in the next section.
72
... TextInput { id: locationTextInput ... width: controlElements.width - locationTextInput.x - controlElements.spacing text: locationText focus: true } ... TextInput { id: updateTextInput ... text: forecastUpdateInterval maximumLength: 3 // we use IntValidator just to filter the input. onAccepted is not used here validator: IntValidator{bottom: 1; top: 999;} } ...
The code above has a few things on top. updateTextInput uses a validator to limit the length of the text and ensure that we get digits in a proper range. Location names do not need a validator, but they need something to handle text input which is longer than just a few digits in updateTextInput. This can be achieved limiting the width to ensure that a long text does not leave the boundaries of the top-level item. If we do not do this and keep width undened, Text Input16 will expand, follow the entered text and at some time leave the visual boundaries. Note: If you have a multi-line text which should be edited by the user, you can use the Text Edit17 element. locationTextInput receives the keyboard focus explicitly, because we set focus to true. When Configure is loaded, the user can start changing the location name:
16 17
http://qt-project.org/doc/qt-4.8/qml-textinput.html http://qt-project.org/doc/qt-4.8/qml-textedit.html
73
The elements Text Input18 and our new CheckBoxes react on mouse clicks. How can user go to form one input elements to another if we would like to support navigation with keyboard keys in addition to the mouse? What should we do if we need to enable keyboard input in CheckBoxes as well? . Qt Quick provides key navigation and raw key processing for these cases. Lets rst take a look at key navigation. These are changes in the code of our two Text Input19 elements to support key navigation:
TextInput { id: locationTextInput ... focus: true KeyNavigation.up: offlineCheckBox KeyNavigation.down: updateTextInput } ... TextInput { id: updateTextInput ... KeyNavigation.up: locationTextInput KeyNavigation.down: secondsCheckBox }
locationTextInput explicitly pulls the focus by setting its focus property to true. The Key Navigation20 items provide attached properties which monitor key presses and move of the input focus from one element to another. Key Navigation21 is a big help in our case where we have many elements and need to organize the movement of the input focus in a certain way. In the code sample above, the input focus is moved from the locationTextInput item to the updateTextInput item if the down arrow key is pressed. The focus goes back from
18 19
74
updateTextInput to locationTextInput if the user presses the up key and so on. We add such statements to all relevant elements in the Configure component. While processing user input, you sometimes need to capture particular keys. This is the case with our Checkboxes. Working with desktop applications, users have learned that it is possible to toggle a check box with the space key. We should implement this feature in our application. This is where the Keys22 items can be used. It is basically a kind of signal sender for almost every key on computer keyboards. Its signals have KeyEvent23 as a parameter. It contains detailed information about the key pressed. We use Keys24 in our checkboxes. The code segment in the previous section uses the attached Keys.onPressed property, which toggles the Checkbox state on Return, Enter and Space keys. More details about keyboard input processing is available in the Keyboard Focus in QML25 article in Qt Documentation. By now we have got all input elements and can process user input. One step is still needed to nish our Configure component. This is a verication and storing of the new values. When the user clicks the exitButton, we need to check the new setting values and pass them to the application if they are ok. This is also a place where we use our Dialog to inform the user that new values are not OK if needed. In this case, the Configure does not close and stays open until the user provides correct values. See the onClicked handler of exitButton in the full code of Configure: (src/components/Configure.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 import "../utils" 1.0 import "../js/style.js" as Style Rectangle { id: root property property property property property
bool showSeconds: true bool showDate: true int forecastUpdateInterval: 5 // minutes string locationText: "Munich" bool forceOffline: false
width: 320 height: 480 Image { id: background source: Style.backgroundImage fillMode: "Tile" anchors.fill: parent }
22 23
75
Grid { id: controlElements spacing: 10 columns: 2 anchors.left: root.left anchors.leftMargin: spacing anchors.verticalCenter: root.verticalCenter anchors.right: root.right Text { id: locationLabel text: qsTr("Forecast for:<br>(city name)") color: locationTextInput.focus? Qt.lighter(Style.penColor) : Style.penColor font.pixelSize: Style.textPixelSize }
TextInput { id: locationTextInput width: controlElements.width - locationTextInput.x - controlElements.spacing text: locationText font.pixelSize: Style.textPixelSize color: Style.penColor focus: true KeyNavigation.up: offlineCheckBox KeyNavigation.down: updateTextInput } Text { id: updateLabel height: 90 text: qsTr("update interval: <br>(in min)") color: updateTextInput.focus? Qt.lighter(Style.penColor) : Style.penColor font.pixelSize: Style.textPixelSize } TextInput { id: updateTextInput width: locationTextInput.width text: forecastUpdateInterval font.pixelSize: Style.textPixelSize color: Style.penColor maximumLength: 3 // we use IntValidator just to filter the input // onAccepted is not used here validator: IntValidator{bottom: 1; top: 999;} KeyNavigation.up: locationTextInput KeyNavigation.down: secondsCheckBox } Text { id: secondsLabel text: qsTr("Show seconds:") color: secondsCheckBox.focus? Qt.lighter(Style.penColor) : Style.penColor font.pixelSize: Style.textPixelSize
76
} CheckBox { id: secondsCheckBox checked: showSeconds KeyNavigation.up: updateTextInput KeyNavigation.down: dateCheckBox } Text { id: dateLabel text: qsTr("Show date:") color: dateCheckBox.focus? Qt.lighter(Style.penColor) : Style.penColor font.pixelSize: Style.textPixelSize } CheckBox { id: dateCheckBox checked: showDate KeyNavigation.up: secondsCheckBox KeyNavigation.down: offlineCheckBox } Text { id: offlineLabel text: qsTr("Clock only") color: offlineCheckBox.focus? Qt.lighter(Style.penColor) : Style.penColor font.pixelSize: Style.textPixelSize } CheckBox { id: offlineCheckBox checked: forceOffline KeyNavigation.up: secondsCheckBox KeyNavigation.down: locationTextInput } } Dialog { id: errorDialog width: root.width anchors.centerIn: parent z: root.z+1 visible: false } Button { id: exitButton text: qsTr("OK") anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: 10 onClicked: { // update interval and location cannot be empty // update interval cannot be zero
77
if (updateTextInput.text == "" || updateTextInput.text == 0) errorDialog.show (qsTr("The update interval cannot be empty")) else if (locationTextInput.text == "") errorDialog.show (qsTr("The location cannot be empty")) else { forecastUpdateInterval = updateTextInput.text root.locationText = locationTextInput.text root.visible = false } // update check box relevant settings root.showSeconds = secondsCheckBox.checked root.showDate = dateCheckBox.checked root.forceOffline = offlineCheckBox.checked } } }
Whats Next?
Youve probably noticed that the offlineCheckBox item with the magical forceOffline setting associated with it. This setting is new. It is used to toggle the states in the next version of our application which will be the topic for the next chapter. We also will take a look at animations in Qt Quick and use them to implement some nice effects in the nal version of our application.
78
CHAPTER
TEN
79
This is because WeatherModelItem failed to get the weather data. Due to this, there are no model items to display. If you use this application on a notebook or on a mobile device, this situation might occur very frequently. It would be great if our application would be able to handle situations when the network is down. We can accomplish this by using the State1 item provided by Qt Quick. Each item in Qt Quick has a state property which holds the name of the current state. There is also a states property which is a list of States2 . This property contains all states known for that item. Each of the States3 in the list has a string name and denes a set of property values. If required, it can even contain some script code which will be executed when that state becomes the current one. An item can be set to a state just by assigning the name of a selected state to the state property. See the documentation of State4 as well as the QML States5 article for more details. We will add three states to the main item of our application: Ofine - It is an initial state in the startup phase. It is also applied if there is no network connection or the if application should stay ofine Loading - A network connection is available, but WeatherModelItem is still loading weather data. This state is useful on slow network connections (on mobile devices for example). Live Weather - Updated weather data is available and displayed. In the Ofine and Loading states, the application should show just the clock in a larger size in the middle of the screen. When Live Weather is active, the application should show the weather data as well. Since our new states are so closely related to the status of the WeatherModelItem, we just bind them directly. The WeatherModelItem does not dene any real states. We hijack its states property to store Ofine, Loading and Live Weather values depending on the status of the current or forecast models: (src/components/WeatherModelItem.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 Item { id: root property property property property property property property property property
1 2
alias forecastModel: forecast alias currentModel: current string location: "Munich" bool forceOffline: false string baseURL: "http://www.google.com" string dataURL: "/ig/api?weather=" string source: baseURL + dataURL + location.split( ).join(%20) int interval: 5 bool modelDataError: false
80
property string statusMessage: "" XmlListModel { id: forecast source: root.source query: "/xml_api_reply/weather/forecast_conditions" XmlRole XmlRole XmlRole XmlRole XmlRole { { { { { name: name: name: name: name: "day_of_week"; query: "day_of_week/@data/string()" } "low"; query: "low/@data/string()" } "high"; query: "high/@data/string()" } "condition"; query: "condition/@data/string()" } "temp_c"; query: "temp_c/@data/string()" }
onStatusChanged: { root.modelDataError = false if (status == XmlListModel.Error) { root.state = "Offline" root.statusMessage = "Error occured: " + errorString() root.modelDataError = true //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Ready) { // check if the loaded model is not empty, and post a message if (get(0) === undefined) { root.state = "Offline" root.statusMessage = "Invalid location \"" + root.location + "\"" root.modelDataError = true } else { root.state = "Live Weather" root.statusMessage = "Live current weather is available" } //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Loading) { root.state = "Loading" root.statusMessage = "Forecast data is loading..." //console.log("Weather Clock: " + root.statusMessage) } else if (status == XmlListModel.Null) { root.state = "Loading" root.statusMessage = "Forecast data is empty..." //console.log("Weather Clock: " + root.statusMessage) } else { root.modelDataError = false console.log("Weather Clock: unknown XmlListModel status:" + status) } } } XmlListModel { id: current source: root.source query: "/xml_api_reply/weather/current_conditions" XmlRole { name: "condition"; query: "condition/@data/string()" } XmlRole { name: "temp_c"; query: "temp_c/@data/string()" } onStatusChanged: { root.modelDataError = false if (status == XmlListModel.Error) {
81
root.state = "Offline" root.statusMessage = "Error occured: " + errorString() root.modelDataError = true //console.log("Weather Clock: Error reading current: " + root.statusMess else if (status == XmlListModel.Ready) { // check if the loaded model is not empty, and post a message if (get(0) === undefined) { root.state = "Offline" root.statusMessage = "Invalid location \"" + root.location + "\"" root.modelDataError = true } else { root.state = "Live Weather" root.statusMessage = "Live current weather is available" } //console.log("Weather Clock: " + root.statusMessage) else if (status == XmlListModel.Loading) { root.state = "Loading" root.statusMessage = "Current weather is loading..." //console.log("Weather Clock: " + root.statusMessage) else if (status == XmlListModel.Null) { root.state = "Loading" root.statusMessage = "Current weather is empty..." //console.log("Weather Clock: " + root.statusMessage) else { root.modelDataError = true console.log("Weather Clock: unknown XmlListModel status:" + status)
} } } Timer { // note that this interval is not accurate to a second on a full minute // since we omit adjustment on seconds like in the clock interval // to simplify the code interval: root.interval*60000 running: Qt.application.active && !root.forceOffline repeat: true onTriggered: { current.reload() forecast.reload() } } }
The actual states will be introduced in the main item WeatherClock. This item gets two new child items holding all elements to be displayed in states with different visualization: clockScreen item - shows the clock in a larger size in Ofine and Loading weatherScreen item - shows clock and the weather forecast in Live Weather, which is basically the same as we had in the clock-n-weather application As a nal step, we just bind the states of WeatherClock to the values of the WeatherModelItem state:
... Rectangle { id: root
82
... state: forceOffline ? "Offline" : weatherModelItem.state ... states: [ State { name: "Offline" PropertyChanges {target: clockScreen; visible: true} PropertyChanges {target: weatherScreen; visible: false} }, State { name: "Live Weather" PropertyChanges {target: clockScreen; visible: false} PropertyChanges {target: weatherScreen; visible: true} }, State { name: "Loading" PropertyChanges {target: clockScreen; visible: true} PropertyChanges {target: weatherScreen; visible: false} PropertyChanges {target: busyIndicator; on: true} } ] ... }
Our State6 denitions contain PropertyChanges7 items which change the visibility of our new screens and turn on the busyIndicator in the Loading state. The Loading state might be active for quite some time. If the clock does not show seconds, the whole application might appear as if it were hanging. We need a animated busy indicator to show the user that the application is still running. The Qt example RSS News Reader8 provides a very nice one. We take it with very minor modications. Our busyIndicator becomes visible in the Loading state and informs the user that the application is processing data in the background. You may have noticed that we use the new forceOffline setting here which was rst spotted in the last chapter. If forceOffline is set to true, the application will stay in the Ofine state regardless of changes in weatherModelItem. If we now change states, changes will occur instantly. The application will look much more attractive if there were transition and animation effects applied during state changes. We will take a look at this in the next section.
83
Generally, all animations manipulate one or more properties of an element, thereby modifying its visual appearance. This modication can have various dynamics and run in various time spans. There can be numerous animations running in parallel or sequentially applied to the same or to different elements. You can start an animation explicitly or implicitly upon a property change. You can also permanently assign an animation to a property so that an animation starts as soon as a property changes. Although there is a generic Animation9 element, most of the time, you will probably use one of the predened animation elements10 provided in Qt Quick. Its very easy to add animations to an application. The major challenge is to nd out which animations to use and how to use them to compose the required visual effect. Animations are very related to Transitions11 . A Transition12 denes how an element is transformed from one State13 to another. In most cases, a transition includes an animation. Qt documentation provides an overview of all animations and transitions as will other more details how to use them in the QML Animation and Transitions14 article. The code segment below shows two transitions between the Ofine and Live Weather states in our application:
transitions: [ Transition { from: "Offline" to: "Live Weather" PropertyAnimation { target: weatherScreen property: "opacity" from: 0 to: 1 easing.type: Easing.Linear duration: 5000 } }, Transition { from: "Live Weather" to: "Offline" PropertyAnimation { target: clockScreen property: "opacity" from: 0 to: 1 easing.type: Easing.Linear duration: 5000 } } ]
The state changes swap the visibility of the off-line view and the full view with weather data. On top of this, we add an animation which changes the opacity property. This fades a screen out letting it disappear fully in 5 seconds.
http://qt-project.org/doc/qt-4.8/qml-animation.html http://qt-project.org/doc/qt-4.8/qml-animation-transition.html 11 http://qt-project.org/doc/qt-4.8/qml-transition.html 12 http://qt-project.org/doc/qt-4.8/qml-transition.html 13 http://qt-project.org/doc/qt-4.8/qml-state.html 14 http://qt-project.org/doc/qt-4.8/qdeclarativeanimation.html
10 9
84
Note: Theoretically, a slight ickering might be visible on the screen in the beginning of transitions since a target element becomes fully visible rst and immediately after this its opacity is turned to 0 in the beginning of the animation. The functionality of our busy indicator is fully based on animations! There is almost no other code in its implementation: (utils/BusyIndicator.qml Downloads (page 2) section) in qt_quick_app_dev_intro_src.zip, see
// This is taken from the "RSS News" demo provided in Qt // The original code has been modified to adapt to the application structure import QtQuick 1.1 Image { id: root property bool on: false source: "../content/resources/busy.png" visible: root.on NumberAnimation on rotation { running: root.on; from: 0; to: 360; loops: Animation.Infinite; duration: 1200 } }
85
Another animation is used to implement a visual effect in the new layout of the clock and weather items in the reworked main item of our application. This will be discussed in the next section.
86
This is how our application looks on the screen if we resize the main window:
The complete code of the new main item looks like this: (WeatherClock/WeatherClock.qml in qt_quick_app_dev_intro_src.zip, see Downloads (page 2) section)
import QtQuick 1.1 import import import import "../utils" 1.0 "../components" 1.0 "../js/style.js" as Style "../js/logic.js" as Logic
Rectangle { id: root property string defaultLocation: configure.locationText property int defaultInterval: configure.forecastUpdateInterval property bool showSeconds: configure.showSeconds property bool showDate: configure.showDate property bool forceOffline: configure.forceOffline state: forceOffline ? "Offline" : weatherModelItem.state width: 360 height: 640 onStateChanged: { if (state == "Offline") statusText.showStatus ("offline"); else if (state == "Loading") statusText.showStatus ("loading..."); else if (state == "Live Weather") statusText.showStatus ("live weather"); } Image { id: background source: Style.backgroundImage fillMode: "Tile" anchors.fill: parent onStatusChanged: if (background.status == Image.Error) console.log("Background image \"" + source + "\" cannot be loaded") } Dialog { id: errorDialog width: root.width anchors.centerIn: parent z: root.z+1 visible: false } WeatherModelItem { id: weatherModelItem location: root.defaultLocation interval: root.defaultInterval forceOffline: root.forceOffline
88
onModelDataErrorChanged: { if (weatherModelItem.modelDataError) errorDialog.show(weatherModelItem.statusMessage) } } Component { id: weatherCurrentDelegate Weather { id: currentWeatherItem labelText: root.defaultLocation conditionText: model.condition tempText: model.temp_c + "C" } } Component { id: weatherForecastDelegate Weather { id: forecastWeatherItem labelText: model.day_of_week conditionText: model.condition tempText: Logic.f2C (model.high) + "C / " + Logic.f2C (model.low) + "C" } } NightClock { id: clockScreen height: 130 anchors.centerIn: root showDate: root.showDate showSeconds: root.showSeconds textColor: Style.offlineClockTextColor } Flow { id: weatherScreen width: root.width height: root.height anchors.fill: parent anchors.margins: Style.baseMargin spacing: 30 NightClock { id: clock height: 80 width: 190 showDate: root.showDate showSeconds: root.showSeconds textColor: Style.onlineClockTextColor } ListView { id: currentWeatherView
89
width: 100 height: 100 model: weatherModelItem.currentModel delegate: weatherCurrentDelegate interactive: false } Repeater { id: forecastWeatherView model: weatherModelItem.forecastModel delegate: weatherForecastDelegate } move: Transition { NumberAnimation { properties: "x,y" duration: 500 easing.type: Easing.OutBounce } } } Text { id: statusText anchors.horizontalCenter: root.horizontalCenter anchors.bottom: exitButton.top anchors.margins: Style.baseMargin color: Qt.lighter(Style.penColor) font.pixelSize: Style.textPixelSize*0.8 text: qsTr("Status: starting...") function showStatus (newStatusText) { text = qsTr("Status: " + newStatusText); } } // it is off and invisible by default BusyIndicator { id: busyIndicator anchors.horizontalCenter: root.horizontalCenter anchors.bottom: statusText.top anchors.margins: Style.baseMargin } Button { id: configureButton text: qsTr("Config") anchors.left: root.left anchors.bottom: root.bottom anchors.margins: Style.baseMargin onClicked: { configure.visible = true; } } Button {
90
id: exitButton text: qsTr("Exit") width: configureButton.width anchors.right: root.right anchors.bottom: root.bottom anchors.margins: Style.baseMargin onClicked: Qt.quit() }
Button { id: toggleStatesButton anchors.right: exitButton.left anchors.left: configureButton.right anchors.bottom: root.bottom anchors.margins: Style.baseMargin // simple binding like this "text: root.state" works here to, but it is more dii // we use explicit strngs instead text: root.state == "Offline" ? qsTr("Get weather") : qsTr("Go offline") onClicked: { if (root.state == "Offline") configure.forceOffline = false; else configure.forceOffline = true; } // for experimental purposes... // onPressedAtXY: { // console.log ("pressed at: " + coordinates) // } } Configure { id: configure anchors.fill: root z: root.z + 1 visible: false showSeconds: true showDate: true forecastUpdateInterval: 5 locationText: qsTr("Munich") forceOffline: false } states: [ State { name: "Offline" PropertyChanges {target: PropertyChanges {target: }, State { name: "Live Weather" PropertyChanges {target: PropertyChanges {target: }, State { name: "Loading" PropertyChanges {target: PropertyChanges {target:
91
PropertyChanges {target: busyIndicator; on: true} } ] transitions: [ Transition { from: "Offline" to: "Live Weather" PropertyAnimation { target: weatherScreen property: "opacity" from: 0 to: 1 easing.type: Easing.Linear duration: 5000 } }, Transition { from: "Live Weather" to: "Offline" PropertyAnimation { target: clockScreen property: "opacity" from: 0 to: 1 easing.type: Easing.Linear duration: 5000 } } ] }
Whats Next?
Our application is now complete and you have learned major aspects of Qt Quick! Certainly, our nal application can be enhanced and extended with many features. We selected a minimal subset to cover the scope of the guide without going into too many details. The next and the last chapter discuss a few selected enhancements.
92
CHAPTER
ELEVEN
93
Delete the main.qml le created by the wizard in that folder and rename the WeatherClock.qml into main.qml Adapt paths to the new location of the QML component and resources: remove ../ in imports in main.qml remove ../ in front ./js/style.js of the value of backgroundImage in
add ../ + in the front of the source property value of the background item in Configure.qml in the components sub-folder The current layout and sizes are tailored for a 320x480 screen, e.g. in Nokia N8. If your device has another screen resolution, you need to change all size-related properties accordingly. Thats it! You can now compile and run the application! The is how it looks in the Simulator in portrait and landscape modes:
94
We currently keep conguration parameters in the Configure component, which provides a UI as well. All conguration changes are lost when the user quits the application. A much better implementation would be to split the Configure component in a UI element and a conguration item. The latter can be loaded in any other item which needs to access conguration parameters. The user can change conguration parameters via the new UI element. Loading of default values and saving them before the application quits can be done by a dedicated setting item. Persistent storage is provided in Qt Quick by the Ofine Storage APIs1 . The Qt Quick Application Developer Guide for Desktop2 explains this in detail in the 4.2. Store and Load Data from a Database Using Javascript section. When the application starts for the rst time, a set of default values is stored in the database. During the next startup, the values from the database are read and assigned to the appropriate properties of the conguration item. All this can be done in the onCompleted handler in the main item. We can store current conguration parameters before we call Qt.quit() in the exitButton. Internationalization A new version of the application could be available in multiple languages. We already use the qsTr() macro. Google weather data can be queried in multiple languages as well. This can save quite some effort. Unfortunately, there is a small issue in our application concerning this. Our weather icons are named after weather condition names in English. If the weather data will be in another language, icons will not be found with the current implementation, since le names will not match condition names. A possible solition would be to use le names in URLs for default icons referred in the weather data as le names for the local icons. Using Mobility APIs to get the current location automatically Instead of a predened location, we could use Mobility API3 and get the location automatically if the application runs on a mobile device. Using other weather feeds It might be a good idea to support at least one additional weather feed. Most of them require registration and in some cases a fee payment as well if the application is used for commercial purposes. You can consider adding other feeds in your version of the application. You can nd more information about other weather feeds here: 5 Weather APIs From WeatherBug to Weather Channel4 Add Weather To Your Website With Autobrand : Weather Underground5 A Weather API Designed for Developers6
Whats Next?
This is the end of the guide! The next chapter is concludes it!
1 2
95
96
CHAPTER
TWELVE
The main purpose of this guide was to help you to start and show where you can go if you need more details. The guide does not cover all details. This would go far beyond an introduction and would overlap with Qt documentation1 and other resources on the internet. We did not touch one very import point in detail, but at least mentioned it a few times. This is about using C++ to extend Qt Quick and provide interfaces to existing software systems. The following articles in Qt Documentation are a good start to learn more about this: QML for Qt Programmers2
1 2
http://qt-project.org/doc/qt-4.8/index.html http://qt-project.org/doc/qt-4.8/qtprogrammers.html
97
Introduction to Application Development with Qt Quick, Release 1.0 Using QML Bindings in C++ Applications3 Extending QML Functionality using C++4 In addition to the Qt Quick examples provided with Qt, many other interesting examples are included in the Qt training materials: Qt Essentials - Qt Quick Edition5 Qt Quick for Designers6 Good luck and have fun using Qt Quick!
3 4
98
CHAPTER
THIRTEEN
13.1 Introduction
JavaScript is a minimalistic dynamically typed scripting language. It is truly object-oriented, although it lacks support for classes. Frequently associated with client-side web development, JavaScript is a language of its own. Originally developed at Netscape and nowadays standardized as ECMAScript-262 (3rd and 5th edition)2 , the language has found wide-spread use and circulates under various names. JScript is Microsofts derivative of the language. JavaScript was the original name chosen by Netscape when the language was introduced with Netscape 2. Adobes ActionScript was also based on ECMAScript-262 before version 3 was released. Qt has been supporting a JavaScript engine compatible with ECMAScript-262 since Qt 4.3. This engine is called QtScript and was originally an independent implementation. Since Qt 4.5, QtScript has been based on JavaScriptCore from WebKit. Qt Quick makes intense use of QtScript.
1 2
http://qt-project.org/wiki/JavaScript http://www.ecma-international.org/publications/standards/Ecma-262.htm
99
To query the type of a variable, use the typeof keyword. typeof returns the name of the type as a string.
var x = 0; typeof x // number typeof { x: 1 } // object typeof typeof { x : 1 } // string typeof JSON.parse({"a": 7}) // object
Note that in JavaScript the expression 7.toString() cant be interpreted correctly. 7. is parsed into a number and thereafter results in a syntax error. The primitive types boolean, number and string are implicitly converted into objects when needed. For this purpose, the global object provides special constructor functions which can also be invoked manually:
typeof 1. // number typeof new Number(1.) // object typeof new String(Hi!) // object
Functions are special kinds of objects. They only differ from objects because they can be called and used as constructors. Properties can be added to functions dynamically:
function f(x) { return f.A * x * x } f.A = 2.7 function Buffer() {} Buffer.MAX_LINE_LENGTH = 4096
Usually those properties serve as global constants and therefore are written in uppercase. Objects themselves can be expressed using an array or object literal. Arrays have no separate type, but are specialized objects which use array indexes as properties: 100 Chapter 13. Annex. JavaScript Language Overview
var o = { name: Werner, age: 84 } // allocate simple object print(o.name, o[age]) // both notations are valid, but [] notation allows generated strings var a = [a, b, 7, 11.] // an array, equivalent to {0: a, 1: b, 2: 7, 3: 11.} typeof o, a // object, object
13.3 Expressions
The expression syntax follows mostly C syntax (as in C++ or Java). As a major difference, there is no sharp distinction between statements and expressions. Basically everything evaluates to something. Function declarations and compounds can be included on-the-y:
function f() {} // evaluates undefined function f() {} + 1 // evaluates to 1, because undefined is casted to 0 (function() {}) // evaluates to a function object (function() { return 0; })() // evaluates to 0
13.4 Branching
Conditional branches follow C syntax.
if (<expression>) <statement1> else // optional <statement2> // optional
13.3. Expressions
101
... do <statement> while (<expression>) ... while (<expression>) <statement> ... for (<init-expression>;<test-expression>;<step-expression>) <statement> ...
The given expression needs to be suitable for the left-hand side of an assignment. In the simplest case, it is just a variable declaration. Consider the following example:
var a = [1,2,3,4] for (var i in a) print(i, a[i]*a[i]) // 0, 1 // 1, 4 // 2, 9 // 3, 16
Here the variable i is assigned to all keys of the array a consecutively. In the next example, the left-hand expression is dynamically generated:
var o = {a0: 11, a1: 7, a2: 5} var k = [] for(k[k.length] in o);
The keys of o are copied to k. The loop statement itself is left empty. For each member in o, the name is assigned to another member of k.
Objects are entirely dynamic sets of properties. New properties are introduced on rst assignment. They can be deleted again by using the delete operator. To query if an object contains a certain property, use the in operator.
z in p // false p.z = 0.3 // introduce new property z z in p // true delete p.z // remove z from p p.z // undefined
Property values can be of any type - including the function type. Methods in JavaScript are just function properties. When a function is invoked in method notation, it gets passed a reference to the object as an implicit argument called this.
p.move = function(x, y) { this.x = x this.y = y } p.move(1, 1) // invoke a method
JavaScript allows any function to be called as a method of any object by using the call method, however, there are only a few cases in which you would want to use the call method.
p2 = { x: 0, y: 0 } p.move.call(p2, 1, 1)
The new operator allocates a new object and calls the given constructor to initialize the object. In this case, the constructor is called Object, but it could be any other function as well. The constructor function gets passed the newly created object as the implicit this argument. In JavaScript there are no classes, but hierarchies of constructor functions which operate like object factories. Common constructor functions are written with a starting capital letter to distinguish them from average functions. The following example shows how to create point coordinates using a constructor function:
function Point(x, y) { this.x = x this.y = y } var p = new Point(1, 2)
103
Each function in JavaScript can be used as a constructor in combination with the new operator. To support inheritance, each function has a default property named prototype. Objects created from a constructor inherit all properties from the constructors prototype. Consider the following example:
function Point(x, y) { this.x = x this.y = y } Point.prototype = new Object // can be omitted here Point.prototype.translate = function(dx, dy) { this.x += dx this.y += dy }
First we declared a new function called Point which is meant to initialize a point. Thereafter we create our own prototype object, which in this case is redundant. The prototype of a function already defaults to an empty object. Properties which should be shared among all points are assigned to the prototype. In this case, we dene the translate function which moves a point by a certain distance. We can now instantiate points using the Point constructor:
var p0 = new Point(1, 1) var p1 = new Point(0, 1) p1.translate(1, -1) p0 === p1 // false p0.translate === p1.translate // true
The p0 and p1 objects carry their own x and y properties, but they share the translate method. Whenever an objects property value is requested by name, the underlying JavaScript engine rst looks into the object itself and, if it doesnt contain that name, it falls back to the objects prototype. Each object carries a copy of its constructors prototype for this purpose. If an object actually contains a certain property, or if it is inherited, it can be inquired using the Object.hasOwnProperty() method.
p0.hasOwnProperty("x") // true p0.hasOwnProperty("translate") // false
So far, we have only dened a single constructor with no real object hierarchy. We will now introduce two additional constructors to show how to chain prototypes and thereby build up more complex relationships between objects:
function Point(x, y) { this.x = x this.y = y } Point.prototype = { move: function(x, y) { this.x = x this.y = y }, translate: function(dx, dy) { this.x += dx this.y += dy
104
}, area: function() { return 0; } } function Ellipsis(x, y, a, b) { Point.call(this, x, y) this.a = a this.b = b } Ellipsis.prototype = new Point Ellipsis.prototype.area = function() { return Math.PI * this.a * this.b; } function Circle(x, y, r) { Ellipsis.call(this, x, y, r, r) } Circle.prototype = new Ellipsis
Here we have three constructors which create points, ellipsis and circles. For each constructor, we have set up a prototype. When a new object is created using the new operator, the object is given an internal copy of the constructors prototype. The internal reference to the prototype is used when resolving property names which are not directly stored in an object. Thereby properties of the prototypes are reused among the objects created from a certain constructor. For instance, let us create a circle and call its move method:
var circle = new Circle(0, 0, 1) circle.move(1, 1)
The JavaScript engine rst looks into the circle object to see if it has a move property. As it cant nd one, it asks for the prototype of circle. The circle objects internal prototype reference was set to Circle.prototype during construction. It was created using the Ellipsis constructor, but it doesnt contain a move property either. Therefore, the name resolution continues with the prototypes prototype, which is created with the Point constructor and contains the move property, whereby the name resolution succeeds. The internal prototype references are commonly referred to as the prototype chain of an object. To query information about the prototype chain, JavaScript provides the instanceof operator.
circle circle circle circle circle instanceof instanceof instanceof instanceof instanceof Circle // true Ellipsis // true Point // true Object // true Array // false, is not an Array
As properties are introduced when they are rst assigned, properties delivered by the prototype chain are overloaded when newly assigned. The Object.hasOwnProperty method and the in operator allow the place where a property is stored to be investigated.
circle.hasOwnProperty("x") // true, assigned by the Point constructor circle.hasOwnProperty("area") // false "area" in circle // true
As can be seen, the in operator resolves names using the prototype chain, while the Object.hasOwnProperty only looks into the current object.
105
In most JavaScript engines, the internal prototype reference is called __proto__ and is accessible from the outside. In our next example, we will use the __proto__ reference to explore the prototype chain. Because this property is non-standard, you should avoid using it in all other contexts. First let us dene a function to inspect an object by iterating its members:
function inspect(o) { for (var n in o) if (o.hasOwnProperty(n)) print(n, "=", o[n]); }
The inspect function prints all members stored in an object so if we now apply this function to the circle object as well as to its prototypes, we obtain the following output:
js> inspect(circle) x = 1 y = 1 a = 1 b = 1 js> inspect(circle.__proto__) x = undefined y = undefined a = undefined b = undefined js> inspect(circle.__proto__.__proto__) x = undefined y = undefined area = function () { return Math.PI * this.a * this.b; } js> inspect(circle.__proto__.__proto__.__proto__) move = function (x, y) { this.x = x this.y = y; } translate = function (dx, dy) { this.x += dx this.y += dy; } area = function () { return 0; } js> inspect(circle.__proto__.__proto__.__proto__.__proto__) js>
As you can see, the move method is actually stored in circle.__proto__.__proto__.__proto__. You can also see a lot of redundant undened members, but this shouldnt cause you any concern as prototype objects are shared among instances.
http://qt-project.org/doc/qt-4.8/ecmascript.html http://qt-project.org/doc/qt-4.8/qdeclarativeglobalobject.html
106
Usually, the global object can be referenced from the global scope by explicitly using the this keyword. The value of this is currently undened in Qt Quick in the majority of contexts. See QML JavaScript Restrictions in Integrating JavaScript5 in Qt documentation. Further scopes are created on-demand whenever a function is called. Scopes are destroyed as any other object when they are no longer needed. When a function is dened, the enclosing scope is kept with the function denition and used as the parent scope for the function invocation scope. The new scope that is created upon function invocation is commonly referred to as the activation object. The scope in which functions are dened is commonly referred to as the lexical scope. The following example shows how to use lexical scopes to hide private members:
function Point(x, y) { this.getX = function() { this.setX = function(x2) this.getY = function() { this.setY = function(y2) } return x; { x = x2; return y; { y = y2; } } } }
When the Point constructor is invoked, it creates get and set methods. The newly generated scope for the invocation of the Point constructor carries the x and y members. The getters and setters reference this scope and therefore it will be retained for the lifetime of the newly created object. Interestingly there is no other way to access x and y other than via the set and get methods. This way JavaScript supports data encapsulation. The concept of a function referencing the enclosing scope and retaining it for the lifetime of the function is commonly called a closure. Low-level programming languages like C do not support closures because local scopes are created using stack frames and therefore need to be destroyed when the function returns.
13.10 Namespaces
Functions play a pivotal role in JavaScript. They serve as simple functions, methods, constructors and are used to encapsulate private properties. Additionally functions serve as anonymous namespaces:
(function() { // my code var smth = new Smth other = [1,2,3] Array = function() {} }) () var smthElse = {}
// safe // bad, goes into global scope // forbidden // bad, goes into global scope
An anonymous function is dened and executed on-the-y. Global initialization code in particular is commonly wrapped in such a way to prevent polluting the global scope. As the global object can be modied as any other object in JavaScript, wrapping code in such a way reduces the risk of accidentally overwriting a global variable. To ensure that it actually works, all variables need to be duly introduced using the var statement.
5
http://qt-project.org/doc/qt-4.8/qdeclarativejavascript.html
13.10. Namespaces
107
Named namespaces can also be created with functions. If for instance we wanted to write a utility library for painting applications, we could write:
function PaintUtil() { PaintUtil.Point = function(x, y) { this.move = function(x2, y2) { x = x2; y = y2 } this.getX = function() { return x; } this.getY = function() { return y; } } // Ellipsis, Circle, other painting utility methods } PaintUtil()
Once this little library module is executed, it will provide the single PaintUtil object which makes the utility functions accessible. A point can be instantiated using the constructor provided by PaintUtil as follows:
var p = new PaintUtil.Point(0.1, 0.2)
Reusable JavaScript modules should only introduce a single global object with a distinguishable name.
13.12 Exceptions
JavaScript provides an exception handling mechanism like most other high-level languages. Exceptions are thrown using the throw statement. Any value can be used as an exception object:
108
throw <expression>;
When an exception is thrown, JavaScript unwinds the current scope until it arrives at a try-catch scope:
try { <statement-list> } catch (<name for exception object>) { // handle exception } finally { // always go through here }
The name of the exception object is only locally dened inside the catch scope. Exceptions can be re-thrown.
13.13 Resources
Useful web links: The JavaScript Reference6 on the Mozilla Developer Network JavaScript. The core. by Dmitry A. Soshnikov7 Changes to JavaScript: EcmaScript 5 by Mark Miller8 - a video from Google Tech Talk, May 18, 2009 Standard ECMA-2629 - PDF download of the ofcial standard Recommended Books: JavaScript: The Good Parts by Douglas Crockford10 Part I - Core JavaScript in JavaScript: The Denitive Guide by David Flanagan11 genindex
13.13. Resources
109