Blog

Multi-platform mobile software development: Qt instead of Xamarin

Jörg Preiß
February 28th, 2017

Microsoft is accessing the field of WPF platform independence with Xamarin Forms. However, there has for a long time been an existing framework that runs on Windows, MacOS, Linux and starting with version 5 on iOS, Android, Sailfish OS and other operating systems – without re-implementation: Qt.

#include „maincontroller.h“ 
#include  
#include  
#include  
#include  
#include  

int main(int argc, char *argv[]) 
{ 
    QApplication app(argc, argv); 

    QQmlApplicationEngine engine; 

    QQmlContext* context = engine.rootContext(); 

    QString currPath = QDir::currentPath(); 
    context->setContextProperty(„currentPath“, currPath); 

    QScreen *screen = QApplication::screens().at(0); 
    int width = screen->availableSize().width(); 
    context->setContextProperty(„availableWidth“, width); 
    int height = screen->availableSize().height(); 
    context->setContextProperty(„availableHeight“, height); 

    MainController* mainController = new MainController(); 
    context->setContextProperty(„mainController“, mainController); 

    engine.load(QUrl(QStringLiteral(„qrc:/main.qml“))); 
    return app.exec(); 
} 

LISTING 1: MAIN.CPP

 

Qt has been around since 1992. For a long time, the company Trolltech distributed the framework commercially. There was a free version for the Linux desktop KDE that later was licensed under GPL. The two versions differed in the availability of certain modules. Starting with version 4.5 in 2009, the LGPL (GNU Lesser General Public License) was added. The current version 5.x is available in commercial and free versions as well. The rights are currently owned by The Qt Company.

Qt development was distinguished by the signal/slot principle. While other frameworks still used events, this already was an implementation of the publish-subscribe pattern. A button provides the clicked() signal, which view components can bind to a slot onClicked().

Version 4.7 introduced the Qt Markup Language, QML. While designers previously generated the completed source code, now the interface could be described in a JSON-like language. On-screen elements can be manipulated with JavaScript, values and lists can be bound. The framework achieved an architecture called model-view-delegate. Version 5.6 was used for this article.

First window

As in WPF logic and view are clearly separated through the use of different languages. The application is initialized by C++ code, as seen in listing 1. In the QQml engine, values can be registered in a QQmlContext and bound to. Generally everything that can be derived from QObject can also be registered. This way, the DataContext known from WPF can be implemented, for example with a controller like in listing 2.

Declared properties can be read inside the QML code. To make changes visible there as well, signals have be emitted when setting the values. Listing 3 clarifies the use of properties: text elements offer read access, TextInput also allows changes. The binding of the first variant sends the new text directly to the second TextInput. In the second variant the change is executed with a function call and needs no further bindings.

#ifndef MAINCONTROLLER_H 
#define MAINCONTROLLER_H 

#include  
#include  

class MainController : public QObject 
{ 
    Q_OBJECT 
    Q_PROPERTY(QString property READ property WRITE 
      setProperty NOTIFY propertyChanged) 
    Q_PROPERTY(QString attribute READ attribute NOTIFY 
      attributeChanged) 
  public: 
    explicit MainController(QObject *parent = 0); 

    QString property() const { 
     return _property; 
    } 
    void setProperty(const QString& value) { 
     if( _property==value ) return; 
      _property = value; 
      emit propertyChanged(); 
    } 

    QString attribute() const { return _attribute; } 

  signals: 
    void propertyChanged(); 
    void attributeChanged(); 

  public slots: 
    void changeAttribute(const QString& value) { 
     if( _attribute==value ) return; 
      _attribute = value; 
      emit attributeChanged(); 
  } 

  private: 
    QString _property; 
    QString _attribute; 
}; 

#endif // MAINCONTROLLER_H  

LISTING 2: MAINCONTROLLER.H

 

import QtQuick 2.5 
import QtQuick.Controls 1.4 
import QtQuick.Dialogs 1.2 
import QtQuick.Window 2.1 

