Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
extending-tutorial.qdoc
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5\page qtqml-tutorials-extending-qml-example.html
6\title Writing QML Extensions with C++
7\brief Tutorial about extending QML with Qt C++.
8
9The \l {Qt Qml} module provides a set of APIs for extending QML through
10C++ extensions. You can write extensions to add your own QML types, extend existing
11Qt types, or call C/C++ functions that are not accessible from ordinary QML code.
12
13This tutorial shows how to write a QML extension using C++ that includes
14core QML features, including properties, signals and bindings. It also shows how
15extensions can be deployed through plugins.
16
17Many of the topics covered in this tutorial are documented in further detail in
18\l{Overview - QML and C++ Integration} and its documentation sub-topics. In
19particular, you may be interested in the sub-topics
20\l{qtqml-cppintegration-exposecppattributes.html}{Exposing Attributes of C++ Classes to QML}
21and \l {qtqml-cppintegration-definetypes.html}{Defining QML Types from C++}.
22
23\section1 Opening the Tutorial Sources
24
25The code in this tutorial is available as part of the Qt sources.
26If you installed Qt with the \QOI, you can
27find the sources in the Qt installation directory under
28Examples/Qt-\QtVersion/qml/tutorials/extending-qml/.
29
30\section1 Creating Project from Scratch
31
32Alternatively, you can follow the tutorial by creating the sources from scratch:
33For each chapter, create a new project using the \e {Qt Quick Application} template
34in \QC, as instructed in \l {\QC: Create Qt Quick Applications}.
35Then follow along by adapting and extending the generated skeleton code.
36
37\section1 Chapter 1: Creating a New Type
38\c extending-qml/chapter1-basics
39
40A common task when extending QML is to provide a new QML type that supports some
41 custom functionality beyond what is provided by the built-in \l {Qt Quick QML Types}{Qt Quick types}.
42For example, this could be done to implement particular data models, or provide
43types with custom painting and drawing capabilities, or access system features
44like network programming that are not accessible through built-in QML features.
45
46In this tutorial, we will show how to use the C++ classes in the Qt Quick
47module to extend QML. The end result will be a simple Pie Chart display implemented by
48several custom QML types connected together through QML features like bindings and
49signals, and made available to the QML runtime through a plugin.
50
51To begin with, let's create a new QML type called "PieChart" that has two properties: a name
52and a color. We will make it available in an importable type namespace called "Charts", with
53a version of 1.0.
54
55We want this \c PieChart type to be usable from QML like this:
56
57\qml
58 import Charts
59
60 PieChart {
61 width: 100; height: 100
62 name: "A simple pie chart"
63 color: "red"
64 }
65\endqml
66
67To do this, we need a C++ class that encapsulates this \c PieChart type and its
68properties. Since QML makes extensive use of Qt's \l{Meta-Object System}{meta object system},
69this new class must:
70
71\list
72\li Inherit from QObject
73\li Declare its properties using the Q_PROPERTY macro
74\endlist
75
76\section2 Class Declaration
77
78Here is our \c PieChart class, defined in \c piechart.h:
79
80\snippet tutorials/extending-qml/chapter1-basics/piechart.h 0
81
82The class inherits from QQuickPaintedItem because we want to override
83QQuickPaintedItem::paint() to perform drawing operations with the QPainter API.
84If the class just represented some data type and was not an item that actually needed
85to be displayed, it could simply inherit from QObject. Or, if we want to extend the
86functionality of an existing QObject-based class, it could inherit from that class instead.
87Alternatively, if we want to create a visual item that doesn't need to perform drawing
88operations with the QPainter API, we can just subclass QQuickItem.
89
90The \c PieChart class defines the two properties, \c name and \c color, with the
91Q_PROPERTY macro, and overrides QQuickPaintedItem::paint(). The \c PieChart
92class is registered using the QML_ELEMENT macro, to allow it to be used from
93QML. If you don't register the class, \c App.qml won't be able to create a
94\c PieChart.
95
96\section2 qmake Setup
97
98For the registration to take effect, the \c qmltypes option is added to
99\c CONFIG in the project file and a \c QML_IMPORT_NAME and
100\c QML_IMPORT_MAJOR_VERSION are given:
101
102\snippet tutorials/extending-qml/chapter1-basics/chapter1-basics.pro 0
103
104Additionally, a \l{Module Definition qmldir Files}{qmldir} file needs to be added manually to
105create a \l{Writing QML Modules}{QML module}.
106
107\quotefile tutorials/extending-qml/chapter1-basics/qmldir
108
109\section2 CMake Setup
110
111For the registration to take effect when using CMake, use the
112\l{qt6_add_qml_module} {qt_add_qml_module} command:
113
114\snippet tutorials/extending-qml/chapter1-basics/CMakeLists.txt 0
115
116The \l{qt6_add_qml_module}{qt_add_qml_module} API automatically generates a
117\c qmldir file for the QML module.
118
119\section2 Class Implementation
120
121The class implementation in \c piechart.cpp simply sets and returns the
122\c m_name and \c m_color values as appropriate, and implements \c paint() to
123draw a simple pie chart:
124
125\snippet tutorials/extending-qml/chapter1-basics/piechart.cpp 0
126\dots 0
127\snippet tutorials/extending-qml/chapter1-basics/piechart.cpp 1
128
129\section2 QML Usage
130
131Now that we have defined the \c PieChart type, we will use it from QML. The \c
132App.qml file creates a \c PieChart item and displays the pie chart's details
133using a standard QML \l Text item:
134
135\snippet tutorials/extending-qml/chapter1-basics/App.qml 0
136
137Notice that although the color is specified as a string in QML, it is automatically
138converted to a QColor object for the PieChart \c color property. Automatic conversions are
139provided for various other \l {QML Value Types}{value types}. For example, a string
140like "640x480" can be automatically converted to a QSize value.
141
142We'll also create a C++ application that uses a QQuickView to run and
143display \c App.qml.
144
145Here is the application \c main.cpp:
146
147\snippet tutorials/extending-qml/chapter1-basics/main.cpp 0
148
149\section2 Project Build
150
151To build the project we include the files, link against the libraries, and
152define a type namespace called "Charts" with version 1.0 for any types exposed
153to QML.
154
155Using qmake:
156
157\quotefile tutorials/extending-qml/chapter1-basics/chapter1-basics.pro
158
159Using CMake:
160
161\quotefile tutorials/extending-qml/chapter1-basics/CMakeLists.txt
162
163Now we can build and run the application:
164
165\image extending-tutorial-chapter1.png {Application creates the pie chart with
166 properties defined by the pie chart type}
167
168\note You may see a warning \e {Expression ... depends on non-bindable properties:
169 PieChart::name}. This happens because we add a binding to the writable \c name
170 property, but haven't yet defined a notify signal for it. The QML engine therefore
171 cannot update the binding if the \c name value changes. This is addressed in
172 the following chapters.
173
174\section1 Chapter 2: Connecting to C++ Methods and Signals
175\c extending-qml/chapter2-methods
176
177Suppose we want \c PieChart to have a "clearChart()" method that erases the
178chart and then emits a "chartCleared" signal. Our \c App.qml would be able
179to call \c clearChart() and receive \c chartCleared() signals like this:
180
181\snippet tutorials/extending-qml/chapter2-methods/App.qml 0
182
183\image extending-tutorial-chapter2.png {User can click anywhere on the
184 application window to clear the chart. This invokes the Clear Chart
185 method}
186
187To do this, we add a \c clearChart() method and a \c chartCleared() signal
188to our C++ class:
189
190\snippet tutorials/extending-qml/chapter2-methods/piechart.h 0
191\dots
192\snippet tutorials/extending-qml/chapter2-methods/piechart.h 1
193\dots
194\snippet tutorials/extending-qml/chapter2-methods/piechart.h 2
195\dots
196\snippet tutorials/extending-qml/chapter2-methods/piechart.h 3
197
198The use of Q_INVOKABLE makes the \c clearChart() method available to the
199Qt Meta-Object system, and in turn, to QML.
200
201\note You can also declare the method as a Qt slot instead of using Q_INVOKABLE,
202because public and protected slots are also callable from QML (you cannot call
203private slots).
204
205The \c clearChart() method changes the color to Qt::transparent,
206repaints the chart, then emits the \c chartCleared() signal:
207
208\snippet tutorials/extending-qml/chapter2-methods/piechart.cpp 0
209
210Now when we run the application and click the window, the pie chart
211disappears, and the application outputs:
212
213\badcode
214 qml: The chart has been cleared
215\endcode
216
217
218
219\section1 Chapter 3: Adding Property Bindings
220\c extending-qml/chapter3-bindings
221
222Property binding is a powerful feature of QML that allows values of different
223types to be synchronized automatically. It uses signals to notify and update
224other types' values when property values are changed.
225
226Let's enable property bindings for the \c color property. That means
227if we have code like this:
228
229\snippet tutorials/extending-qml/chapter3-bindings/App.qml 0
230
231\image extending-tutorial-chapter3.png {Pie chart B's color property is bound
232 to pie chart A's color property.}
233
234The "color: chartA.color" statement binds the \c color value of
235\c chartB to the \c color of \c chartA.
236Whenever \c chartA's \c color value changes, \c chartB's \c color value
237updates to the same value. When the window is clicked, the \c onClicked
238handler in the MouseArea changes the color of \c chartA, thereby changing
239both charts to the color blue.
240
241It's easy to enable property binding for the \c color property.
242We add a \l{Qt's Property System}{NOTIFY} feature to its Q_PROPERTY() declaration to indicate that a "colorChanged" signal
243is emitted whenever the value changes.
244
245\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 0
246\dots
247\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 1
248\dots
249\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 2
250\dots
251\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 3
252
253Then, we emit this signal in \c setColor():
254
255\snippet tutorials/extending-qml/chapter3-bindings/piechart.cpp 0
256
257It's important for \c setColor() to check that the color value has actually changed
258before emitting \c colorChanged(). This ensures the signal is not emitted unnecessarily and
259also prevents loops when other types respond to the value change.
260
261The use of bindings is essential to QML. You should always add NOTIFY
262signals for properties if they are able to be implemented, so that your
263properties can be used in bindings. Properties that cannot be bound cannot be
264automatically updated and cannot be used as flexibly in QML. Also, since
265bindings are invoked so often and relied upon in QML usage, users of your
266custom QML types may see unexpected behavior if bindings are not implemented.
267
268
269
270\section1 Chapter 4: Using Custom Property Types
271
272\c extending-qml/chapter4-customPropertyTypes
273
274The \c PieChart type currently has a string-type property and a color-type property.
275It could have many other types of properties. For example, it could have an
276int-type property to store an identifier for each chart:
277
278\code
279 // C++
280 class PieChart : public QQuickPaintedItem
281 {
282 Q_PROPERTY(int chartId READ chartId WRITE setChartId NOTIFY chartIdChanged)
283 ...
284
285 public:
286 void setChartId(int chartId);
287 int chartId() const;
288 ...
289
290 signals:
291 void chartIdChanged();
292 };
293
294 // QML
295 PieChart {
296 ...
297 chartId: 100
298 }
299\endcode
300
301Aside from \c int, we could use various other property types. Many of the Qt
302data types such as QColor, QSize and QRect are automatically supported from QML.
303(See \l {Data Type Conversion Between QML and C++} documentation for a full list.)
304
305If we want to create a property whose type is not supported by QML by default,
306we need to register the type with the QML engine.
307
308For example, let's replace the use of the \c property with a type called
309"PieSlice" that has a \c color property. Instead of assigning a color,
310we assign an \c PieSlice value which itself contains a \c color:
311
312\snippet tutorials/extending-qml/chapter4-customPropertyTypes/App.qml 0
313
314Like \c PieChart, this new \c PieSlice type inherits from QQuickPaintedItem and declares
315its properties with Q_PROPERTY():
316
317\snippet tutorials/extending-qml/chapter4-customPropertyTypes/pieslice.h 0
318
319To use it in \c PieChart, we modify the \c color property declaration
320and associated method signatures:
321
322\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 0
323\dots
324\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 1
325\dots
326\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 2
327\dots
328\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 3
329
330There is one thing to be aware of when implementing \c setPieSlice(). The \c PieSlice
331is a visual item, so it must be set as a child of the \c PieChart using
332QQuickItem::setParentItem() so that the \c PieChart knows to paint this child
333item when its contents are drawn:
334
335\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.cpp 0
336
337Like the \c PieChart type, the \c PieSlice type has to be exposted to QML
338using QML_ELEMENT.
339
340\snippet tutorials/extending-qml/chapter4-customPropertyTypes/pieslice.h 0
341\dots
342
343As with \c PieChart, we add the "Charts" type namespace, version 1.0, to our
344build file:
345
346Using qmake:
347
348\quotefile tutorials/extending-qml/chapter4-customPropertyTypes/chapter4-customPropertyTypes.pro
349
350Using CMake:
351
352\dots
353\snippet tutorials/extending-qml/chapter4-customPropertyTypes/CMakeLists.txt 0
354\snippet tutorials/extending-qml/chapter4-customPropertyTypes/CMakeLists.txt 1
355\dots
356
357
358
359\section1 Chapter 5: Using List Property Types
360\c extending-qml/chapter5-listproperties
361
362Right now, a \c PieChart can only have one \c PieSlice. Ideally a chart would
363have multiple slices, with different colors and sizes. To do this, we could
364have a \c slices property that accepts a list of \c PieSlice items:
365
366\snippet tutorials/extending-qml/chapter5-listproperties/App.qml 0
367
368\image extending-tutorial-chapter5.png {The slices property accepts a list of
369 pie slice items. The pieslice item sets the angle and color of each pie slice}
370
371To do this, we replace the \c pieSlice property in \c PieChart with a \c slices property,
372declared as a \l QQmlListProperty type. The \l QQmlListProperty class enables the
373creation of list properties in types exposed to QML. We replace the \c pieSlice()
374function with a \c slices() function that returns a list of slices. We also use
375a QList to store the internal list of slices as \c m_slices:
376
377\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 0
378\dots
379\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 1
380\dots
381\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 2
382
383Although the \c slices property does not have an associated \c WRITE function,
384it is still modifiable because of the way \l QQmlListProperty works.
385In the \c PieChart implementation, we implement \c PieChart::slices() to
386return a \l QQmlListProperty value:
387
388\snippet tutorials/extending-qml/chapter5-listproperties/piechart.cpp 0
389
390This synthesizes the necessary functions to interact with the list from QML.
391The resulting \l QQmlListProperty is a \e view into the list. Alternately, you can
392manually provide the individual access functions for the list. This is necessary
393if your list is not a \l QList or if you want to restrict or otherwise customize
394QML access to your list. In most cases, however, the constructor taking a
395\l QList pointer is the safest and easiest option.
396
397The \c PieSlice class has also been modified to include \c fromAngle and \c angleSpan
398properties and to draw the slice according to these values. This is a straightforward
399modification if you have read the previous pages in this tutorial, so the code is not shown here.
400
401
402
403\section1 Chapter 6: Writing an Extension Plugin
404
405\c extending-qml/chapter6-plugins
406
407Currently the \c PieChart and \c PieSlice types are used by \c App.qml,
408which is displayed using a QQuickView in a C++ application. An alternative
409way to use our QML extension is to create a plugin library to make it available
410to the QML engine as a new QML import module. This allows the \c PieChart and
411\c PieSlice types to be registered into a type namespace which can be imported
412by any QML application, instead of restricting these types to be only used by
413the one application.
414
415The steps for creating a plugin are described in \l {Creating C++ Plugins for QML}.
416To start with, we create a plugin class named \c ChartsPlugin. It subclasses
417QQmlEngineExtensionPlugin and uses the Q_PLUGIN_METADATA() macro to register the
418plugin with the Qt meta object system.
419
420Here is the \c ChartsPlugin definition in \c chartsplugin.h:
421
422\snippet tutorials/extending-qml/chapter6-plugins/Charts/chartsplugin.h 0
423
424Then, we configure the build file to define the project as a plugin library.
425
426Using qmake:
427
428\quotefile tutorials/extending-qml/chapter6-plugins/Charts/Charts.pro
429
430Using CMake:
431
432\quotefile tutorials/extending-qml/chapter6-plugins/Charts/CMakeLists.txt
433
434When building this example on Windows or Linux, the \c Charts directory will be
435located at the same level as the application that uses our new import module.
436This way, the QML engine will find our module as the default search path for QML
437imports includes the directory of the application executable. On \macos, the
438plugin binary is copied to \c Contents/PlugIns in the the application bundle.
439With qmake, this path is set in \c {chapter6-plugins/app.pro}:
440
441\quotefromfile tutorials/extending-qml/chapter6-plugins/app.pro
442\skipto macos
443\printuntil }
444
445To account for this, we also need to add this location as a
446\l {QML Import Path}{QML import path} in \c main.cpp:
447
448\snippet tutorials/extending-qml/chapter6-plugins/main.cpp 0
449\dots
450
451Defining custom import paths is useful also when there are multiple
452applications using the same QML imports.
453
454The \c .pro file also contains additional magic to ensure that the
455\l {Module Definition qmldir Files}{module definition qmldir file} is always copied
456to the same location as the plugin binary.
457
458The \c qmldir file declares the module name and the plugin that is made available
459by the module:
460
461\quotefile tutorials/extending-qml/chapter6-plugins/Charts/qmldir
462
463Now we have a QML module that can be imported to any application, provided that the
464QML engine knows where to find it. The example contains an executable that loads
465\c App.qml, which uses the \c {import Charts 1.0} statement. Alternatively, you can
466load the QML file using the \l {Prototyping with the QML Runtime Tool}{qml tool},
467setting the import path to the current directory so that it finds the \c qmldir file:
468
469\code
470 qml -I . App.qml
471\endcode
472
473The module "Charts" will be loaded by the QML engine, and the types provided by that
474module will be available for use in any QML document which imports it.
475
476
477
478\section1 Chapter 7: Summary
479
480In this tutorial, we've shown the basic steps for creating a QML extension:
481
482\list
483\li Define new QML types by subclassing QObject and registering them with
484 QML_ELEMENT or QML_NAMED_ELEMENT()
485\li Add callable methods using \l Q_INVOKABLE or Qt slots, and connect to Qt signals
486 with an \c onSignal syntax
487\li Add property bindings by defining \l{Qt's Property System}{NOTIFY} signals
488\li Define custom property types if the built-in types are not sufficient
489\li Define list property types using QQmlListProperty
490\li Create a plugin library by defining a Qt plugin and writing a
491 \l {Module Definition qmldir Files}{qmldir} file
492\endlist
493
494The \l{Overview - QML and C++ Integration}{QML and C++ Integration overview}
495documentation shows other useful features that can be added to QML extensions.
496For example, we could use \l{Default Properties}{default properties} to allow
497slices to be added without using the \c slices property:
498
499\badcode
500 PieChart {
501 PieSlice { ... }
502 PieSlice { ... }
503 PieSlice { ... }
504 }
505\endcode
506
507Or randomly add and remove slices from time to time using \l{Property Value Sources}{property value sources}:
508
509\badcode
510 PieChart {
511 PieSliceRandomizer on slices {}
512 }
513\endcode
514
515\note To continue learning about QML extensions and features follow the
516\l {Writing advanced QML Extensions with C++} tutorial.
517*/