Now that our application is becoming more complete, we need to add some functionality to make it usable on a daily basis. First of all, we need a real button to quit and stop using the entire application window for this. Additionally, we need a new top-level window where we can manage configuration settings. When the user changes settings, the application should verify changes and let the user know if something is wrong. To implement this, we need some basic UI elements.
The first thing is to create a button component, which will be used to quit the application, open the configuration window, close it, and so on. 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 we’ll just describe one which suits our needs.
Our button can be a simple click-sensitive rectangle with rounded corners. In previous sections, we saw that an element can receive mouse events if we include a MouseArea [http://qt-project.org/doc/qt-4.8/qml-mousearea.html] element and let it fill 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 need to use Qt Quick signals to implement this. Let’s take a look at how they work first.
We already got in touch with a related Qt Quick functionality when we saw that it is possible to implement a handler which reacts to property changes, for example, the status property of Image [http://qt-project.org/doc/qt-4.8/qml-image.html]:
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"))
}
Signals are very similar to the property notification 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 a function call.
This is how it works for our Button component:
(src/utils/Button.qml in qt_quick_app_dev_intro_src.zip, see Downloads 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
Connections {
onPressed: logPresses(mouse)
}
// this works as well instead of using Connections
// onPressed: logPresses(mouse)
}
}
Button defines two signals: clicked and pressedAtXY. We only use clicked in our application, and 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 for mouseArea above, you might wonder where the mouse parameter comes from. We did not declare it in our application. It actually belongs to the definition of clicked signal of the MouseArea [http://qt-project.org/doc/qt-4.8/qml-mousearea.html] element. The same happens with our pressedAtXY signal, which defines a coordinates parameter. All items using Button and processing the pressedAtXY signal has to access its parameter under the exact name. For example:
Button {
id: toggleStatesButton
...
onPressedAtXY: {
console.log ("pressed at: " + coordinates)
}
}
Note that we define the clicked signal as:
signal clicked (variant mouse)
We do this even though mouse is of the MouseEvent [http://qt-project.org/doc/qt-4.8/qml-mouseevent.html] type (according to the documentation for MouseArea [http://qt-project.org/doc/qt-4.8/qml-mousearea.html]). In the current version of Qt Quick, signal parameters can only be of basic types [http://qt-project.org/doc/qt-4.8/qdeclarativebasictypes.html]. This should not concern you as the type is converted to the appropriate type when it arrives.
For more details about using signals in Qt Quick, see the QML Signal and Handler Event System [http://qt-project.org/doc/qt-4.8/qmlevents.html] article in Qt documentation. You should also check the documentation of MouseArea [http://qt-project.org/doc/qt-4.8/qml-mousearea.html] as well as the QML Mouse Events [http://qt-project.org/doc/qt-4.8/mouseevents.html] article to discover more possibilities such as getting other mouse events, tracing hovering, and implementing drag-drop.
As any proper button, our Button should provide some visual feedback when it is clicked. We do this by tweaking its colors a bit. We have a small JavaScript function which modifies the color value to a new, pressed value. It makes the color darker in our 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)
Then we bind it to a color property of its label text:
color: pressedColor(Style.penColor)
That’s it! This is how our Button looks when unpressed:
and pressed:
The Dialog is another utility component that we need. We use it to notify the user about critical situations. Our Dialog is very simple. It pops up on top of another element and just displays a text message that must be confirmed by clicking the OK button. This is the code for the new Dialog component:
(src/utils/Dialog.qml in qt_quick_app_dev_intro_src.zip, see Downloads section)
import QtQuick 1.1
import "../js/style.js" as Style
Rectangle {
id: root
property string message: "Error! This is a long message with details"
width: 100
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.baseMargin
}
}
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
}
...
Button {
id: exitButton
...
onClicked: {
...
errorDialog.show (qsTr("The location cannot be empty"));
...
}
}
...
}
When loaded, the Dialog initially stays invisible. It goes on top of 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 Example [http://qt-project.org/doc/qt-4.8/declarative-ui-components-tabwidget.html] 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 efficiently, 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 of the root 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
}
...
}
This is how it looks on the screen:
We can use the Text Input [http://qt-project.org/doc/qt-4.8/qml-textinput.html] element to get text or digit based user input, but we need something else for on-off type of settings. Usually, this is done using the 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 as we can easily create one using Qt Quick. This is the complete code for our new CheckBox component:
(src/utils/CheckBox.qml in qt_quick_app_dev_intro_src.zip, see Downloads 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)
}
Our CheckBox is based on Item [http://qt-project.org/doc/qt-4.8/qml-item.html]. 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 flipped back and forth. This is done by binding the source property of the checkBox Image [http://qt-project.org/doc/qt-4.8/qml-image.html] 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 when checked and unchecked:
Further on, there is a section of code, which includes the keyboard navigation handling. This topic will be discussed in the next section.