ApplicationWindow { 
    visible: true 
    width: 640 
    height: 480 
    title: qsTr(„First Application“) 

    ... 

    Rectangle { 
        id: propertyGroup 
        anchors { 
            margins: 20 
            top: settingsGroup.bottom 
            left: parent.left 
            right: parent.right 
        } 
        height: 60 
        color: ‚darkgrey‘ 

        GroupBox { 
            title: ‚Property‘ 

            Row { 
                spacing: 5 
                Text { text: ‚New value:‘ } 
                // 1. Variante: Update beim Schreiben 
                TextInput { 
                    id: prop 
                    text: mainController.property 
                    width: 160 
                    Binding { 
                        target: mainController 
                        property: „property“ 
                        value: prop.text 
                    } 
                } 
                Text { text: ‚Current value:‘ } 
                TextInput { 
                    id: currVal 
                    text: mainController.property 
                    readOnly: true 
                } 
            } 
        } 
    } 
    Rectangle { 
        id: attributeGroup 
        anchors { 
            margins: 20 
            top: propertyGroup.bottom 
            left: parent.left 
            right: parent.right 
        } 
        height: 60 
        color: ‚grey‘ 
        GroupBox { 
            id: groupAttrib 
            title: ‚Attribute‘ 

            Row { 
                spacing: 5 
                Text { 
                    text: ‚Attribute:‘ 
                    anchors.verticalCenter: parent.verticalCenter 
                } 
                // 2. Variante: Update per Funktionsaufruf 
                TextInput { 
                    id: attrib 
                    text: mainController.attribute 
                    anchors.verticalCenter: parent.verticalCenter 
                    width: 160 
                } 
                Button { 
                    text: ‚Set‘ 
                    width: 60 
                    anchors.verticalCenter: parent.verticalCenter 
                    onClicked: { 
                        mainController.changeAttribute(attrib.text); 
                    } 
                } 
                Text { 
                    text: ‚Value:‘ 
                    anchors.verticalCenter: parent.verticalCenter 
                } 
                TextInput { 
                    text: mainController.attribute 
                    anchors.verticalCenter: parent.verticalCenter 
                    readOnly: true 
                } 
            } 
        } 
    } 
} 
using System;
using System.CodeDom.Compiler;using System.ComponentModel;
using System.Xml.Serialization;using System.Runtime.Serialization;using System.Text;
using BLL = sf.moplus.demo.BLL;
namespace sf.moplus.demo.BLL.Kunden_Daten
{
  ///-------------------------------------------
  ///
This clac      {
        if (_adresseID != value)
        {
          _adresseID = value;
          IsModified = true;
        }
      }

LISTING 3: MAIN.QML

 

Lists, filters, sorting

How about the handling of lists? In WPF, a ListBox is filled by an ItemsSource, its style is changed and the ListItem templates are customized. In QML, the list is filled according to a model and the style of the elements is described with a delegate.

There are multiple ways to present elements. The Highrise example uses a GridView for the multi-column display and a ListView for the master view. Another option which this article does not cover would be listing using a Repeater.

In the current example the list delegates are static. However they could just as well be computed by a JavaScript function. This for example allows a tile view that switches view depending on the window size.

Listing 4 renders the list of all rooms with their current status. A GridView is used because the list has two columns. Neither sorting nor filtering are needed so that a model can be used directly. That model is BuildingModel, a class derived from QAbstractListModel (listing 5).

The software accesses the different attributes of a ListItem through roles. To read and display the list, at least the functions data(), rowCount() and roleNames () must be overwritten. In the current model, the room status will be editable too, so the function setData() is overwritten as well. Here, the emission of the dataChanged signal at the end of the function is of note, because no change would be propagated without it.

emit dataChanged(index, index);

Instead of specifying the attribute accesses by roles, the values can also be distributed to columns. In this case derive from QAbstractTableModel and distribute the attributes to columns. Corresponding examples can be found in the documentation.

The room list on the right is displayed in full. In contrast, the master view on the list should be filterable. As the data source is the same – all rooms – only a QSortFilterProxyModel is needed which in this case handles all the filtering out of unwanted elements. For this the function filterAcceptsRow() is overwritten. Building on that, overwriting lessThan() could enable sorting.

Listing 6 shows the advantage of registered elements within the QmlContext: the BuildingProxy has to be registered in the QmlContext, so the ListView can access it. Once it is registered, however, it can be accessed from everywhere. The automatisms in Qt trigger the update of affected windows.

import QtQuick 2.3 
import QtGraphicalEffects 1.0 
import „components“ 

FocusScope { 
    id: root 

    GridView { 
        id: roomList 
        model: roomsModel 
        cellWidth: root.width/2-20 
        cellHeight: model.count > 0 ? root.height / (model.count * 2) : 0 
        focus: true 
        clip: true 
        delegate: roomDelegate 
        highlight: Rectangle { color: „lightsteelblue“; radius: 5 } 
        anchors { 
            left: parent.left 
            right: scrollbar.left 
            top: parent.top 
            topMargin: 5 
            bottom: parent.bottom 
        } 
    } 

    ... 

}  

LISTING 4: ROOMGRIDVIEW.QML

 

Keyboard controls

Focus is a frequently misunderstood concept regardless of framework. If I’m in an input field and press [Tab] I want to jump to the next input field. That is the simple case. But even the simple Highrise example already involves three different and actually simultaneously active focuses: the EditBox is the just mentioned input field. Additionally, there is a focused element that changes the rooms lighting status on a press of [Return] – analogous to a double click. Keys.onReturnPressed is responsible for this.

Keys.onReturnPressed: { 
  model.lightstate = model.lightstate===1 ? 0 : 1; 
}

 

#ifndef BUILDINGMODEL_H 
#define BUILDINGMODEL_H 
#include  
#include  
#include „model/room.h“ 

class Building; 

class BuildingModel : public QAbstractListModel 
{ 
    enum RoomRoles { 
        IdRole = Qt::UserRole + 1, 
        NameRole = Qt::UserRole + 2, 
        LightStateRole = Qt::UserRole + 3, 
        RoomStateRole = Qt::UserRole + 4, 
    }; 

public: 
    BuildingModel(Building* building); 
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; 
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); 
    int rowCount(const QModelIndex &parent = QModelIndex()) const; 
    QHash<int, QByteArray> roleNames() const; 

