aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/doc/src/cppintegration/extending-tutorial.qdoc
blob: 458768bf18ce8a67f4558d7f0f955c425114fed5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
\example tutorials/extending-qml
\title Writing QML Extensions with C++
\brief Tutorial about extending QML with Qt C++.

The \l {Qt QML} module provides a set of APIs for extending QML through
C++ extensions. You can write extensions to add your own QML types, extend existing
Qt types, or call C/C++ functions that are not accessible from ordinary QML code.

This tutorial shows how to write a QML extension using C++ that includes
core QML features, including properties, signals and bindings. It also shows how
extensions can be deployed through plugins.

Many of the topics covered in this tutorial are documented in further detail in
\l {qtqml-cppintegration-topic.html}{Integrating QML and C++} and its documentation
sub-topics. In particular, you may be interested in the sub-topics
\l{qtqml-cppintegration-exposecppattributes.html}{Exposing Attributes of C++ Classes to QML}
and \l {qtqml-cppintegration-definetypes.html}{Defining QML Types from C++}.

\section1 Running the Tutorial Examples

The code in this tutorial is available as an example project with subprojects
associated with each tutorial chapter. In \l{Qt Creator Manual}{Qt Creator}, open
the \uicontrol Welcome mode and select the tutorial from \uicontrol Examples. In
\uicontrol Edit mode, expand the \e extending-qml project, right-click on the
subproject (chapter) you want to run and select \uicontrol Run.

\section1 Chapter 1: Creating a New Type
\c extending-qml/chapter1-basics

A common task when extending QML is to provide a new QML type that supports some
 custom functionality beyond what is provided by the built-in \l {Qt Quick QML Types}{Qt Quick types}.
For example, this could be done to implement particular data models, or provide
types with custom painting and drawing capabilities, or access system features
like network programming that are not accessible through built-in QML features.

In this tutorial, we will show how to use the C++ classes in the Qt Quick
module to extend QML. The end result will be a simple Pie Chart display implemented by
several custom QML types connected together through QML features like bindings and
signals, and made available to the QML runtime through a plugin.

To begin with, let's create a new QML type called "PieChart" that has two properties: a name
and a color.  We will make it available in an importable type namespace called "Charts", with
a version of 1.0.

We want this \c PieChart type to be usable from QML like this:

\badcode
    import Charts 1.0

    PieChart {
        width: 100; height: 100
        name: "A simple pie chart"
        color: "red"
    }
\endcode

To do this, we need a C++ class that encapsulates this \c PieChart type and its two
properties. Since QML makes extensive use of Qt's \l{Meta-Object System}{meta object system},
this new class must:

\list
\li Inherit from QObject
\li Declare its properties using the Q_PROPERTY macro
\endlist

Here is our \c PieChart class, defined in \c piechart.h:

\snippet tutorials/extending-qml/chapter1-basics/piechart.h 0

The class inherits from QQuickPaintedItem because we want to override
QQuickPaintedItem::paint() in perform drawing operations with the QPainter API.
If the class just represented some data type and was not an item that actually needed
to be displayed, it could simply inherit from QObject. Or, if we want to extend the
functionality of an existing QObject-based class, it could inherit from that class instead.
Alternatively, if we want to create a visual item that doesn't need to perform drawing
operations with the QPainter API, we can just subclass QQuickItem.

The \c PieChart class defines the two properties, \c name and \c color, with the
Q_PROPERTY macro, and overrides QQuickPaintedItem::paint(). The \c PieChart
class is registered using the QML_ELEMENT macro, to allow it to be used from
QML. If you don't register the class, \c app.qml won't be able to create a
\c PieChart.

For the registration to take effect, the \c qmltypes option is added to
\c CONFIG in the project file and a \c QML_IMPORT_NAME and
\c QML_IMPORT_MAJOR_VERSION are given:

\snippet tutorials/extending-qml/chapter1-basics/chapter1-basics.pro 0

The class implementation in \c piechart.cpp simply sets and returns the
\c m_name and \c m_color values as appropriate, and implements \c paint() to
draw a simple pie chart. It also turns off the QGraphicsItem::ItemHasNoContents
flag to enable painting:

\snippet tutorials/extending-qml/chapter1-basics/piechart.cpp 0
\dots 0
\snippet tutorials/extending-qml/chapter1-basics/piechart.cpp 1

