{"id":7564,"date":"2017-02-28T16:00:26","date_gmt":"2017-02-28T15:00:26","guid":{"rendered":"http:\/\/www.centigrade.de\/blog\/en\/?p=7564"},"modified":"2020-02-17T10:24:29","modified_gmt":"2020-02-17T09:24:29","slug":"multi-platform-mobile-software-development-qt-instead-of-xamarin","status":"publish","type":"blog","link":"https:\/\/www.centigrade.de\/en\/blog\/multi-platform-mobile-software-development-qt-instead-of-xamarin\/","title":{"rendered":"Multi-platform mobile software development: Qt instead of Xamarin"},"content":{"rendered":"<p>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 \u2013 without re-implementation: <a href=\"http:\/\/www.qt.io\/download\" target=\"_blank\" rel=\"noopener noreferrer\">Qt<\/a>.<\/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 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.<\/p>\n<p>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<em> clicked()<\/em> signal, which view components can bind to a slot <em>onClicked()<\/em>.<\/p>\n<p>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.<!--more--><\/p>\n<h3>First window<\/h3>\n<p>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 <strong>listing 1<\/strong>. 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 <strong>listing 2<\/strong>.<\/p>\n<p>Declared properties can be read inside the QML code. To make changes visible there as well, signals have be emitted when setting the values. <strong>Listing 3<\/strong> clarifies the use of properties: <em>text<\/em> elements offer read access, <em>TextInput<\/em> also allows changes. The binding of the first variant sends the new text directly to the second <em>TextInput<\/em>. In the second variant the change is executed with a function call and needs no further 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>Lists, filters, sorting<\/h3>\n<p>How about the handling of lists? In WPF, a <em>ListBox<\/em> is filled by an <em>ItemsSource<\/em>, its style is changed and the <em>ListItem<\/em> templates are customized. In QML, the list is filled according to a model and the style of the elements is described with a delegate.<\/p>\n<p>There are multiple ways to present elements. The Highrise example uses a <em>GridView<\/em> for the multi-column display and a <em>ListView<\/em> for the master view. Another option which this article does not cover would be listing using a Repeater.<\/p>\n<p>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.<\/p>\n<p><strong>Listing 4<\/strong> renders the list of all rooms with their current status. A <em>GridView<\/em> 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 <em>BuildingModel<\/em>, a class derived from <em>QAbstractListModel<\/em> (<strong>listing 5<\/strong>).<\/p>\n<p>The software accesses the different attributes of a <em>ListItem<\/em> through roles. To read and display the list, at least the functions <em>data(), rowCount()<\/em> and<em> roleNames ()<\/em> must be overwritten. In the current model, the room status will be editable too, so the function <em>setData()<\/em> is overwritten as well. Here, the emission of the <em>dataChanged<\/em> signal at the end of the function is of note, because no change would be propagated without it.<\/p>\n<pre class=\"theme:plain-white lang:default decode:true\">emit dataChanged(index, index);<\/pre>\n<p>Instead of specifying the attribute accesses by roles, the values can also be distributed to columns. In this case derive from <em>QAbstractTableModel<\/em> and distribute the attributes to columns. Corresponding examples can be found in the documentation.<\/p>\n<p>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 \u2013 all rooms \u2013 only a <em>QSortFilterProxyModel<\/em> is needed which in this case handles all the filtering out of unwanted elements. For this the function <em>filterAcceptsRow()<\/em> is overwritten. Building on that, overwriting <em>lessThan()<\/em> could enable sorting.<\/p>\n<p><strong>Listing 6<\/strong> shows the advantage of registered elements within the <em>QmlContext<\/em>: the <em>BuildingProxy<\/em> has to be registered in the <em>QmlContext<\/em>, so the <em>ListView<\/em> can access it. Once it is registered, however, it can be accessed from everywhere. The automatisms in Qt trigger the update of affected windows.<\/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\">Keyboard controls<\/h3>\n<p class=\"p1\">Focus is a frequently misunderstood concept regardless of framework. If I\u2019m 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] \u2013 analogous to a double click. <em>Keys.onReturnPressed<\/em> is responsible for this.<\/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>Likewise there is a focused element that in this case only serves as navigation. Each focus area in QML is enclosed in a <em>FocusScope<\/em> as can be seen in the files mentioned above. Within\u00a0<em>Highrise\/BuildingView.qml<\/em> all three areas are marked as accessible with <em>activeFocusOnTab.<\/em><\/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<h3>Custom Components<\/h3>\n<p>Strictly speaking, Highrise already uses multiple custom components: every QML file can be used as a type in another QML file, like in <strong>listing 4<\/strong> and <strong>listing 6<\/strong> for the two left and right views.<\/p>\n<p>But all other files in<em> Highrise\/components<\/em> were designed as independent controls too \u2013 the <em>EditBox<\/em> 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.<\/p>\n<p>The look button can be influenced in a limited way by the style property. But by using <em>Rectangles, MouseAreas<\/em> and other basic elements, the usage of a component can be specified more precisely.<\/p>\n<p>For example take a <em>RangeSlider<\/em>, 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.<\/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>Animations<\/h3>\n<p>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.<\/p>\n<p>QML provides a state machine here as well. In <em>EditBox<\/em> 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 <em>state = &#8220;hasText&#8221;<\/em>.<\/p>\n<p>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 <em>OpacityAnimator<\/em>, <em>RotationAnimator<\/em> or a general <em>NumberAnimation<\/em>. Animations can run simultaneously with <em>ParallelAnimation<\/em> or one after another with <em>SequentialAnimation<\/em>. The two types can also be nested.<\/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\">This example shows Highrise with a GridView and a ListView<\/p><\/div>\n<h3 class=\"p1\">Custom ressources<\/h3>\n<p class=\"p1\">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<em> pragma Singleton<\/em>, inside a <em>QtObject<\/em> with all properties is created. For a color singleton the <em>colors.qml<\/em> is<\/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\">respectively for Fonts.qml:<\/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\">Both files are registered as singleton modules and can be used in all other QML files:<\/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>While in XAML the main problems are displayed in the console if they are displayed at all, in QML <em>console.debug()<\/em> in the JavaScript code is used. Problems can appear if bindings to models do not work or update signals are not emitted.<\/p>\n<p>Here, maybe <a href=\"https:\/\/github.com\/KDAB\/GammaRay\" target=\"_blank\" rel=\"noopener noreferrer\">GammaRay<\/a> 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.<\/p>\n<h3 class=\"p1\">Fazit<\/h3>\n<p>QML and XAML have a lot in common. A signal in one language is a <em>INotifyPropertyChanged<\/em>.\u00a0 Bindings and attached properties exist. It is considered poor style in both worlds to implement too much functionality in the markup.<\/p>\n<p>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\u2019t 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.<\/p>\n<p><em>This article first appeared in\u00a0<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":[155,483,484,40],"class_list":["post-7564","blog","type-blog","status-publish","hentry","tag-design-engineering","tag-qt","tag-xamarin","tag-xaml"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/blog\/7564","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/users\/33"}],"version-history":[{"count":1,"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/blog\/7564\/revisions"}],"predecessor-version":[{"id":11369,"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/blog\/7564\/revisions\/11369"}],"wp:attachment":[{"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/media?parent=7564"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.centigrade.de\/en\/wp-json\/wp\/v2\/tags?post=7564"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}