    QList<Room*> GetRooms() { 
        return _rooms; 
    } 

private: 
    QList<Room*> _rooms; 
    friend class BuildingProxy; 
}; 

class BuildingProxy: public QSortFilterProxyModel 
{ 
protected: 
    virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; 
}; 

#endif // BUILDINGMODEL_H 

LISTING 5: BUILDINGMODEL.H

 

Likewise there is a focused element that in this case only serves as navigation. Each focus area in QML is enclosed in a FocusScope as can be seen in the files mentioned above. Within Highrise/BuildingView.qml all three areas are marked as accessible with activeFocusOnTab.

RoomListView { 
  id: roomList 
  width: root.width/2 
  focus: true 
  activeFocusOnTab: true 
  anchors { 
    margins: 20 
    left: parent.left 
    top: header.bottom 
    bottom: parent.bottom 
  } 
}

Custom Components

Strictly speaking, Highrise already uses multiple custom components: every QML file can be used as a type in another QML file, like in listing 4 and listing 6 for the two left and right views.

But all other files in Highrise/components were designed as independent controls too – the EditBox with embedded icon as well as the scrollbar. The more we work in Qt and QML the more we work without pre-made components like a button.

The look button can be influenced in a limited way by the style property. But by using Rectangles, MouseAreas and other basic elements, the usage of a component can be specified more precisely.

For example take a RangeSlider, a slider with a lower and upper value. Overlaying two sliders does not work. Instead we take a rectangle as the slider track, two rectangles as thumbs, each containing a MouseArea to make the thumbs touchable, and carry over the values with JavaScript code. Of course there is the possibility to implement custom controls in C++ and providing them inside QML.

import QtQuick 2.3 
import QtQuick.Controls 1.2 
import „components“ 

