demo1_hello.qml
This is the main QML file that loads and unloads the plugin code and its toolbar buttons.
// <!--Imports...-->
import org.qfield
import Theme
Item {
id: plugin
parent: iface.mapCanvas()
anchors.fill: parent
Loader {
id: pluginLoader
active: false
anchors.fill: parent
source: Qt.resolvedUrl('./components/d1_plugin_component.qml')
}
QfToolButton {
id: pluginButton
bgcolor: Theme.darkGray
iconSource: Theme.getThemeVectorIcon('ic_camera_photo_black_24dp')
iconColor: Theme.mainColor
round: true
onClicked: {
iface.logMessage("Loading d1_plugin_component.qml")
pluginLoader.active = !(pluginLoader.active)
}
}
Component.onCompleted: {
iface.addItemToPluginsToolbar(
pluginButton
)
}
}
Classes in Our Demo
QObject
|___ QtObject
| |___ Item (Base for Visual Types, like QtWidget)
| | |___ Rectangle
| | |___ Text
| | |___ Image
| | |___ MouseArea
| | |___ Loader
| | |___ Controls
| | | |___ Button
| | | |___ AbstractButton
| | | | |___ QToolButton
| | | | | |___ QfToolButton
| |___ Component (Container/Utility Class. Not related to Item!)
| |___ Connections
| |___ Other non visual types and utilities
Item (plugin):
- Item is our root element. Everything in the plugin must be defined within this object.
- Item is a base parent class that corresponds to QtWidget. All other widgets inherit from it.
Properties
- id: This is the object name ("plugin"). This property is available for all QML objects. Oddly enough, it is optional and is often omitted for items that are not referenced programmatically (like labels)
- parent: Just like in PyQt, this is a reference to the parent widget. It is only necessary to define parent in the root item. For everything within the item, parent is implicitly defined by the nested structure. It is absolutely necessary to define the parent on the root element Item as the map canvas, because otherwise it would have no parent and no place in QField's user interface and would not be rendered.
- anchors: This is a very CSS-like mechanism that defines the geometry bounds of the widget relative to other items. Normally, the geometry is defined in relation to the parent.
- anchors.fill: parent tells the widget to expand to cover the entire parent widget. So the plugin item anchor tells us that this widget will cover the entire map canvas - i.e., the entire screen.
- Warning: Pay special attention to your anchors. If you configure them incorrectly, your component could disappear or be rendered in unexpected ways, and it can be hard to debug.
Members of the Plugin Item:
- Loader: The Loader loads and unloads the actual plugin object.
- QfToolButton: This is the button that is clicked to open and close the plugin.
Component.onCompleted
This is the signal-slot construction that triggers the addition of the pluginButton to the QField toolbar when the plugin item has been created.
Looking at Component.onCompleted within Item and also considering that the Loader loads a Component, one might be tempted to think that Component is the parent class of Item, and this signal is inherited.
This is not the case. Component and Item have nothing to do with each other.
- Component is an "attached property mechanism". What does that mean? I think of it as a class that wraps other objects and provides signals when they are constructed and destructed.
- Component has 2 signals:
- onCompleted (the object has finished loading)
- onDestruction (like close on a QtWidget)
- In our Item, the Component is used to create a signal/slot connection that executes when the Item construction is complete.
Loader (pluginLoader):
A Loader is an Item that dynamically loads and unloads other Items. Loaders construct and destruct their Components when their "active" property value is toggled, which allows runtime triggers for construction and destruction. This makes it possible to trigger the loading of the plugin with a button.
Why We Need a Loader Here:
Our plugin will do things like reading layers in the project. We don't want this to happen until the project is loaded. In fact, we want our plugin code to wait until the user asks for it by clicking on the plugin's tool button or later selecting an object on the map.
If we put our plugin code directly into our root item without the Loader, we would be trying to read layers that don't exist yet and perform a lot of processing that may not be needed.
Properties
- id and anchors: because it is an Item widget
- not parent: because parent is implicitly defined from the nesting. The plugin item is its parent. Only the root item must define its parent.
- active: the boolean property that determines whether the Loader's code is constructed or not. The QfToolButton will load and unload the code by triggering changes to the value of this property.
- source: the path to the QML file containing the Items to be loaded. The source is referred to as a source component, although if you look into the QML file, you will see Items, not Components. This is because the Loader internally creates a Component object whose job it is to load the Items in the QML file referenced by source.
QfToolButton (pluginButton)
This is simple. QfToolButton is a tool button widget that we have connected to the Loader. We could use QToolButton, but we use QField's QfToolButton here so it maintains QField's design. For the rest of the plugin, I tend to prefer the Qt objects.
I can use QfToolButton or any other QField widget because I imported them from org.qfield.
This Item is not in the Loader because it is the Item that controls the Loader.
Properties
- round: A property of QfToolButton. Makes the button a circle.
- iconSource: A property of QfToolButton. Out of pure laziness, I used one of the predefined icons in the QField Theme module. We're throwing this button away in the next demo anyway.
- iconColor and bgcolor: I adopted defined colors from the imported Theme module, which is a good idea if you want your colors to match QField's colors.
- onClicked: Is a signal handler that contains JavaScript. When the button is clicked, it outputs a message to the log (which is the QField log that the user sees) and toggles the boolean active property of the pluginLoader, which triggers either a construction or destruction of the plugin component (collection of Items in the component QML file)
Remember, it's not Component.onClicked. Everything except onCompletion and onDestruction are members of the Item component and have nothing to do with Component.