Utilizing list models with ArcGIS Runtime SDK for Qt

1721
2
08-21-2017 10:34 AM
Labels (1)
LucasDanzinger
Esri Frequent Contributor
4 2 1,721

ArcGIS Runtime SDK for Qt 100 brought many new features to the SDK, such as Raster support, 3D, Vector Tiles, and much more. Beyond many of these big ticket items, there were also lots of architectural and design improvements to the API that make it much easier and more convenient to develop apps with the SDK. One of my favorite new features of the ArcGIS Runtime SDK for Qt (version 100) is the extensive use of list models throughout the API. List models are data structures similar to to lists or vectors that can be used directly in any of the various views that Qt supports (i.e. ListView, GridView, etc). What this means to you as a developer is that you can very easily take the data returned to you from the Runtime API (like features, graphics, maps, layers, attributes, symbols, attachments, and so on), and display them in a view with no data manipulation or massaging. For example, if I wanted to build a table of contents (TOC) for the operational layers in a map, I could access the Map::operationalLayers property, and this would return to me a list model of the layers in the map. I could then take that list model and pass it directly into a ListView component, and display the data in the ListView. This has many benefits including:

  • It is good practice to separate your UI from your business logic
  • The models and views are dynamic, so if a list model gets updated (e.g. a new layer gets added to the map), the View will update automatically without you writing any code to handle this.
  • For QML, it greatly reduces the amount of imperitive JavaScript code, allowing you to write almost exclusively declarative QML code. Writing as declarative of code as possible is something you should always strive for with QML.

Model/view, MVC, MVVM, and other similar concepts have endless resources explaining the benefits of the pattern and how to best architect your application using these patterns. Because of this, this article will not discuss that type of information, but will instead focus on the fundamentals of working with list models coming from ArcGIS Runtime, and how you can integrate them with Qt's various view types.

There are 3 main concepts that you will need to understand in order to display your data from a list model in a view: Models, Views, and Delegates.

Models
Models are lists of data and are found throughout the ArcGIS Runtime. Some examples include a LayerListModel, LegendInfoListModel, AttachmentListModel, and AttributeListModel. Models have a couple of important things to know about as an application developer. The first thing you need to know is what the model contains. In the example of a LayerListModel, you can read in the documentation that it is a list of Layers available in the map. The second important thing to note are the roles available on the model. For example, the LayerListModel has a name and layerVisible role (among many others). These roles are basically the properties that you can access for each item in the list model. With this information, you should begin to see how one could start building a table of contents.

Views
Views are the UI elements that actually display the data. ListViews and GridViews are some of the most common types. Your phone's music app will likely make heavy use of these types of views, as they might display a list of songs by a particular artist, or they might show a grid of a particular artist's album covers. One important thing to understand is that the View determines what will be shown, but it will not determine how something is shown. For example, in the example of the TOC control, we might know that we want to have a list of layers shown in a view, but the view does not dictate how each element in that list of layers looks (i.e. the text to display, the color of text, the type face, etc). Rather, the delegate controls this information.

Delegates
Delegates describe what each element in the list will look like. Going back to the TOC example, we know that we want each item show a Checkbox/Switch, so that the user can toggle visibility, and then have the layer name to the right of that. This is where the list model's roles come into play. The delegate can access any of the roles that are exposed and documented in the API reference. The view will use the defined delegate as a template for how to display the data coming out of the model.

A simple example

To see this all in practice, I suggest you look at the various samples on GitHub. A good, simple example of this is the "Change Sublayer Visibility" sample. This sample has a basemap and a map image layer that contains 3 sublayers: Cities, Continent, and World.

We can use the ArcGISSublayerListModel to build a simple TOC where you can see the sublayer name, and toggle the visibility. This section of code contains all of the code related to the TOC control that you see in the upper left of this screenshot. Going back to the previous discussion, there are 3 important things we need: a model, a view, and a delegate.

In this case, a ListView is declared, and some anchoring and sizing properties are set. All this is doing is saying that we are going to display a list of something.

