Modern mobile applications with Qt and QML: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
m (remove cleanup notice after cleaning up)
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
[toc align_right="yes" depth="3"]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Learning]]
[[Category:Learning]]


'''English''' ["French":http://qt-devnet.developpez.com/tutoriels/qt-quick/qml/applications-mobiles-modernes/]
'''English''' [[http://qt-devnet.developpez.com/tutoriels/qt-quick/qml/applications-mobiles-modernes/ French]]


= Modern Mobile Applications with Qt and QML =
= Modern Mobile Applications with Qt and QML =
Line 13: Line 12:
The User interface will be written with QML; the backbone is written in Qt C+''.
The User interface will be written with QML; the backbone is written in Qt C+''.


h2. Create new project
== Create new project ==
 
If you do not have Qt already installed, visit [http://qt.nokia.com/downloads/ Qt download page] to download Qt SDK for your platform and install it.
If you do not have Qt already installed, visit "Qt download page":http://qt.nokia.com/downloads/ to download Qt SDK for your platform and install it.


Launch Qt Creator, select '''File''' -> '''New File or Project'''. In the new dialog select '''Qt C++ Project'''-> '''Qt Gui Application''', and then click '''Choose…'''  
Launch Qt Creator, select '''File''' -> '''New File or Project'''. In the new dialog select '''Qt C++ Project'''-> '''Qt Gui Application''', and then click '''Choose…'''  
Line 35: Line 33:
The application skeleton is ready, now we can proceed to code the logic and design the UI.
The application skeleton is ready, now we can proceed to code the logic and design the UI.


h2. Code core logic in C''+
== Code core logic in C''+ ==
 
First we need to add the [http://doc.qt.nokia.com/4.7/qtdeclarative.html ''qt-declarative''] module to our project. This is the module that provides a widget in which a QML interface is displayed.
First we need to add the "''qt-declarative''":http://doc.qt.nokia.com/4.7/qtdeclarative.html module to our project. This is the module that provides a widget in which a QML interface is displayed.


Open project file (''4Toddler.pro'') and append the line
Open project file (''4Toddler.pro'') and append the line


<code>QT ''= core gui<code>
<code>QT ''= core gui</code>


with ''declarative''
with ''declarative''


</code>QT''= core gui declarative</code>
<code>QT''= core gui declarative</code>


Now we need to change the base class for our main window. Replace ''QMainWindow'' with ''QDeclarativeView'' and include ''QDeclarativeView''
Now we need to change the base class for our main window. Replace ''QMainWindow'' with ''QDeclarativeView'' and include ''QDeclarativeView''
Line 53: Line 50:
class MainWindow : public QDeclarativeView
class MainWindow : public QDeclarativeView
{
{
 
}
}
</code>
</code>
Line 62: Line 59:
MainWindow::MainWindow(QWidget *parent)
MainWindow::MainWindow(QWidget *parent)
{
{
Init();
  Init();
}
}
</code>
</code>
Line 89: Line 86:
Rectangle
Rectangle
{
{
// ID of this element. Using this ID we can access the element and its properties from other elements
  // ID of this element. Using this ID we can access the element and its properties from other elements
id: canvas
  id: canvas
// Background color, black in this case
  // Background color, black in this case
color: "black"
  color: "black"
// Change sizes of this element to fill the parent
  // Change sizes of this element to fill the parent
anchors.fill: parent
  anchors.fill: parent
// Element can receive focus
  // Element can receive focus
focus: true
  focus: true
}
}
</code>
</code>
Line 108: Line 105:
We need to add some initialization code for our QML interface by defining a new method inside the '''mainwindow.h''' file …
We need to add some initialization code for our QML interface by defining a new method inside the '''mainwindow.h''' file …


<code>void Init();<code>
<code>void Init();</code>


.. And implementing it in the '''mainwindow.cpp''' file
.. And implementing it in the '''mainwindow.cpp''' file


</code>
<code>
void MainWindow::Init()
void MainWindow::Init()
{
{
// Path to the content folder
  // Path to the content folder
QString contentPath;
  QString contentPath;


#ifdef QT_DEBUG
  #ifdef QT_DEBUG
// In the debug version of our project this is a path to the project folder
  // In the debug version of our project this is a path to the project folder
contentPath = "D:/MyProjects/QT/4Toddler";
  contentPath = "D:/MyProjects/QT/4Toddler";
#else
  #else
// In the release version - path to the application folder
  // In the release version - path to the application folder
contentPath = QApplication::applicationDirPath();
  contentPath = QApplication::applicationDirPath();
#endif
  #endif
setFocusPolicy(Qt::StrongFocus);
  setFocusPolicy(Qt::StrongFocus);
// Change QML document sizes to fit the main window
  // Change QML document sizes to fit the main window
setResizeMode(QDeclarativeView::SizeRootObjectToView);
  setResizeMode(QDeclarativeView::SizeRootObjectToView);
// Load QML file
  // Load QML file
setSource(QUrl::fromLocalFile(contentPath + "/main.qml"));
  setSource(QUrl::fromLocalFile(contentPath + "/main.qml"));
}
}
<code>
</code>


Now we need to replace one line in the '''main.cpp''' file
Now we need to replace one line in the '''main.cpp''' file


</code>
<code>
int main(int argc, char *argv[])
int main(int argc, char *argv[])
{
{
 
w.show();
  w.show();
 
}
}
<code>
</code>


With
With


</code>
<code>
int main(int argc, char *argv[])
int main(int argc, char *argv[])


{
{
 
w.showFullScreen();
  w.showFullScreen();
 
}
}
<code>
</code>


This will force our window to open in full-screen mode.
This will force our window to open in full-screen mode.
Line 160: Line 157:
== Structure code with components ==
== Structure code with components ==


Before we launch our application, let's add two buttons. One is to display '''About''' dialog and the second to '''Close''' our window. We do not need to implement buttons twice, we going to create a "component":http://doc.qt.nokia.com/4.7-snapshot/qml-extending-types.html and use it every time we need to add a new button by overriding just a few properties.
Before we launch our application, let's add two buttons. One is to display '''About''' dialog and the second to '''Close''' our window. We do not need to implement buttons twice, we going to create a [http://doc.qt.nokia.com/4.7-snapshot/qml-extending-types.html component] and use it every time we need to add a new button by overriding just a few properties.


Let's add the new Qml file - '''WindowButton.qml''' to our project. ''Note the filename begins with a capital letter — this signifies it defines a QML component.''
Let's add the new Qml file - '''WindowButton.qml''' to our project. ''Note the filename begins with a capital letter — this signifies it defines a QML component.''


</code>
<code>
Image
Image
{
{
// ID of this element
  // ID of this element
id: button
  id: button
// The MouseArea item enables simple mouse handling
  // The MouseArea item enables simple mouse handling
MouseArea
  MouseArea
{
  {
anchors.fill: parent
    anchors.fill: parent
id: mouseArea
    id: mouseArea
// On click call callback() handler
    // On click call callback() handler
onClicked: callback()
    onClicked: callback()
}
  }
}
}
<code>
</code>


Now we can add two buttons to our window
Now we can add two buttons to our window


</code>
<code>
// Place items in row
// Place items in row


Row
Row
{
{
// Right side of the item is anchored to the right side of the parent element
  // Right side of the item is anchored to the right side of the parent element
anchors.right: parent.right
  anchors.right: parent.right
// Right margin
  // Right margin
anchors.rightMargin: 4
  anchors.rightMargin: 4
// Top side of the item is anchored to the top side of the parent element
  // Top side of the item is anchored to the top side of the parent element
anchors.top: parent.top
  anchors.top: parent.top
// Top margin
  // Top margin
anchors.topMargin: 4
  anchors.topMargin: 4
// Margins for children elements
  // Margins for children elements
spacing: 4
  spacing: 4
WindowButton
  WindowButton
{
  {
// Button to display the About dialog
    // Button to display the About dialog
id: about
    id: about
// Path to background picture. This is a relative path to the path of this QML file
    // Path to background picture. This is a relative path to the path of this QML file
source: "about.png"
    source: "about.png"
// Callback method, which will be called on mouse click
    // Callback method, which will be called on mouse click
// onClicked: callback()
    // onClicked: callback()
function callback()
    function callback() {}
{
  }
}
  WindowButton
}
  {
WindowButton
    // Button to close window
{
    id: exit
// Button to close window
    source: "exit.png"
id: exit
    function callback() {}
source: "exit.png"
  }
function callback()
{
}
}
}
}
<code>
</code>


== Communicate between QML and C++ ==
== Communicate between QML and C++ ==
Line 227: Line 220:
Let's add a new function to the '''mainwindow.h''' header file
Let's add a new function to the '''mainwindow.h''' header file


</code>Q_INVOKABLE void Quit();</code>
<code>Q_INVOKABLE void Quit();</code>


And implementation in '''mainwindow.cpp''' file
And implementation in '''mainwindow.cpp''' file
Line 233: Line 226:
<code>
<code>
void MainWindow::Quit()
void MainWindow::Quit()
{
{
 
  QApplication::quit();
QApplication::quit();
 
}
}
</code>
</code>
Line 243: Line 233:
Now we need to "tell" QML about this method. Inside '''Init''' we should to add just one line:
Now we need to "tell" QML about this method. Inside '''Init''' we should to add just one line:


<code>rootContext()->setContextProperty("window", this);<code>
<code>rootContext()->setContextProperty("window", this);</code>


Now we can access '''window''' object's functions declared as '''Q_INVOKABLE''' from QML. '''Window''' here is just an example, you could use any object name you want.
Now we can access '''window''' object's functions declared as '''Q_INVOKABLE''' from QML. '''Window''' here is just an example, you could use any object name you want.
Line 249: Line 239:
Let's add '''callback()''' function implementation to close button
Let's add '''callback()''' function implementation to close button


</code>
<code>
function callback()
function callback()
{
{
window.Quit();
  window.Quit();
}
}
<code>
</code>
 
h2. Visualize state and add animation


== Visualize state and add animation ==
Ok, the application can be launched. Launch it and click the '''Close''' button. You see? State of the button is not changed after click; it looks like the button is just inactive. Let's add state changes for normal and clicked states.
Ok, the application can be launched. Launch it and click the '''Close''' button. You see? State of the button is not changed after click; it looks like the button is just inactive. Let's add state changes for normal and clicked states.


</code>
<code>
Image
Image
{
{
 
states:[
  states:[
State
    State
{
    {
// Name of the state
      // Name of the state
name: "hovered"
      name: "hovered"
// When condition. Item will go to this state if condition is true
      // When condition. Item will go to this state if condition is true
// In this case on mouse click
      // In this case on mouse click
when: mouseArea.pressed
      when: mouseArea.pressed
// Which properties will be changed in that state
      // Which properties will be changed in that state
// In this case- opacity
      // In this case- opacity
PropertyChanges { target: button; opacity: 1}
      PropertyChanges { target: button; opacity: 1}
},
    },
 
    State
State
    {
{
      name: "normal"
name: "normal"
      // This state will be activated if mouse button is not pressed
// This state will be activated if mouse button is not pressed
      when: mouseArea.pressed == false
when: mouseArea.pressed == false
      PropertyChanges { target: button; opacity: 0.7; }
PropertyChanges { target: button; opacity: 0.7; }
    }
}
  ]
]
}
}
<code>
</code>


Item will change state if the condition described in ''when'' property is true. You could change state manually by assigning state property.
Item will change state if the condition described in ''when'' property is true. You could change state manually by assigning state property.
Let's launch the application again. Ok, this time it looks more active. We can make our button nicer by adding some animation.
Let's launch the application again. Ok, this time it looks more active. We can make our button nicer by adding some animation.


</code>
<code>
Image
Image
{
{
 
Behavior on opacity
  Behavior on opacity
{
  {
// Animaion step is a 100 milliseconds
    // Animaion step is a 100 milliseconds
// On every iteration opacity will increase or descrease with step equals to 0,1
    // On every iteration opacity will increase or descrease with step equals to 0,1
NumberAnimation { duration: 100 }
    NumberAnimation { duration: 100 }
}
  }
}
}
<code>
</code>


The ''Behavior'' is a simple and flexible way to create animations. This element allows you to specify a default animation for a property change.
The ''Behavior'' is a simple and flexible way to create animations. This element allows you to specify a default animation for a property change.
Line 314: Line 302:
Let's add '''About.qml''' file to our project.
Let's add '''About.qml''' file to our project.


</code>
<code>
// Parent element for dialog window
// Parent element for dialog window
Rectangle
Rectangle
{
{
id: about
  id: about
// Function to display dialog window
  // Function to display dialog window
// This function just changes opacity of the main element to 1
  // This function just changes opacity of the main element to 1
function show()
  function show() {
{
    about.opacity = 1
about.opacity = 1
  }
}
  // Function to hide dialog window
// Function to hide dialog window
  // This function just changes opacity of the main element to 0
// This function just changes opacity of the main element to 0
  function hide() {
function hide()
    about.opacity = 0
{
  }
about.opacity = 0
  // Transparent background
}
  color: "transparent"
// Transparent background
  // Opacity is 0 by default, dialog not visible
color: "transparent"
  opacity: 0
// Opacity is 0 by default, dialog not visible
  // Anchors to parent width and height.
opacity: 0
  width: parent.width
// Anchors to parent width and height.
  height: parent.height
width: parent.width
  // Element is visible if opacity > 0
height: parent.height
  // opacity > 0
// Element is visible if opacity > 0
  visible: opacity > 0
// opacity > 0
  // Child element to create semitransparent background
visible: opacity > 0
  Rectangle
// Child element to create semitransparent background
  {
Rectangle
    anchors.fill: parent
{
    opacity: 0.5
anchors.fill: parent
    color: "gray"
opacity: 0.5
  }
color: "gray"
  // Body of the dialog window
}
  Rectangle
// Body of the dialog window
  {
Rectangle
    id: dialog
{
    // Fixed width and height
id: dialog
    width: 360
 
    height: 230
// Fixed width and height
    // To make dialog centered inside parent we need to calculate its x and y coordinates
width: 360
    x: parent.width / 2 - dialog.width / 2
height: 230
    y: parent.height / 2 - dialog.height / 2
// To make dialog centered inside parent we need to calculate its x and y coordinates
    // Place dialog on top of other elements
x: parent.width / 2 - dialog.width / 2
    z: 10
y: parent.height / 2 - dialog.height / 2
    border.color: "gray"
// Place dialog on top of other elements
    Text
z: 10
    {
border.color: "gray"
      text: "4 Toddler"
Text
      font.bold: true
{
      font.pixelSize: 22
text: "4 Toddler"
      anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
      anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
    }
 
  }
anchors.horizontalCenter: parent.horizontalCenter
  Behavior on opacity
anchors.verticalCenter: parent.verticalCenter
  {
}
    NumberAnimation { duration: 100 }
}
  }
Behavior on opacity
  MouseArea
{
  {
NumberAnimation { duration: 100 }
    anchors.fill: parent
}
    // Hide dialog on mouse click
MouseArea
    onClicked: hide()
{
  }
anchors.fill: parent
// Hide dialog on mouse click
onClicked: hide()
}
}
}
<code>
</code>


Take a look at the line
Take a look at the line


</code>visible: opacity > 0</code>
<code>visible: opacity > 0</code>


As you can see, property can be assigned and can be calculated.
As you can see, property can be assigned and can be calculated.
Line 396: Line 380:
Rectangle
Rectangle
{
{
id: canvas
  id: canvas
..
  ..
About
  About
{
  {
id: aboutDlg
    id: aboutDlg
}
  }
}
}
</code>
</code>
Line 407: Line 391:
And '''callback()''' function implementation
And '''callback()''' function implementation


<code>aboutDlg.show();<code>
<code>aboutDlg.show();</code>


to
to


</code>
<code>
WindowButton
WindowButton
{
{
id: about
  id: about
 
function callback()
  function callback()
{
  {
aboutDlg.show()
    aboutDlg.show()
}
  }
}
}
<code>
</code>


Finally we need to implement the main functionality for our application.
Finally we need to implement the main functionality for our application.
Line 427: Line 411:
The element to display a random icon on the screen will be an '''Image''' element. Let's add a new file for the new element - ''Block.qml''
The element to display a random icon on the screen will be an '''Image''' element. Let's add a new file for the new element - ''Block.qml''


</code>
<code>
Image
Image
{
{
id: block;
  id: block;
// New custom properties to change visibility of this element
  // New custom properties to change visibility of this element
property bool remove: false
  property bool remove: false
property bool show: false
  property bool show: false
opacity: 0
  opacity: 0
fillMode: Image.Stretch
  fillMode: Image.Stretch
states: [
  states: [
State
    State
{
    {
// State is used to remove element from the screen and destroy it
      // State is used to remove element from the screen and destroy it
name: "remove"; when: remove  true
      name: "remove"; when: remove  true
       PropertyChanges { target: block; opacity: 0 }
       PropertyChanges { target: block; opacity: 0 }
       StateChangeScript { script: block.destroy(1000) }
       StateChangeScript { script: block.destroy(1000) }
Line 448: Line 432:
       // State is used to display element
       // State is used to display element
       name: "show"; when: show  true
       name: "show"; when: show  true
PropertyChanges { target: block; opacity: 1 }
      PropertyChanges { target: block; opacity: 1 }
}
    }
]
  ]
Behavior on opacity { NumberAnimation { duration: 300 } }
  Behavior on opacity { NumberAnimation { duration: 300 } }
}
}
<code>
</code>


Now we need to implement the keyboard handler. Handler will be implemented with '''JavaScript'''. Let's add new ''main.js'' to our project.
Now we need to implement the keyboard handler. Handler will be implemented with '''JavaScript'''. Let's add new ''main.js'' to our project.


</code>// Template for new elements
<code>
// Template for new elements
var component = Qt.createComponent("block.qml")
var component = Qt.createComponent("block.qml")
// Max number of items on the screen
// Max number of items on the screen
Line 466: Line 451:
function handleKey()
function handleKey()
{
{
// x coordinate is a random number from 0 to window with in pixels
  // x coordinate is a random number from 0 to window with in pixels
var x = Math.floor(Math.random() * canvas.width)
  var x = Math.floor(Math.random() * canvas.width)
// y coordinate is a random number from 0 to window height in pixels
  // y coordinate is a random number from 0 to window height in pixels
var y = Math.floor(Math.random() * canvas.height)
  var y = Math.floor(Math.random() * canvas.height)
// This function will create a new element on each call with random x and y coordinates
  // This function will create a new element on each call with random x and y coordinates
createNewBlock(x, y)
  createNewBlock(x, y)
}
}
// Function to create a new element
// Function to create a new element
function createNewBlock(x, y)
function createNewBlock(x, y)
{
{
if(component.status != Component.Ready)
  if(component.status != Component.Ready) {
{
    return false
return false
  }
}
  // Remove items if number of items is more than maxBlocksCount
// Remove items if number of items is more than maxBlocksCount
  if(blocksArray.length > maxBlocksCount) {
if(blocksArray.length > maxBlocksCount)
    removeAllBlocks()
{
  }
removeAllBlocks()
  var newBlock = component.createObject(canvas)
}
  if(newBlock == null) {
var newBlock = component.createObject(canvas)
    return false
if(newBlock == null)
  }
{
  // Path to image is available via randomIcon property of the main window
return false
  var iconFile = window.randomIcon
}
  newBlock.source = ("Icons/" + iconFile)
// Path to image is available via randomIcon property of the main window
  newBlock.x = x
var iconFile = window.randomIcon
  newBlock.y = y
newBlock.source = ("Icons/" + iconFile)
  // Change state to show
newBlock.x = x
  newBlock.show = true
newBlock.y = y
  blocksArray.push(newBlock)
// Change state to show
  // Play random sound effect
newBlock.show = true
  window.PlaySound()
blocksArray.push(newBlock)
  return true
// Play random sound effect
window.PlaySound()
return true
}
}
// Function do remove all existings items
// Function do remove all existings items
function removeAllBlocks()
function removeAllBlocks() {
{
  for(var i = 0; i < blocksArray.length; +''i) {
for(var i = 0; i < blocksArray.length; +''i)
    blocksArray[i].remove = true
{
  }
blocksArray[i].remove = true
  while(blocksArray.length != 0) {
}
    blocksArray.pop()
while(blocksArray.length != 0)
  }
{
blocksArray.pop()
}
}
}
<code>
</code>


As you see in code above we should implement new property '''randomIcon''' and method '''PlaySound'''.
As you see in code above we should implement new property '''randomIcon''' and method '''PlaySound'''.
Line 520: Line 499:
Let's add property declaration and method to access it to our ''mainwindow.h'' file.
Let's add property declaration and method to access it to our ''mainwindow.h'' file.


</code>Q_PROPERTY(QString randomIcon READ RandomIcon)</code>
<code>Q_PROPERTY(QString randomIcon READ RandomIcon)</code>


<code>QString RandomIcon();<code>
<code>QString RandomIcon();</code>


Implementation in ''mainwindow.cpp'' file
Implementation in ''mainwindow.cpp'' file


</code>
<code>
QString MainWindow::RandomIcon()
QString MainWindow::RandomIcon()
{
{
QStringList iconFilesList;
  QStringList iconFilesList;
QString searchPath = m_ContentPath'' "/Icons/";
  QString searchPath = m_ContentPath'' "/Icons/";
QDir directory = QDir(searchPath);
  QDir directory = QDir(searchPath);
QStringList filters;
  QStringList filters;
filters << "'''.png";
  filters << "'''.png";
directory.setNameFilters(filters);
  directory.setNameFilters(filters);
// Get the list of the png files inside Icons directory
  // Get the list of the png files inside Icons directory
iconFilesList = directory.entryList(QDir::AllEntries);
  iconFilesList = directory.entryList(QDir::AllEntries);
// Generate random index of the element
  // Generate random index of the element
int fileIdx = qrand() % iconFilesList.count();
  int fileIdx = qrand() % iconFilesList.count();


// Return file name
  // Return file name
return iconFilesList.at(fileIdx);
  return iconFilesList.at(fileIdx);
}
}
<code>
</code>


Now we need to declare the method to play a random sound effect in ''mainwindow.h'' file
Now we need to declare the method to play a random sound effect in ''mainwindow.h'' file


</code>Q_INVOKABLE void PlaySound();</code>
<code>Q_INVOKABLE void PlaySound();</code>


And implementation in ''mainwindow.cpp'' file
And implementation in ''mainwindow.cpp'' file
Line 554: Line 533:
void MainWindow::PlaySound()
void MainWindow::PlaySound()
{
{
QStringList soundFilesList;
  QStringList soundFilesList;
QDir directory = QDir(m_ContentPath + "/Sounds/");
  QDir directory = QDir(m_ContentPath + "/Sounds/");
QStringList filters;
  QStringList filters;
filters << "'''.wav";
  filters << "'''.wav";
directory.setNameFilters(filters);
  directory.setNameFilters(filters);
// Wav files list inside Sounds directory
  // Wav files list inside Sounds directory
soundFilesList = directory.entryList(QDir::AllEntries);
  soundFilesList = directory.entryList(QDir::AllEntries);
// Generate random index of the element
  // Generate random index of the element
int fileIdx = qrand() % soundFilesList.count();
  int fileIdx = qrand() % soundFilesList.count();
// File name
  // File name
QString soundFile = m_ContentPath + "/Sounds/" + soundFilesList.at(fileIdx);
  QString soundFile = m_ContentPath + "/Sounds/" + soundFilesList.at(fileIdx);
// Play file asynchronously
  // Play file asynchronously
QSound::play(soundFile);
  QSound::play(soundFile);
}
}
</code>
</code>
Line 582: Line 561:
Rectangle
Rectangle
{
{
id: canvas
  id: canvas
 
Keys.onPressed: {
  Keys.onPressed: {
if(event.isAutoRepeat == false) {
    if(event.isAutoRepeat == false) {
Main.handleKey()
      Main.handleKey()
}
    }
}
  }
}
}
</code>
</code>
Line 600: Line 579:
Particles
Particles
{
{
id: particles
  id: particles
width: 1
  width: 1
height: 1
  height: 1
anchors.centerIn: parent
  anchors.centerIn: parent
emissionRate: 0
  emissionRate: 0
lifeSpan: 700
  lifeSpan: 700
lifeSpanDeviation: 600
  lifeSpanDeviation: 600
angle: 0
  angle: 0
angleDeviation: 360
  angleDeviation: 360
velocity: 100
  velocity: 100
velocityDeviation: 30
  velocityDeviation: 30
source: randomImage()
  source: randomImage()
// Get random image path
  // Get random image path
function randomImage()
  function randomImage() {
{
    // Array of the image files
// Array of the image files
    var images = ["red.png", "blue.png", "green.png", "white.png", "yellow.png"]
var images = ["red.png", "blue.png", "green.png", "white.png", "yellow.png"]
    // Get random index of the array element
// Get random index of the array element
    var idx = Math.floor((Math.random() * 100)) % images.length
var idx = Math.floor((Math.random() * 100)) % images.length
    // Return the relative image file path
// Return the relative image file path
    return ("Stars/" + images[idx])
return ("Stars/" + images[idx])
  }
}
}
}
</code>
</code>
Line 630: Line 608:
Image
Image
{
{
id: block
  id: block
 
Firework
  Firework
{
  {
id: firework
    id: firework
}
  }
 
}
}
</code>
</code>
Line 642: Line 620:
To launch fireworks on item going to visible state we need to add just one line of code
To launch fireworks on item going to visible state we need to add just one line of code


<code>StateChangeScript { script: firework.burst(50); }<code>
<code>StateChangeScript { script: firework.burst(50); }</code>


to "show" state
to "show" state


</code>
<code>
State
State
{
{
name: "show"; when: show == true
  name: "show"; when: show == true
StateChangeScript { script: firework.burst(50)}
  StateChangeScript { script: firework.burst(50)}
PropertyChanges { target: block; opacity: 1 }
  PropertyChanges { target: block; opacity: 1 }
}
}
<code>
</code>


Launch application and press any key. Image appears on the screen with fireworks and sound effect.
Launch application and press any key. Image appears on the screen with fireworks and sound effect.
Line 666: Line 644:
-----
-----


This article originally appeared on "the Intel AppUp(SM) developer program blog":http://appdeveloper.intel.com/en-us/blog/2010/12/24/modern-mobile-applications-qt-and-qml. A Russian version is available "here":http://habrahabr.ru/blogs/qt_software/110544/
This article originally appeared on [http://appdeveloper.intel.com/en-us/blog/2010/12/24/modern-mobile-applications-qt-and-qml the Intel AppUp(SM) developer program blog]. A Russian version is available [http://habrahabr.ru/blogs/qt_software/110544/ here]


Thanks to "Dmitry":http://intelloware.com for allowing us to copy his tutorial.
Thanks to [http://intelloware.com Dmitry] for allowing us to copy his tutorial.

Latest revision as of 12:37, 1 May 2016


English [French]

Modern Mobile Applications with Qt and QML

Qt is a flexible and powerful framework for creating cross-platform applications. QML is now part of Qt, providing a markup language which gives complete freedom in development of user interfaces.

A good way to see the power of Qt is to start coding with it. Let's write a simple application using Qt and QML. This will be an application I called 4Toddler; the application will start in full-screen mode and it has just two buttons placed in the top right corner of the window: About and Close. The main function is to display a random image with a fireworks effect and with a random sound effect.

The User interface will be written with QML; the backbone is written in Qt C+.

Create new project

If you do not have Qt already installed, visit Qt download page to download Qt SDK for your platform and install it.

Launch Qt Creator, select File -> New File or Project. In the new dialog select Qt C++ Project-> Qt Gui Application, and then click Choose…

screenshot

In the new window: type project name, select path to the project folder and then click Next.

screenshot

In the next window: uncheck Generate form option. We do not need to generate a form. Click Next

screenshot

In the last window: just click Finish.

http://appdeveloper.intel.com/sites/files/4-qt-qml-art.png

The application skeleton is ready, now we can proceed to code the logic and design the UI.

Code core logic in C+

First we need to add the qt-declarative module to our project. This is the module that provides a widget in which a QML interface is displayed.

Open project file (4Toddler.pro) and append the line

QT ''= core gui

with declarative

QT''= core gui declarative

Now we need to change the base class for our main window. Replace QMainWindow with QDeclarativeView and include QDeclarativeView

#include <QDeclarativeView>
class MainWindow : public QDeclarativeView
{
  
}

Also we need to cut off QMainWindow(parent) from the MainWindow constructor; we do not need this initialization anymore.

MainWindow::MainWindow(QWidget *parent)
{
  Init();
}

If you launch the application right now you will see an empty window. This is because we have not initialized or created our QML interface yet.

Add QML interface

Let's add a new file to our project: right click on the project in the projects explorer window.

http://appdeveloper.intel.com/sites/files/5-qt-qml-art.png

Add New then select Qt section and Qt QML File in the templates list and click Choose…

http://appdeveloper.intel.com/sites/files/6-qt-qml-art.png

Type the file name in the Name field, click Next then Finish in the next window.

http://appdeveloper.intel.com/sites/files/7-qt-qml-art.png

The wizard will create and open the new QML file in the editor. There is just one Rectangle element inside it. This will be the root element for our user interface. Let's add some new properties to this element.

Note: some versions of Qt Creator will add a Hello World text element centered in the Rectangle. Remove the text element before proceeding.

Rectangle
{
  // ID of this element. Using this ID we can access the element and its properties from other elements
  id: canvas
  // Background color, black in this case
  color: "black"
  // Change sizes of this element to fill the parent
  anchors.fill: parent
  // Element can receive focus
  focus: true
}

There is nothing special for now, just black background.

Multilingual

During the remainder of this tutorial we will shift back and forth between QML and C++ development.

We need to add some initialization code for our QML interface by defining a new method inside the mainwindow.h file …

void Init();

.. And implementing it in the mainwindow.cpp file

void MainWindow::Init()
{
  // Path to the content folder
  QString contentPath;

  #ifdef QT_DEBUG
  // In the debug version of our project this is a path to the project folder
  contentPath = "D:/MyProjects/QT/4Toddler";
  #else
  // In the release version - path to the application folder
  contentPath = QApplication::applicationDirPath();
  #endif
  setFocusPolicy(Qt::StrongFocus);
  // Change QML document sizes to fit the main window
  setResizeMode(QDeclarativeView::SizeRootObjectToView);
  // Load QML file
  setSource(QUrl::fromLocalFile(contentPath + "/main.qml"));
}

Now we need to replace one line in the main.cpp file

int main(int argc, char *argv[])
{
  
  w.show();
  
}

With

int main(int argc, char *argv[])

{
  
  w.showFullScreen();
  
}

This will force our window to open in full-screen mode.

Structure code with components

Before we launch our application, let's add two buttons. One is to display About dialog and the second to Close our window. We do not need to implement buttons twice, we going to create a component and use it every time we need to add a new button by overriding just a few properties.

Let's add the new Qml file - WindowButton.qml to our project. Note the filename begins with a capital letter — this signifies it defines a QML component.

Image
{
  // ID of this element
  id: button
  // The MouseArea item enables simple mouse handling
  MouseArea
  {
    anchors.fill: parent
    id: mouseArea
    // On click call callback() handler
    onClicked: callback()
  }
}

Now we can add two buttons to our window

// Place items in row

Row
{
  // Right side of the item is anchored to the right side of the parent element
  anchors.right: parent.right
  // Right margin
  anchors.rightMargin: 4
  // Top side of the item is anchored to the top side of the parent element
  anchors.top: parent.top
  // Top margin
  anchors.topMargin: 4
  // Margins for children elements
  spacing: 4
  WindowButton
  {
    // Button to display the About dialog
    id: about
    // Path to background picture. This is a relative path to the path of this QML file
    source: "about.png"
    // Callback method, which will be called on mouse click
    // onClicked: callback()
    function callback() {}
  }
  WindowButton
  {
    // Button to close window
    id: exit
    source: "exit.png"
    function callback() {}
  }
}

Communicate between QML and C++

Now we should implement both callback() methods. To close the window we will call the Quit function of the main window. Now we will see how Qt and QML communicate. Methods will be implemented inside Qt and then called from the QML file.

Let's add a new function to the mainwindow.h header file

Q_INVOKABLE void Quit();

And implementation in mainwindow.cpp file

void MainWindow::Quit()
{
  QApplication::quit();
}

Now we need to "tell" QML about this method. Inside Init we should to add just one line:

rootContext()->setContextProperty("window", this);

Now we can access window object's functions declared as Q_INVOKABLE from QML. Window here is just an example, you could use any object name you want.

Let's add callback() function implementation to close button

function callback()
{
  window.Quit();
}

Visualize state and add animation

Ok, the application can be launched. Launch it and click the Close button. You see? State of the button is not changed after click; it looks like the button is just inactive. Let's add state changes for normal and clicked states.

Image
{
  
  states:[
    State
    {
      // Name of the state
      name: "hovered"
      // When condition. Item will go to this state if condition is true
      // In this case on mouse click
      when: mouseArea.pressed
      // Which properties will be changed in that state
      // In this case- opacity
      PropertyChanges { target: button; opacity: 1}
    },
    State
    {
      name: "normal"
      // This state will be activated if mouse button is not pressed
      when: mouseArea.pressed == false
      PropertyChanges { target: button; opacity: 0.7; }
    }
  ]
}

Item will change state if the condition described in when property is true. You could change state manually by assigning state property. Let's launch the application again. Ok, this time it looks more active. We can make our button nicer by adding some animation.

Image
{
  
  Behavior on opacity
  {
    // Animaion step is a 100 milliseconds
    // On every iteration opacity will increase or descrease with step equals to 0,1
    NumberAnimation { duration: 100 }
  }
}

The Behavior is a simple and flexible way to create animations. This element allows you to specify a default animation for a property change.

Launch the application and try to click on About or Close buttons. This looks much better!

http://appdeveloper.intel.com/sites/files/8-qt-qml-art.png

We will implement the About dialog only using QML. Dialog will appear on the screen when About button is clicked and disappear when the user clicks inside the button or on the background.

Let's add About.qml file to our project.

// Parent element for dialog window
Rectangle
{
  id: about
  // Function to display dialog window
  // This function just changes opacity of the main element to 1
  function show() {
    about.opacity = 1
  }
  // Function to hide dialog window
  // This function just changes opacity of the main element to 0
  function hide() {
    about.opacity = 0
  }
  // Transparent background
  color: "transparent"
  // Opacity is 0 by default, dialog not visible
  opacity: 0
  // Anchors to parent width and height.
  width: parent.width
  height: parent.height
  // Element is visible if opacity > 0
  // opacity > 0
  visible: opacity > 0
  // Child element to create semitransparent background
  Rectangle
  {
    anchors.fill: parent
    opacity: 0.5
    color: "gray"
  }
  // Body of the dialog window
  Rectangle
  {
    id: dialog
    // Fixed width and height
    width: 360
    height: 230
    // To make dialog centered inside parent we need to calculate its x and y coordinates
    x: parent.width / 2 - dialog.width / 2
    y: parent.height / 2 - dialog.height / 2
    // Place dialog on top of other elements
    z: 10
    border.color: "gray"
    Text
    {
      text: "4 Toddler"
      font.bold: true
      font.pixelSize: 22
      anchors.horizontalCenter: parent.horizontalCenter
      anchors.verticalCenter: parent.verticalCenter
    }
  }
  Behavior on opacity
  {
    NumberAnimation { duration: 100 }
  }
  MouseArea
  {
    anchors.fill: parent
    // Hide dialog on mouse click
    onClicked: hide()
  }
}

Take a look at the line

visible: opacity > 0

As you can see, property can be assigned and can be calculated.

Let's add About dialog and callback() function implementation for the button About. In Main.qml file we need to declare the new About element.

Rectangle
{
  id: canvas
  ..
  About
  {
    id: aboutDlg
  }
}

And callback() function implementation

aboutDlg.show();

to

WindowButton
{
  id: about
  
  function callback()
  {
    aboutDlg.show()
  }
}

Finally we need to implement the main functionality for our application.

The element to display a random icon on the screen will be an Image element. Let's add a new file for the new element - Block.qml

Image
{
  id: block;
  // New custom properties to change visibility of this element
  property bool remove: false
  property bool show: false
  opacity: 0
  fillMode: Image.Stretch
  states: [
    State
    {
      // State is used to remove element from the screen and destroy it
      name: "remove"; when: remove  true
      PropertyChanges { target: block; opacity: 0 }
      StateChangeScript { script: block.destroy(1000) }
    },
    State
    {
      // State is used to display element
      name: "show"; when: show  true
      PropertyChanges { target: block; opacity: 1 }
    }
  ]
  Behavior on opacity { NumberAnimation { duration: 300 } }
}

Now we need to implement the keyboard handler. Handler will be implemented with JavaScript. Let's add new main.js to our project.

// Template for new elements
var component = Qt.createComponent("block.qml")
// Max number of items on the screen
var maxBlocksCount = 10
// Array for the items
var blocksArray = new Array()
// Keyboard handler
function handleKey()
{
  // x coordinate is a random number from 0 to window with in pixels
  var x = Math.floor(Math.random() * canvas.width)
  // y coordinate is a random number from 0 to window height in pixels
  var y = Math.floor(Math.random() * canvas.height)
  // This function will create a new element on each call with random x and y coordinates
  createNewBlock(x, y)
}
// Function to create a new element
function createNewBlock(x, y)
{
  if(component.status != Component.Ready) {
    return false
  }
  // Remove items if number of items is more than maxBlocksCount
  if(blocksArray.length > maxBlocksCount) {
    removeAllBlocks()
  }
  var newBlock = component.createObject(canvas)
  if(newBlock == null) {
    return false
  }
  // Path to image is available via randomIcon property of the main window
  var iconFile = window.randomIcon
  newBlock.source = ("Icons/" + iconFile)
  newBlock.x = x
  newBlock.y = y
  // Change state to show
  newBlock.show = true
  blocksArray.push(newBlock)
  // Play random sound effect
  window.PlaySound()
  return true
}
// Function do remove all existings items
function removeAllBlocks() {
  for(var i = 0; i < blocksArray.length; +''i) {
    blocksArray[i].remove = true
  }
  while(blocksArray.length != 0) {
    blocksArray.pop()
  }
}

As you see in code above we should implement new property randomIcon and method PlaySound.

Let's add property declaration and method to access it to our mainwindow.h file.

Q_PROPERTY(QString randomIcon READ RandomIcon)
QString RandomIcon();

Implementation in mainwindow.cpp file

QString MainWindow::RandomIcon()
{
  QStringList iconFilesList;
  QString searchPath = m_ContentPath'' "/Icons/";
  QDir directory = QDir(searchPath);
  QStringList filters;
  filters << "'''.png";
  directory.setNameFilters(filters);
  // Get the list of the png files inside Icons directory
  iconFilesList = directory.entryList(QDir::AllEntries);
  // Generate random index of the element
  int fileIdx = qrand() % iconFilesList.count();

  // Return file name
  return iconFilesList.at(fileIdx);
}

Now we need to declare the method to play a random sound effect in mainwindow.h file

Q_INVOKABLE void PlaySound();

And implementation in mainwindow.cpp file

void MainWindow::PlaySound()
{
  QStringList soundFilesList;
  QDir directory = QDir(m_ContentPath + "/Sounds/");
  QStringList filters;
  filters << "'''.wav";
  directory.setNameFilters(filters);
  // Wav files list inside Sounds directory
  soundFilesList = directory.entryList(QDir::AllEntries);
  // Generate random index of the element
  int fileIdx = qrand() % soundFilesList.count();
  // File name
  QString soundFile = m_ContentPath + "/Sounds/" + soundFilesList.at(fileIdx);
  // Play file asynchronously
  QSound::play(soundFile);
}

Almost done. All we need is to add the keyboard handler to our parent QML element. But first we should include main.js file to main.qml file

import Qt 4.7
import "main.js" as Main

Keyboard event handler for root element

Rectangle
{
  id: canvas
  
  Keys.onPressed: {
    if(event.isAutoRepeat == false) {
      Main.handleKey()
    }
  }
}

That's all! Now you can launch the application and try to press any key on the keyboard. But… I almost forgot about the fireworks effect.

Let's add a new file called Fireworks.qml. Why do we need a new file? Because this will be a reusable element, this will save time in the future.

import Qt.labs.particles 1.0
Particles
{
  id: particles
  width: 1
  height: 1
  anchors.centerIn: parent
  emissionRate: 0
  lifeSpan: 700
  lifeSpanDeviation: 600
  angle: 0
  angleDeviation: 360
  velocity: 100
  velocityDeviation: 30
  source: randomImage()
  // Get random image path
  function randomImage() {
    // Array of the image files
    var images = ["red.png", "blue.png", "green.png", "white.png", "yellow.png"]
    // Get random index of the array element
    var idx = Math.floor((Math.random() * 100)) % images.length
    // Return the relative image file path
    return ("Stars/" + images[idx])
  }
}

Now we need to add our Firework element to the Block element. Open Block.qml file and add declaration of the new element

Image
{
  id: block
  
  Firework
  {
    id: firework
  }
  
}

To launch fireworks on item going to visible state we need to add just one line of code

StateChangeScript { script: firework.burst(50); }

to "show" state

State
{
  name: "show"; when: show == true
  StateChangeScript { script: firework.burst(50)}
  PropertyChanges { target: block; opacity: 1 }
}

Launch application and press any key. Image appears on the screen with fireworks and sound effect.

http://appdeveloper.intel.com/sites/files/9-qt-qml-art.png

This is a simple application, but this is a good playground to understand the principles of QML.

To learn more about QML you can extend this application by adding features. Who knows, maybe someday this sample will grow to a commercial app? Idea from me: educational software with letters, numbers, shapes, colors, etc.



This article originally appeared on the Intel AppUp(SM) developer program blog. A Russian version is available here

Thanks to Dmitry for allowing us to copy his tutorial.