{"id":7528,"date":"2017-02-28T16:00:26","date_gmt":"2017-02-28T15:00:26","guid":{"rendered":"http:\/\/www.centigrade.de\/blog\/de\/?p=7528"},"modified":"2020-02-17T10:24:28","modified_gmt":"2020-02-17T09:24:28","slug":"softwareentwicklung-fuer-mobil-und-multiplattform-qt-statt-xamarin","status":"publish","type":"blog","link":"https:\/\/www.centigrade.de\/de\/blog\/softwareentwicklung-fuer-mobil-und-multiplattform-qt-statt-xamarin\/","title":{"rendered":"Softwareentwicklung f\u00fcr Mobil und Multiplattform: Qt statt Xamarin"},"content":{"rendered":"<p>Microsoft er\u00f6ffnet sich mit Xamarin Forms das Feld der Plattformunabh\u00e4ngigkeit f\u00fcr WPF. Dabei existiert bereits seit Langem ein Framework, das auch ohne erneute Implementierung unter den Betriebssystemen Windows, MacOS, Linux und seit Version 5 auch unter iOS, Android, Sailfish OS und anderen l\u00e4uft. Die Rede ist von <a href=\"http:\/\/www.qt.io\/download\" target=\"_blank\" rel=\"noopener noreferrer\">Qt<\/a>. Ausgesprochen werden die beiden Buchstaben wie das englische Wort f\u00fcr niedlich: cute [kju:t].<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true\">#include \u201emaincontroller.h\u201c \r\n#include  \r\n#include  \r\n#include  \r\n#include  \r\n#include  \r\n\r\nint main(int argc, char *argv[]) \r\n{ \r\n    QApplication app(argc, argv); \r\n\r\n    QQmlApplicationEngine engine; \r\n\r\n    QQmlContext* context = engine.rootContext(); \r\n\r\n    QString currPath = QDir::currentPath(); \r\n    context-&gt;setContextProperty(\u201ecurrentPath\u201c, currPath); \r\n\r\n    QScreen *screen = QApplication::screens().at(0); \r\n    int width = screen-&gt;availableSize().width(); \r\n    context-&gt;setContextProperty(\u201eavailableWidth\u201c, width); \r\n    int height = screen-&gt;availableSize().height(); \r\n    context-&gt;setContextProperty(\u201eavailableHeight\u201c, height); \r\n\r\n    MainController* mainController = new MainController(); \r\n    context-&gt;setContextProperty(\u201emainController\u201c, mainController); \r\n\r\n    engine.load(QUrl(QStringLiteral(\u201eqrc:\/main.qml\u201c))); \r\n    return app.exec(); \r\n} \r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 1: MAIN.CPP<\/p>\n<p>&nbsp;<\/p>\n<p>Qt existiert bereits seit 1992. Die sp\u00e4tere Firma Trolltech vertrieb das Framework lange Zeit kommerziell. F\u00fcr den Linux- Desktop KDE gab es eine freie Version, die sp\u00e4ter auch unter die GPL gestellt wurde. Beide Versionen unterschieden sich durch die Verf\u00fcgbarkeit bestimmter Module. Ab Version 4.5 im Jahre 2009 wurde die LPGL hinzugef\u00fcgt. Auch die aktuelle Version 5.x liegt in einer kommerziellen und einer freien Version vor. Mittlerweile liegen die Rechte bei The QT Company.<\/p>\n<p>Das Besondere an der Qt-Entwicklung war das Signal\/Slot- Prinzip. W\u00e4hrend viele andere Frameworks noch auf Events setzten, wurde hier schon das sp\u00e4tere Publish-Subscribe-Pattern eingesetzt. Ein Button stellt das Signal <em>clicked()<\/em> bereit, eine View-Komponente kann dieses Signal an einen eigenen Slot <em>onClicked()<\/em> binden.<\/p>\n<p>Mit der Version 4.7 wurde die Qt Markup Language QML eingef\u00fchrt. Generierte der Designer vorher noch den fertigen Quellcode, kann man nun die Oberfl\u00e4che in einer JSON-artigen Sprache beschreiben. Die Bildschirmelemente k\u00f6nnen durch JavaScript manipuliert, Werte und Listen gebunden werden. Man erreicht insgesamt eine Architektur, die Model- View-Delegate genannt wird. F\u00fcr den vorliegenden Artikel wurde Qt in Version 5.6 verwendet.<!--more--><\/p>\n<h3>Erstes Fenster<\/h3>\n<p>Die Aufteilung zwischen der Logik und der Ansicht erf\u00e4hrt, \u00e4hnlich wie bei WPF, eine klare Trennung durch die Verwendung von unterschiedlichen Sprachen. Die Applikation wird durch C++-Code initialisiert, wie in <strong>Listing 1<\/strong> zu sehen. Innerhalb der QQmlEngine k\u00f6nnen in einem QQmlContext Werte registriert werden, an die gebunden werden kann. Prinzipiell l\u00e4sst sich alles registrieren, was von QObject abgeleitet ist. So l\u00e4sst sich der von WPF bekannte DataContext realisieren, hinter dem ein Controller stecken kann wie in <strong>Listing 2<\/strong>.<\/p>\n<p>Deklarierte Properties k\u00f6nnen innerhalb des QML-Codes gelesen werden. Damit dort auch \u00c4nderungen sichtbar werden, sind noch Signale beim Setzen der Werte zu schicken. <strong>Listing 3<\/strong> verdeutlicht die Verwendung der Properties: Die <em>Text<\/em>-Elemente bieten lesenden Zugriff, <em>TextInput<\/em> erlaubt auch \u00c4nderungen. Das Binding in der ersten Variante leitet den neuen Text an das zweite <em>TextInput<\/em> weiter. In der zweiten Variante wird die \u00c4nderung per Funktionsaufruf durchgef\u00fchrt und bedarf keines weiteren Bindings.<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true\">#ifndef MAINCONTROLLER_H \r\n#define MAINCONTROLLER_H \r\n\r\n#include  \r\n#include  \r\n\r\nclass MainController : public QObject \r\n{ \r\n    Q_OBJECT \r\n    Q_PROPERTY(QString property READ property WRITE \r\n      setProperty NOTIFY propertyChanged) \r\n    Q_PROPERTY(QString attribute READ attribute NOTIFY \r\n      attributeChanged) \r\n  public: \r\n    explicit MainController(QObject *parent = 0); \r\n\r\n    QString property() const { \r\n     return _property; \r\n    } \r\n    void setProperty(const QString&amp; value) { \r\n     if( _property==value ) return; \r\n      _property = value; \r\n      emit propertyChanged(); \r\n    } \r\n\r\n    QString attribute() const { return _attribute; } \r\n\r\n  signals: \r\n    void propertyChanged(); \r\n    void attributeChanged(); \r\n\r\n  public slots: \r\n    void changeAttribute(const QString&amp; value) { \r\n     if( _attribute==value ) return; \r\n      _attribute = value; \r\n      emit attributeChanged(); \r\n  } \r\n\r\n  private: \r\n    QString _property; \r\n    QString _attribute; \r\n}; \r\n\r\n#endif \/\/ MAINCONTROLLER_H  \r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 2: MAINCONTROLLER.H<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true\">import QtQuick 2.5 \r\nimport QtQuick.Controls 1.4 \r\nimport QtQuick.Dialogs 1.2 \r\nimport QtQuick.Window 2.1 \r\n\r\nApplicationWindow { \r\n    visible: true \r\n    width: 640 \r\n    height: 480 \r\n    title: qsTr(\u201eFirst Application\u201c) \r\n\r\n    ... \r\n\r\n    Rectangle { \r\n        id: propertyGroup \r\n        anchors { \r\n            margins: 20 \r\n            top: settingsGroup.bottom \r\n            left: parent.left \r\n            right: parent.right \r\n        } \r\n        height: 60 \r\n        color: \u201adarkgrey\u2018 \r\n\r\n        GroupBox { \r\n            title: \u201aProperty\u2018 \r\n\r\n            Row { \r\n                spacing: 5 \r\n                Text { text: \u201aNew value:\u2018 } \r\n                \/\/ 1. Variante: Update beim Schreiben \r\n                TextInput { \r\n                    id: prop \r\n                    text: mainController.property \r\n                    width: 160 \r\n                    Binding { \r\n                        target: mainController \r\n                        property: \u201eproperty\u201c \r\n                        value: prop.text \r\n                    } \r\n                } \r\n                Text { text: \u201aCurrent value:\u2018 } \r\n                TextInput { \r\n                    id: currVal \r\n                    text: mainController.property \r\n                    readOnly: true \r\n                } \r\n            } \r\n        } \r\n    } \r\n    Rectangle { \r\n        id: attributeGroup \r\n        anchors { \r\n            margins: 20 \r\n            top: propertyGroup.bottom \r\n            left: parent.left \r\n            right: parent.right \r\n        } \r\n        height: 60 \r\n        color: \u201agrey\u2018 \r\n        GroupBox { \r\n            id: groupAttrib \r\n            title: \u201aAttribute\u2018 \r\n\r\n            Row { \r\n                spacing: 5 \r\n                Text { \r\n                    text: \u201aAttribute:\u2018 \r\n                    anchors.verticalCenter: parent.verticalCenter \r\n                } \r\n                \/\/ 2. Variante: Update per Funktionsaufruf \r\n                TextInput { \r\n                    id: attrib \r\n                    text: mainController.attribute \r\n                    anchors.verticalCenter: parent.verticalCenter \r\n                    width: 160 \r\n                } \r\n                Button { \r\n                    text: \u201aSet\u2018 \r\n                    width: 60 \r\n                    anchors.verticalCenter: parent.verticalCenter \r\n                    onClicked: { \r\n                        mainController.changeAttribute(attrib.text); \r\n                    } \r\n                } \r\n                Text { \r\n                    text: \u201aValue:\u2018 \r\n                    anchors.verticalCenter: parent.verticalCenter \r\n                } \r\n                TextInput { \r\n                    text: mainController.attribute \r\n                    anchors.verticalCenter: parent.verticalCenter \r\n                    readOnly: true \r\n                } \r\n            } \r\n        } \r\n    } \r\n} \r\nusing System;\r\nusing System.CodeDom.Compiler;using System.ComponentModel;\r\nusing System.Xml.Serialization;using System.Runtime.Serialization;using System.Text;\r\nusing BLL = sf.moplus.demo.BLL;\r\nnamespace sf.moplus.demo.BLL.Kunden_Daten\r\n{\r\n  \/\/\/-------------------------------------------\r\n  \/\/\/\r\nThis clac      {\r\n        if (_adresseID != value)\r\n        {\r\n          _adresseID = value;\r\n          IsModified = true;\r\n        }\r\n      }\r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 3: MAIN.QML<\/p>\n<p>&nbsp;<\/p>\n<h3>Listen, Filter, Sortierung<\/h3>\n<p>Wie sieht der Umgang mit Listen aus? In der WPF-Welt f\u00fcllt man eine <em>ListBox<\/em> mit einer <em>ItemsSource<\/em> und ver\u00e4ndert den Style der Liste sowie die Templates der <em>ListItems<\/em>. Unter QML f\u00fcllt man die Liste anhand eines Models und beschreibt das Aussehen der Elemente mithilfe eines Delegates.<\/p>\n<p>Es gibt verschiedene M\u00f6glichkeiten der Pr\u00e4sentation der Elemente. Im Beispielprojekt Highrise wird eine <em>GridView<\/em> f\u00fcr die mehrspaltige Anzeige sowie eine <em>ListView<\/em> f\u00fcr die \u00dcbersicht benutzt. Eine weitere M\u00f6glichkeit w\u00e4re die Auflistung mithilfe eines Repeaters, auf die der Artikel nicht eingeht.<\/p>\n<p>Im vorliegenden Beispiel sind die Delegates der Listen statisch. Es spricht allerdings nichts dagegen, sie durch eine JavaScript- Funktion berechnen zu lassen. Dadurch ist zum Beispiel eine Kachelview m\u00f6glich, die in Abh\u00e4ngigkeit der Fenstergr\u00f6\u00dfe zwischen verschiedenen Ansichten umschaltet.<\/p>\n<p>In <strong>Listing 4<\/strong> wird die Liste aller R\u00e4ume mit ihrem derzeitigen Zustand visualisiert. Die Liste ist zweispaltig, weshalb eine <em>GridView<\/em> zum Einsatz kommt. Es wird auch weder Sortierung noch Filterung ben\u00f6tigt, sodass direkt auf ein Model zugegriffen werden kann. Bei dem Model handelt es sich um das <em>BuildingModel<\/em>, eine Klasse, die von <em>QAbstractListModel<\/em> abgeleitet wird (<strong>Listing 5<\/strong>).<\/p>\n<p>Auf die verschiedenen Attribute eines <em>ListItems<\/em> greift die Software durch Rollen zu. Es m\u00fcssen wenigstens die Funktionen <em>data()<\/em>, <em>rowCount()<\/em> und <em>roleNames()<\/em> \u00fcberschrieben werden, um die Liste lesend darzustellen. Im vorliegenden Model werden auch die Zust\u00e4nde der R\u00e4ume ver\u00e4nderbar sein, sodass auch die Funktion <em>setData()<\/em> \u00fcberschrieben ist. Hier ist besonders auf das Emittieren des Signals <em>dataChanged<\/em> am Ende der Funktion hinzuweisen, ohne die keine \u00c4nderung propagiert wird.<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">emit dataChanged(index, index);<\/pre>\n<p>Anstatt die Zugriffe auf Attribute per Rollen zu spezifizieren, gibt es auch die M\u00f6glichkeit, die Werte auf Spalten zu verteilen. In diesem Fall leitet man vom <em>QAbstractTableModel<\/em> ab und verteilt die Attribute auf Columns. Entsprechende Beispiele lassen sich der Dokumentation entnehmen.<\/p>\n<p>Die Raumliste auf der rechten Seite wird also komplett ausgegeben. Bei der \u00dcbersicht links soll dagegen eine Filterung m\u00f6glich sein. Da die Datenquelle gleich bleibt \u2013 alle R\u00e4ume \u2013 wird lediglich ein <em>QSortFilterProxyModel<\/em> ben\u00f6tigt, das in diesem Fall das Herausfiltern aller nicht gew\u00fcnschten Elemente \u00fcbernimmt. Hierf\u00fcr wird die Funktion <em>filterAcceptsRow()<\/em> \u00fcberschrieben. Dar\u00fcber hinaus k\u00f6nnte es eine Sortierung geben, indem<em> lessThan()<\/em> \u00fcberschrieben wird.<\/p>\n<p><strong>Listing 6<\/strong> zeigt den Vorteil registrierter Elemente innerhalb des <em>QmlContexts<\/em>: Der <em>BuildingProxy<\/em> muss im <em>QmlContext<\/em> bekannt gemacht werden, damit die <em>ListView<\/em> darauf zugreifen kann. Ist er aber einmal registriert, kann man von \u00fcberall darauf zugreifen. Die Automatismen innerhalb von Qt veranlassen das Update der betroffenen Fenster.<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true\">import QtQuick 2.3 \r\nimport QtGraphicalEffects 1.0 \r\nimport \u201ecomponents\u201c \r\n\r\nFocusScope { \r\n    id: root \r\n\r\n    GridView { \r\n        id: roomList \r\n        model: roomsModel \r\n        cellWidth: root.width\/2-20 \r\n        cellHeight: model.count &gt; 0 ? root.height \/ (model.count * 2) : 0 \r\n        focus: true \r\n        clip: true \r\n        delegate: roomDelegate \r\n        highlight: Rectangle { color: \u201elightsteelblue\u201c; radius: 5 } \r\n        anchors { \r\n            left: parent.left \r\n            right: scrollbar.left \r\n            top: parent.top \r\n            topMargin: 5 \r\n            bottom: parent.bottom \r\n        } \r\n    } \r\n\r\n    ... \r\n\r\n}  \r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 4: ROOMGRIDVIEW.QML<\/p>\n<p>&nbsp;<\/p>\n<h3 class=\"p1\">Tastatursteuerung<\/h3>\n<p class=\"p1\">Ein immer wieder stark missverstandenes Konzept ist der Fokus \u2013 egal unter welchem Framework. Befinde ich mich in einer Eingabezeile und dr\u00fccke [Tab], m\u00f6chte ich in der n\u00e4chsten Eingabezeile landen. So weit der einfache Fall. Aber das simple Highrise-Beispiel weist schon drei verschiedene und eigentlich gleichzeitig aktive Fokusse auf: die <em>EditBox<\/em> stellt die gerade genannte Eingabem\u00f6glichkeit dar. Es gibt noch ein fokussiertes Element, das beim Dr\u00fccken von [Return] den Lichtstatus des Raums \u00e4ndert \u2013 analog zu einem Doppelklick. Hierf\u00fcr ist <em>Keys.onReturnPressed<\/em> verantwortlich.<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">Keys.onReturnPressed: { \r\n  model.lightstate = model.lightstate===1 ? 0 : 1; \r\n}<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true\">#ifndef BUILDINGMODEL_H \r\n#define BUILDINGMODEL_H \r\n#include  \r\n#include  \r\n#include \u201emodel\/room.h\u201c \r\n\r\nclass Building; \r\n\r\nclass BuildingModel : public QAbstractListModel \r\n{ \r\n    enum RoomRoles { \r\n        IdRole = Qt::UserRole + 1, \r\n        NameRole = Qt::UserRole + 2, \r\n        LightStateRole = Qt::UserRole + 3, \r\n        RoomStateRole = Qt::UserRole + 4, \r\n    }; \r\n\r\npublic: \r\n    BuildingModel(Building* building); \r\n    QVariant data(const QModelIndex &amp;index, int role = Qt::DisplayRole) const; \r\n    virtual bool setData(const QModelIndex &amp;index, const QVariant &amp;value, int role = Qt::EditRole); \r\n    int rowCount(const QModelIndex &amp;parent = QModelIndex()) const; \r\n    QHash&lt;int, QByteArray&gt; roleNames() const; \r\n\r\n    QList&lt;Room*&gt; GetRooms() { \r\n        return _rooms; \r\n    } \r\n\r\nprivate: \r\n    QList&lt;Room*&gt; _rooms; \r\n    friend class BuildingProxy; \r\n}; \r\n\r\nclass BuildingProxy: public QSortFilterProxyModel \r\n{ \r\nprotected: \r\n    virtual bool filterAcceptsRow(int source_row, const QModelIndex &amp;source_parent) const; \r\n}; \r\n\r\n#endif \/\/ BUILDINGMODEL_H \r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 5: BUILDINGMODEL.H<\/p>\n<p>&nbsp;<\/p>\n<p>Ebenso gibt es ein fokussiertes Element, das hier allerdings nur der Navigation dient. Jeweils ein Fokusbereich wird in QML durch einen <em>FocusScope<\/em> eingeschlossen, wie in den oben genannten Dateien zu sehen ist. Innerhalb <em>Highrise\/BuildingView.qml<\/em> werden alle drei Bereiche durch <em>activeFocusOnTab<\/em> als erreichbar markiert.<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">RoomListView { \r\n  id: roomList \r\n  width: root.width\/2 \r\n  focus: true \r\n  activeFocusOnTab: true \r\n  anchors { \r\n    margins: 20 \r\n    left: parent.left \r\n    top: header.bottom \r\n    bottom: parent.bottom \r\n  } \r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h3>Eigene Komponenten<\/h3>\n<p>Streng genommen wurden in Highrise bereits mehrere eigene Komponenten benutzt: Jede QML-Datei kann als Typ in einer anderen QML-Datei verwendet werden, so wie in <strong>Listing 4<\/strong> und <strong>Listing 6<\/strong> f\u00fcr die beiden Ansichten links und rechts.<\/p>\n<p>Aber auch alle Dateien unter <em>Highrise\/components<\/em> wurden als eigenst\u00e4ndige Controls konzipiert, sei es die <em>EditBox<\/em> mit eingebettetem Icon oder die <em>Scrollbar<\/em>. Je mehr man mit Qt und Qml arbeitet, desto eher verzichtet man auf den Einsatz vorgefertigter Komponenten wie einen Button.<\/p>\n<p>Einen Button kann man mithilfe der style-Property in begrenztem Umfang in seinem Aussehen beeinflussen. Aber durch den Einsatz von <em>Rectangles<\/em>, <em>MouseAreas<\/em> und sonstigen Basiselementen l\u00e4sst sich das Einsatzgebiet einer Komponente noch genauer spezifizieren.<\/p>\n<p>Als konkretes Beispiel sei ein <em>RangeSlider<\/em> genannt. also ein Slider mit einem unteren und einem oberen Wert. Es hilft nicht, zwei Slider \u00fcbereinanderzulegen. Stattdessen nimmt man ein Rechteck als Zuglinie des Sliders, zwei Rectangles f\u00fcr die Thumbs, darin jeweils eine MouseArea, um die Thumbs anfassbar zu machen, und \u00fcbernimmt die Werte mittels JavaScript-Code. Nat\u00fcrlich besteht auch die M\u00f6glichkeit, eigene Controls in C++ zu implementieren und sie innerhalb von Qml zur Verf\u00fcgung zu stellen.<\/p>\n<pre class=\"theme:eclipse height:300 lang:default decode:true \">import QtQuick 2.3 \r\nimport QtQuick.Controls 1.2 \r\nimport \u201ecomponents\u201c \r\n\r\nRectangle { \r\n    id: root \r\n    color: \u201atransparent\u2018 \r\n\r\n    BuildingViewHeader { \r\n        id: header \r\n        activeFocusOnTab: true \r\n        onFocusChanged: { \r\n            if(focus===false) \r\n                roomList.forceActiveFocus(); \r\n        } \r\n    } \r\n\r\n    RoomListView { \r\n        id: roomList \r\n        width: root.width\/2 \r\n        focus: true \r\n        activeFocusOnTab: true \r\n        anchors { \r\n            margins: 20 \r\n            left: parent.left \r\n            top: header.bottom \r\n            bottom: parent.bottom \r\n        } \r\n    } \r\n\r\n    RoomGridView { \r\n        id: roomGrid \r\n        activeFocusOnTab: true \r\n        anchors { \r\n            margins: 20 \r\n            top: header.bottom \r\n            bottom: parent.bottom \r\n            left: roomList.right \r\n            right: parent.right \r\n        } \r\n    } \r\n} \r\n<\/pre>\n<p class=\"wp-caption-text\">LISTING 6: BUILDINGVIEW.QML<\/p>\n<p>&nbsp;<\/p>\n<h3>Animationen<\/h3>\n<p>Zugegebenerma\u00dfen sind die M\u00f6glichkeiten von WPF durch den Einsatz von Storyboards gewaltig, allerdings nicht immer leicht einzusetzen. Es gibt Entwickler, die Blend nur verwenden, um Optionen der Storyboards zu konfigurieren.<\/p>\n<p>Qml bietet an dieser Stelle ebenfalls eine Statemachine. In <em>EditBox<\/em> wird nur ein State deklariert: Wenn ein Text eingegeben wurde, verschwindet das Watermark-Icon. Wird der neue State betreten, werden die angegebenen Properties gesetzt. Wird er andererseits wieder verlassen, werden die \u00c4nderungen zur\u00fcckgenommen. Hier kn\u00fcpft sich der State an eine Bedingung. Der State kann aber auch durch eine Zuweisung <em>state = \u201chasText\u201d<\/em> gewechselt werden.<\/p>\n<p>Da das Ausblenden des Icons zu abrupt w\u00e4re, soll es animiert werden. Hier kommen die Transitions ins Spiel. Sie beschreiben die \u00dcberg\u00e4nge im Allgemeinen oder von einem State in einen anderen. In den Transitions beschreiben die verschiedenen Animation-Elemente die Art der Animation. Es gibt beispielsweise einen <em>OpacityAnimator<\/em>, einen <em>RotationAnimator<\/em> oder allgemein eine <em>NumberAnimation<\/em>. Die Animationen k\u00f6nnen durch <em>ParallelAnimation<\/em> parallel ablaufen oder durch <em>SequentialAnimation<\/em> nacheinander, und beide Typen lassen sich schachteln.<\/p>\n<div id=\"attachment_7546\" style=\"width: 810px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-7546\" class=\"wp-image-7546\" src=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise.jpg\" alt=\"Highrise\" width=\"800\" height=\"582\" srcset=\"https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise.jpg 1185w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise-300x218.jpg 300w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise-768x559.jpg 768w, https:\/\/www.centigrade.de\/wordpress\/wp-content\/uploads\/Highrise-1024x745.jpg 1024w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/a><p id=\"caption-attachment-7546\" class=\"wp-caption-text\">Das Beispielprojekt Highrise mit einer GridView und einer ListView<\/p><\/div>\n<h3 class=\"p1\">Eigene Ressourcen<\/h3>\n<p class=\"p1\">Was man als WPF-Entwickler sofort vermisst, ist die M\u00f6glichkeit, seine Ressourcen in ResourceDictionaries auszulagern. Es ist einfach schlechter Stil, die Farbdefinitionen in allen Controls zu wiederholen. Aber es gibt Abhilfe: die Singleton- Module. Die erste Zeile der entsprechenden Qml-Datei lautet <em>pragma Singleton<\/em>, darin wird ein <em>QtObject<\/em> erzeugt mit allen Properties. F\u00fcr ein Farb-Singleton lautet also beispielsweise eine <em>Colors.qml<\/em><\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">pragma Singleton \r\nimport QtQuick 2.4 \r\n\r\nQtObject { \r\n    property var colorWhite: '#FFFFFFFF' \r\n    property var brushBackground: colorWhite \r\n...<\/pre>\n<p class=\"p1\">oder aber f\u00fcr Fonts.qml entsprechend<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">pragma Singleton \r\nimport QtQuick 2.4 \r\n\r\nQtObject { \r\n  property font mainFont: Qt.font({ \r\n    family: ttfFont.name, \r\n    pointSize: 24 \r\n  }) \r\n  property FontLoader ttfFont: FontLoader {\r\n    id: _ttfFont;\r\n    source: 'qrc:\/assets\/fonts\/myfont.ttf'\r\n  } \r\n...<\/pre>\n<p class=\"p1\">Beide Dateien werden als Singleton-Module registriert und k\u00f6nnen in jeder anderen QML-Datei benutzt werden:<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">  auto fontsModule =\r\n    QStringLiteral(\"qrc:\/qml\/modules\/Fonts.qml\"); \r\n  qmlRegisterSingletonType( QUrl(fontsModule), \"Fonts\",\r\n    1, 0, \"Fonts\" ); \r\n  auto colorsModule =\r\n    QStringLiteral(\"qrc:\/qml\/modules\/Colors.qml\"); \r\n  qmlRegisterSingletonType( QUrl(colorsModule),\r\n    \"Colors\", 1, 0, \"Colors\" ); \r\n...<\/pre>\n<h3 class=\"p1\">Debugging<\/h3>\n<p class=\"p1\">Werden im XAML die wesentlichen Probleme \u2013 wenn \u00fcberhaupt \u2013 in der Console ausgegeben, greift man in QML auf <em>console.debug()<\/em> innerhalb des JavaScript-Codes zur\u00fcck. Schwierig wird es, wenn Bindungen zu den Modellen nicht greifen oder Update-Signale nicht emittiert werden.<\/p>\n<p class=\"p1\">An dieser Stelle kann vielleicht <a href=\"https:\/\/github.com\/KDAB\/GammaRay\" target=\"_blank\" rel=\"noopener noreferrer\">dies <\/a>helfen. \u00c4hnlich Snoop stellt es den aktuellen Baum dar, listet Signale und verbundene Slots auf, visualisiert den Zustand der StateMachine und dergleichen mehr.<\/p>\n<h3 class=\"p1\">Fazit<\/h3>\n<p class=\"p1\">Es gibt viele Gemeinsamkeiten zwischen QML und XAML. Was bei der einen Sprache ein Signal darstellt, \u00fcbernimmt bei der anderen ein <em>INotifyPropertyChanged<\/em>. Es existieren Bindungen und Attached Properties. In beiden Welten entspricht es schlechtem Stil, zu viel Funktionalit\u00e4t innerhalb des Markups zu realisieren.<\/p>\n<p class=\"p1\">Aber es gibt auch gro\u00dfe Unterschiede. Da Techniken wie Templating und ResourceDictionaries in der von WPF gewohnten Weise fehlen, ist die Herangehensweise bei Qt eine andere. Daf\u00fcr erscheint die Lernkurve insgesamt etwas weniger steil. Angenehm f\u00e4llt auch auf, dass die Anreicherung der Oberfl\u00e4chenfunktionalit\u00e4t mittels JavaScript direkter ist. Techniken wie Behaviors werden nicht gebraucht. Neben den genannten Plattformen fehlt das nahende Internet of Things. Es besteht die M\u00f6glichkeit, einen Qt-optimierten Software-Stack auf Ger\u00e4ten zu booten. Ob sich dieser Ansatz gegen\u00fcber Windows 10 behaupten wird, muss sich zeigen.<\/p>\n<p class=\"p1\"><em>Dieser Artikel erschien erstmalig in der <a href=\"http:\/\/www.dotnetpro.de\/\" target=\"_blank\" rel=\"noopener noreferrer\">dotnetpro<\/a><\/em><\/p>\n","protected":false},"author":33,"featured_media":0,"template":"","tags":[483,484,40],"class_list":["post-7528","blog","type-blog","status-publish","hentry","tag-qt","tag-xamarin","tag-xaml"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/7528","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/users\/33"}],"version-history":[{"count":1,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/7528\/revisions"}],"predecessor-version":[{"id":11368,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/blog\/7528\/revisions\/11368"}],"wp:attachment":[{"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/media?parent=7528"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.centigrade.de\/de\/wp-json\/wp\/v2\/tags?post=7528"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}