Qt Quick - How to write a cross platform mapping application with a QML front-end and C++ back-end

2427
1
12-19-2016 01:57 PM
Labels (1)
LucasDanzinger
Esri Frequent Contributor
3 1 2,427

Up until now, Qt ArcGIS Runtime developers could follow one of two paradigms - either write their apps in all C++ with Qt Widgets or all QML with Qt Quick. One of the new and exciting things about our newest release of ArcGIS Runtime 100.0 is that we now support a third paradigm - writing your frontend in QML and your backend in C++ using the Qt Quick framework. This is a great opportunity for C++ developers to embrace a more modern approach to the Qt framework, and to start leveraging some of the advantages that QML brings to the table as a UI language. Some of the main benefits you will see with using Qt Quick is that:

  • You can easily seperate your business logic (C++) from your declarative UI (QML)
  • This is supported on nearly every platform. When using the Qt Quick framework, our C++ API is now supported on all the same platforms as our QML API.
  • QML supports touch interactions
  • QML scales well between different platforms and form factors
  • QML supports fluid animations and other similar features expected in modern user interfaces

If you'd like to get started using this new paradigm, there are 3 main concepts to understand: registering C++ classes as QML types, creating C++ methods that can be called from QML, and creating QML properties.

Registering C++ classes as QML types:

In order to access a C++ class in QML, you must register it as a QML type. For example, one of the most common things you will want to do as an ArcGIS Runtime developer is declare a MapView in QML, but write the business logic for the MapView in C++. To do this, you will register the MapQuickView (a version of the MapView that inherits from QQuickItem) as a QML type somewhere in the main.cpp with the following syntax:

qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView");

In this case, replace "Esri.Samples" with whatever you would like your namespace to be called, replace 1 and 0 with the major and minor version of your application, and optionally replace "MapView" with whatever you want to call the type in QML. Once you have done this, you can declare a MapView in QML by importing your new namespace, and declaring a MapView:

import Esri.Samples 1.0‍‍

MapView {
    anchors.fill: parent
    objectName: "mapView"
}‍‍‍‍‍‍‍‍‍‍

In this case, we gave the MapView an object name so that we could obtain it from the C++ side, and then add a Map into the MapView in C++.

// find QML MapView component
m_mapView = findChild<MapQuickView*>("mapView");

// create a new basemap instance
Basemap* basemap = Basemap::imageryWithLabels(this);
// create a new map instance
m_map = new Map(basemap, this);
// set map on the map view
m_mapView->setMap(m_map);‍‍‍‍‍‍‍‍‍

This full workflow can be found in the Display a Map sample. You would use the same workflow to create a 3D scene, and a similar workflow for any other QObject* type that you want to expose from C++ to QML. This is only a very basic example. More information on how this all works, along with additional parameters and workflows can be found in Qt's Documentation.

Invoking C++ methods from QML

Once you have your C++ classes exposed as QML types, the next logical step is to be able to call a method on that class from the QML side. For example, you may have a QML Slider control that you want to rotate the MapView whenever the Slider is moved. To do this, you simply need to add the Q_INVOKABLE macro to any of your method declarations that you want to be able to be called from QML:

Q_INVOKABLE void setMapViewRotation(double degrees);

Then, from QML, you would call the method with the following syntax:

Slider {
    onPressedChanged: {
        // Call C++ invokable function to change the rotation of the map view
        mapRotationSample.setMapViewRotation(value);
    }
}‍‍‍‍‍‍

This full workflow can be found in the Map Rotation sample. The section entitled "Exposing Methods (Including Qt Slots)" in this Qt documentation explains this in further detail.

Creating bindable QML properties

Once you begin using QML, you begin to realize that the true power in QML is not in calling functions whenever some signal emits, but rather in using property binding so that things are updated automatically without any imperative code needing to be written at all. This is true declarative code, and is the way QML was intended to be used. For example, if you are signing into your organizations portal, and you want to obtain several things about the user's login information to display on the screen, such as username, thumbnail, and email, you don't want to call a getter from the QML side every time. Instead, you want to simply create a property that you can bind onto, and whenever that property changes, all UI elements will automatically update with the new information.

To do this, you must follow several steps. First, you must declare your property in the C++ header file with the Q_PROPERTY macro. This macro requires you give it a name, a type, a getter, an optional setter, and a signal. For example:

Q_PROPERTY(QString username READ username NOTIFY usernameChanged)

In this example, QString is the type, "username" is the name referenced from QML, "username" is the getter in C++, and "usernameChanged" is the signal.  Next, you need to set up your declaration for the getter and signal:

private:
  QString username() const;

signals:
  void usernameChanged();‍‍‍‍‍

The C++ implementation would look something like this:

QString PortalUserInfo::username() const
{
  if (m_user)
    return m_user->username();

  return "UNKNOWN";
}‍‍‍‍‍‍‍

Whenever this value changes, you must emit the signal, which will trigger the Q_PROPERTY to update any other object that has bound to it:

void PortalUserInfo::onPortalLoadStatusChanged(LoadStatus loadStatus)
{
  if (loadStatus == LoadStatus::Loaded)
  {
    m_user = m_portal->portalUser();
    emit usernameChanged(); // this will trigger all elements to update with the new value
  }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

 

Finally, bind to the Q_PROPERTY in QML:

Text {
    text: username
}‍‍‍

This full workflow can be found in the PortalUserInfo sample. Further details and options for specifying bindable properties with QML can be found in the Qt documentation.

These are the basic things you will need to understand to get started with using the Qt Quick framework with C++ and QML. All of our samples on GitHub use this new paradigm, so I highly encourage you to check these out to get some ideas on how everything works. We also have a new template that is integrated into Qt Creator that will get you started with building Qt Quick C++/QML applications.

1 Comment
About the Author
I'm a Geographer working in Product Development at Esri, focusing my time on the ArcGIS Runtime SDKs. I'm an Esri Certified ArcGIS Desktop Professional (10 years experience working with ArcGIS) with a wide range of technical skills including native application development, spatial databases, desktop/web GIS, and scripting. My Master's degree is in GIS with a focus in Natural Resource Management. Currently, I'm most interested in building cross-platform and lightweight apps using our ArcGIS Runtime SDK for Qt.