Now that we have defined the \c PieChart type, we will use it from QML. The \c app.qml
file creates a \c PieChart item and display the pie chart's details
using a standard QML \l Text item:

\snippet tutorials/extending-qml/chapter1-basics/app.qml 0

Notice that although the color is specified as a string in QML, it is automatically
converted to a QColor object for the PieChart \c color property. Automatic conversions are
provided for various other \l {QML Basic Types}{basic types}; for example, a string
like "640x480" can be automatically converted to a QSize value.

We'll also create a C++ application that uses a QQuickView to run and
display \c app.qml.

Here is the application \c main.cpp:

\snippet tutorials/extending-qml/chapter1-basics/main.cpp 0

We write a \c .pro project file that includes the files and the \c qml library, and
defines a type namespace called "Charts" with a version of 1.0 for any types exposed
to QML:

\quotefile tutorials/extending-qml/chapter1-basics/chapter1-basics.pro

Now we can build and run the application:

\image extending-tutorial-chapter1.png

\note You may see a warning \e {Expression ... depends on non-NOTIFYable properties:
      PieChart::name}. This happens because we add a binding to the writable \c name
      property, but haven't yet defined a notify signal for it. The QML engine therefore
      cannot update the binding if the \c name value changes. This is addressed in
      the following chapters.

The source code from the following files are referred to in this chapter:
\noautolist
\generatelist examplefiles .*chapter1.*

\section1 Chapter 2: Connecting to C++ Methods and Signals
\c extending-qml/chapter2-methods

Suppose we want \c PieChart to have a "clearChart()" method that erases the
chart and then emits a "chartCleared" signal. Our \c app.qml would be able
to call \c clearChart() and receive \c chartCleared() signals like this:

\snippet tutorials/extending-qml/chapter2-methods/app.qml 0

\image extending-tutorial-chapter2.png

To do this, we add a \c clearChart() method and a \c chartCleared() signal
to our C++ class:

\snippet tutorials/extending-qml/chapter2-methods/piechart.h 0
\dots
\snippet tutorials/extending-qml/chapter2-methods/piechart.h 1
\dots
\snippet tutorials/extending-qml/chapter2-methods/piechart.h 2
\dots
\snippet tutorials/extending-qml/chapter2-methods/piechart.h 3

The use of Q_INVOKABLE makes the \c clearChart() method available to the
Qt Meta-Object system, and in turn, to QML. Note that it could have
been declared as a Qt slot instead of using Q_INVOKABLE, as
slots are also callable from QML. Both of these approaches are valid.

The \c clearChart() method simply changes the color to Qt::transparent,
repaints the chart, then emits the \c chartCleared() signal:

\snippet tutorials/extending-qml/chapter2-methods/piechart.cpp 0

Now when we run the application and click the window, the pie chart
disappears, and the application outputs:

\badcode
    qml: The chart has been cleared
\endcode

The source code from the following files are referred to in this chapter:
\generatelist examplefiles .*chapter2.*

\section1 Chapter 3: Adding Property Bindings
\c extending-qml/chapter3-bindings

Property binding is a powerful feature of QML that allows values of different
types to be synchronized automatically. It uses signals to notify and update
other types' values when property values are changed.

Let's enable property bindings for the \c color property. That means
if we have code like this:

\snippet tutorials/extending-qml/chapter3-bindings/app.qml 0

\image extending-tutorial-chapter3.png

The "color: chartA.color" statement binds the \c color value of
\c chartB to the \c color of \c chartA.
Whenever \c chartA's \c color value changes, \c chartB's \c color value
updates to the same value. When the window is clicked, the \c onClicked
handler in the MouseArea changes the color of \c chartA, thereby changing
both charts to the color blue.