ListView {
 id: layerVisibilityListView
 anchors.margins: 10 * scaleFactor
 width: parent.width
 height: parent.height
 clip: true‍‍‍‍‍‍

Next you see a model property. This is setting the View's model property to the map service's sublayer list model. 

model: mapImageLayer.mapImageSublayers‍‍

Finally, we need a delegate to define how each item in the list will appear. In this case, we want text displaying the layer's name, and a Switch component for toggling visibility. These name and visibility properties are available as the roles "name" and "sublayerVisible" (this can be found the in the documentation for ArcGISSublayerListModel).

delegate: Item {
    id: layerVisibilityDelegate
    width: parent.width
    height: 35 * scaleFactor

    Row {
        spacing: 5
        anchors.verticalCenter: parent.verticalCenter
        Text {
            width: 75 * scaleFactor
            text: name // "name" is a role in the list model
            wrapMode: Text.WordWrap
            font.pixelSize: 14 * scaleFactor
        }
        Switch {
            checked: sublayerVisible // "sublayerVisible" is a role in the list model
            onCheckedChanged: {
                sublayerVisible = checked;
            }
        }
   }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

All together, this results in the following code for building a basic TOC:

// Create a list view to display the items
ListView {
    id: layerVisibilityListView
    anchors.margins: 10 * scaleFactor
    width: parent.width
    height: parent.height
    clip: true

    // Assign the model to the list model of sublayers
    model: mapImageLayer.mapImageSublayers

    // Assign the delegate to the delegate created above
    delegate: Item {
        id: layerVisibilityDelegate
        width: parent.width
        height: 35 * scaleFactor

        Row {
            spacing: 5
            anchors.verticalCenter: parent.verticalCenter
            Text {
                width: 75 * scaleFactor
                text: name
                wrapMode: Text.WordWrap
                font.pixelSize: 14 * scaleFactor
            }

            Switch {
                checked: sublayerVisible

                onCheckedChanged: {
                    sublayerVisible = checked;
                }
            }
        }
    }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The above example focuses on Qt Quick, but you can utilize these model types  with Qt Widgets as well. Many of the same concepts apply, but the workflow differs slightly when working in a QWidgets based UI. To use these with widgets, you can bind the model types to a QAbstractItemView (for example a QTableView) by calling setModel. In order to customise the data you wish to display in the view, a good approach is to create your own QIdentityProxyModel to expose those parts of the underlying model data you wish to use. For example, when using a BasemapListModel you could create your own "BasemapsProxyModel” which takes a BasemapListModel as the sourceModel and returns two columns of data from the full list in the BasemapRoles enum (the basemap title and the snippet which describes it). Your proxy model should then override QIdentityProxyModel::columnCount to return a count of 2. For QIdentityProxyModel::data you should return data from the underlying model based on the column of the supplied QModelIndex. Your code could look something like this:

QModelIndex srcIdx = sourceModel()->index(index.row(), 0);
if (role == Qt::DisplayRole)
{
    switch (index.column())
    {
    case 0:
      return sourceModel()->data(srcIdx, Esri::ArcGISRuntime::BasemapListModel::BasemapItemTitleRole);
      break;
    case 1:
      return sourceModel()->data(srcIdx, Esri::ArcGISRuntime::BasemapListModel::BasemapItemSnippetRole);
      break;
    default:
      break;
    }
}

This is only a small introduction to a very large topic. Once these concepts become clear to you, I encourage you to start creating more views for the various models that are exposed in the Runtime API. Here are some additional helpful resources that should help explain model/view programming with Qt.

Additional Resources:

QML:
- QML Book - http://qmlbook.github.io/ch06/
- Models and Views in Qt Quick - http://doc.qt.io/qt-5/qtquick-modelviewsdata-modelview.html
- Using C++ Models with Qt Quick Views - http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html

Qt Widgets Resources:
- Model/View Programming - http://doc.qt.io/qt-5/model-view-programming.html

2 Comments
_____
by
New Member

//

EricBader
Occasional Contributor III

Another great and insightful post!

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.