Rectangle { 
    id: root 
    color: ‚transparent‘ 

    BuildingViewHeader { 
        id: header 
        activeFocusOnTab: true 
        onFocusChanged: { 
            if(focus===false) 
                roomList.forceActiveFocus(); 
        } 
    } 

    RoomListView { 
        id: roomList 
        width: root.width/2 
        focus: true 
        activeFocusOnTab: true 
        anchors { 
            margins: 20 
            left: parent.left 
            top: header.bottom 
            bottom: parent.bottom 
        } 
    } 

    RoomGridView { 
        id: roomGrid 
        activeFocusOnTab: true 
        anchors { 
            margins: 20 
            top: header.bottom 
            bottom: parent.bottom 
            left: roomList.right 
            right: parent.right 
        } 
    } 
} 

LISTING 6: BUILDINGVIEW.QML

 

Animations

The potential of WPF is admittedly enormous with the use of storyboards, but not always easy to use. There are developers that use Blend just to configure the storyboard options.

QML provides a state machine here as well. In EditBox only one state is declared: when a text is entered the watermark icon disappears. When the new state is entered the specified properties are set. Conversely, when the state is left, the changes are undone. In this case the state is linked to a condition. But the state can also be changed by assigning state = “hasText”.

As simply hiding the icon would be too abrupt, it should be animated. Here we turn to transitions. They specify transitions in general, or from one state to another. In the transitions the different animation elements specify the type of animation. For example, there is OpacityAnimator, RotationAnimator or a general NumberAnimation. Animations can run simultaneously with ParallelAnimation or one after another with SequentialAnimation. The two types can also be nested.

Highrise

This example shows Highrise with a GridView and a ListView

Custom ressources

WPF developers are going to miss the possibility to outsource ressouces into RessourceDictionaries. It is simply bad style to repeat color definitions in every control. But there is help: the singleton modules. The first line of the according QML file is pragma Singleton, inside a QtObject with all properties is created. For a color singleton the colors.qml is

pragma Singleton 
import QtQuick 2.4 

QtObject { 
    property var colorWhite: '#FFFFFFFF' 
    property var brushBackground: colorWhite 
...

respectively for Fonts.qml:

pragma Singleton 
import QtQuick 2.4 

QtObject { 
  property font mainFont: Qt.font({ 
    family: ttfFont.name, 
    pointSize: 24 
  }) 
  property FontLoader ttfFont: FontLoader {
    id: _ttfFont;
    source: 'qrc:/assets/fonts/myfont.ttf'
  } 
...

Both files are registered as singleton modules and can be used in all other QML files:

  auto fontsModule =
    QStringLiteral("qrc:/qml/modules/Fonts.qml"); 
  qmlRegisterSingletonType( QUrl(fontsModule), "Fonts",
    1, 0, "Fonts" ); 
  auto colorsModule =
    QStringLiteral("qrc:/qml/modules/Colors.qml"); 
  qmlRegisterSingletonType( QUrl(colorsModule),
    "Colors", 1, 0, "Colors" ); 
...

Debugging

While in XAML the main problems are displayed in the console if they are displayed at all, in QML console.debug() in the JavaScript code is used. Problems can appear if bindings to models do not work or update signals are not emitted.

Here, maybe GammaRay can help. Similar to Snoop it shows the current tree, lists signals and linked slots, visualizes the state of the state machine and so on.

Fazit

QML and XAML have a lot in common. A signal in one language is a INotifyPropertyChanged.  Bindings and attached properties exist. It is considered poor style in both worlds to implement too much functionality in the markup.

But there are large differences as well. Because familiar WPF approaches like templating and resource dictionaries are missing, QT demands a different mindset. In return, the learning curve seems to be less steep overall. The enrichment of interface functionality with JavaScript is pleasant as well. Approaches like behaviours aren’t needed. Besides the mentioned platforms, the growing internet of things is missing. There is the option of booting a Qt-optimized software stack on devices. Time will tell if this option will stand its ground opposite Windows 10.

This article first appeared in dotnetpro

Want to know more about our services, products or our UX process?
We are looking forward to hearing from you.

Senior UX Manager
+49 681 959 3110

Before sending your request, please confirm that we may contact you by clicking in the checkbox above.