It's easy to enable property binding for the \c color property.
We add a \l{Qt's Property System}{NOTIFY} feature to its Q_PROPERTY() declaration to indicate that a "colorChanged" signal
is emitted whenever the value changes.

\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 0
\dots
\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 1
\dots
\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 2
\dots
\snippet tutorials/extending-qml/chapter3-bindings/piechart.h 3

Then, we emit this signal in \c setPieSlice():

\snippet tutorials/extending-qml/chapter3-bindings/piechart.cpp 0

It's important for \c setColor() to check that the color value has actually changed
before emitting \c colorChanged(). This ensures the signal is not emitted unnecessarily and
also prevents loops when other types respond to the value change.

The use of bindings is essential to QML. You should always add NOTIFY
signals for properties if they are able to be implemented, so that your
properties can be used in bindings. Properties that cannot be bound cannot be
automatically updated and cannot be used as flexibly in QML. Also, since
bindings are invoked so often and relied upon in QML usage, users of your
custom QML types may see unexpected behavior if bindings are not implemented.

The source code from the following files are referred to in this chapter:
\generatelist examplefiles .*chapter3.*

\section1 Chapter 4: Using Custom Property Types

\c extending-qml/chapter4-customPropertyTypes

The \c PieChart type currently has a string-type property and a color-type property.
It could have many other types of properties. For example, it could have an
int-type property to store an identifier for each chart:

\code
    // C++
    class PieChart : public QQuickPaintedItem
    {
        Q_PROPERTY(int chartId READ chartId WRITE setChartId NOTIFY chartIdChanged)
        ...

    public:
        void setChartId(int chartId);
        int chartId() const;
        ...

    signals:
        void chartIdChanged();
    };

    // QML
    PieChart {
        ...
        chartId: 100
    }
\endcode

Aside from \c int, we could use various other property types. Many of the Qt
data types such as QColor, QSize and QRect are automatically supported from QML.
(See \l {Data Type Conversion Between QML and C++} documentation for a full list.)

If we want to create a property whose type is not supported by QML by default,
we need to register the type with the QML engine.

For example, let's replace the use of the \c property with a type called
"PieSlice" that has a \c color property. Instead of assigning a color,
we assign an \c PieSlice value which itself contains a \c color:

\snippet tutorials/extending-qml/chapter4-customPropertyTypes/app.qml 0

Like \c PieChart, this new \c PieSlice type inherits from QQuickPaintedItem and declares
its properties with Q_PROPERTY():

\snippet tutorials/extending-qml/chapter4-customPropertyTypes/pieslice.h 0

To use it in \c PieChart, we modify the \c color property declaration
and associated method signatures:

\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 0
\dots
\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 1
\dots
\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 2
\dots
\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.h 3

There is one thing to be aware of when implementing \c setPieSlice(). The \c PieSlice
is a visual item, so it must be set as a child of the \c PieChart using
QQuickItem::setParentItem() so that the \c PieChart knows to paint this child
item when its contents are drawn:

\snippet tutorials/extending-qml/chapter4-customPropertyTypes/piechart.cpp 0

Like the \c PieChart type, the \c PieSlice type has to be exposted to QML
using QML_ELEMENT. As with \c PieChart, we add the "Charts" type namespace,
version 1.0 to the .pro file:

\snippet tutorials/extending-qml/chapter4-customPropertyTypes/pieslice.h 0
\dots
\quotefile tutorials/extending-qml/chapter1-basics/chapter4-customPropertyTypes.pro

The source code from the following files are referred to in this chapter:
\generatelist examplefiles .*chapter4.*

\section1 Chapter 5: Using List Property Types
\c extending-qml/chapter5-listproperties

Right now, a \c PieChart can only have one \c PieSlice. Ideally a chart would
have multiple slices, with different colors and sizes. To do this, we could
have a \c slices property that accepts a list of \c PieSlice items:

\snippet tutorials/extending-qml/chapter5-listproperties/app.qml 0

\image extending-tutorial-chapter5.png

To do this, we replace the \c pieSlice property in \c PieChart with a \c slices property,
declared as a QQmlListProperty type. The QQmlListProperty class enables the
creation of list properties in QML extensions. We replace the \c pieSlice()
function with a \c slices() function that returns a list of slices, and add
an internal \c append_slice() function (discussed below). We also use a QList to
store the internal list of slices as \c m_slices:

\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 0
\dots
\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 1
\dots
\snippet tutorials/extending-qml/chapter5-listproperties/piechart.h 2

Although the \c slices property does not have an associated \c WRITE function,
it is still modifiable because of the way QQmlListProperty works.
In the \c PieChart implementation, we implement \c PieChart::slices() to
return a QQmlListProperty value and indicate that the internal
\c PieChart::append_slice() function is to be called whenever a request is made from QML
to add items to the list:

\snippet tutorials/extending-qml/chapter5-listproperties/piechart.cpp 0

The \c append_slice() function simply sets the parent item as before,
and adds the new item to the \c m_slices list. As you can see, the append function for a
QQmlListProperty is called with two arguments: the list property, and
the item that is to be appended.

The \c PieSlice class has also been modified to include \c fromAngle and \c angleSpan
properties and to draw the slice according to these values. This is a straightforward
modification if you have read the previous pages in this tutorial, so the code is not shown here.

The source code from the following files are referred to in this chapter:
\generatelist examplefiles .*chapter5.*

\section1 Chapter 6: Writing an Extension Plugin

\c extending-qml/chapter6-plugins

Currently the \c PieChart and \c PieSlice types are used by \c app.qml,
which is displayed using a QQuickView in a C++ application. An alternative
way to use our QML extension is to create a plugin library to make it available
to the QML engine as a new QML import module. This allows the \c PieChart and
\c PieSlice types to be registered into a type namespace which can be imported
by any QML application, instead of restricting these types to be only used by
the one application.

The steps for creating a plugin are described in \l {Creating C++ Plugins for QML}.
To start with, we create a plugin class named \c ChartsPlugin. It subclasses QQmlExtensionPlugin
and registers our QML types in the inherited \l{QQmlExtensionPlugin::}{registerTypes()} method.

Here is the \c ChartsPlugin definition in \c chartsplugin.h:

\snippet tutorials/extending-qml/chapter6-plugins/import/chartsplugin.h 0

And its implementation in \c chartsplugin.cpp:

\snippet tutorials/extending-qml/chapter6-plugins/import/chartsplugin.cpp 0

Then, we write a \c .pro project file that defines the project as a plugin library
and specifies with DESTDIR that library files should be built into a \c {../Charts}
directory.

\quotefile tutorials/extending-qml/chapter6-plugins/import/import.pro

When building this example on Windows or Linux, the \c Charts directory will be
located at the same level as the application that uses our new import module.
This way, the QML engine will find our module as the default search path for QML
imports includes the directory of the application executable. On \macos, the
plugin binary is copied to \c Contents/PlugIns in the the application bundle;
this path is set in \c {chapter6-plugins/app.pro}:

\quotefromfile tutorials/extending-qml/chapter6-plugins/app.pro
\skipto osx
\printuntil }

To account for this, we also need to add this location as a
\l {QML Import Path}{QML import path} in \c main.cpp:

\snippet tutorials/extending-qml/chapter6-plugins/main.cpp 0
\dots

Defining custom import paths is useful also when there are multiple
applications using the same QML imports.

The \c .pro file also contains additional magic to ensure that the
\l {Module Definition qmldir Files}{module definition qmldir file} is always copied
to the same location as the plugin binary.

The \c qmldir file declares the module name and the plugin that is made available
by the module:

\quotefile tutorials/extending-qml/chapter6-plugins/import/qmldir

Now we have a QML module that can be imported to any application, provided that the
QML engine knows where to find it. The example contains an executable that loads
\c app.qml, which uses the \c {import Charts 1.0} statement. Alternatively, you can
load the QML file using the \l{Prototyping with qmlscene}{qmlscene tool}, setting the
import path to the current directory so that it finds the \c qmldir file:

\code
    qmlscene -I . app.qml
\endcode

The module "Charts" will be loaded by the QML engine, and the types provided by that
module will be available for use in any QML document which imports it.

The source code from the following files are referred to in this chapter:
\generatelist examplefiles .*chapter6.*

\section1 Chapter 7: Summary

In this tutorial, we've shown the basic steps for creating a QML extension:

\list
\li Define new QML types by subclassing QObject and registering them with
    QML_ELEMENT or QML_NAMED_ELEMENT()
\li Add callable methods using \l Q_INVOKABLE or Qt slots, and connect to Qt signals
    with an \c onSignal syntax
\li Add property bindings by defining \l{Qt's Property System}{NOTIFY} signals
\li Define custom property types if the built-in types are not sufficient
\li Define list property types using QQmlListProperty
\li Create a plugin library by defining a Qt plugin and writing a
    \l {Module Definition qmldir Files}{qmldir} file
\endlist

The \l{Integrating QML and C++} documentation shows
other useful features that can be added to QML extensions. For example, we
could use \l{Default Properties}{default properties} to allow
slices to be added without using the \c slices property:

\badcode
    PieChart {
        PieSlice { ... }
        PieSlice { ... }
        PieSlice { ... }
    }
\endcode

Or randomly add and remove slices from time to time using \l{Property Value Sources}{property value sources}:

\badcode
    PieChart {
        PieSliceRandomizer on slices {}
    }
\endcode

\sa {Integrating QML and C++}
*/