Hi. I'm Jesper Pedersen from KDAB. You're watching
Programming with Qt and QML. We've now made it to the integration section. We're going to talk about
how we get our values from the C++ level up to the QML level. We'll talk about
how we export QObject subclasses -- those QObject subclasses which have
properties -- from C++ up to QML. We'll talk about how we create new elements
that do not have a visual appearance that could be a timer, for example. We have a timer on QML,
but our example will nevertheless be a timer, just because we were first. We had that in
training material before it was in QML. It can't be bothered changing that. And, at the end of
this section, we'll talk about how we create new visible QML elements. So, how do you create your own element that is not just a composition
of other elements on the QML level, but where the painting is done in the C++
level. So, we got four very interesting videos coming up here. You already started
on the first one. So, please do enjoy the ride. We'll start out talking about how we export a
value -- just a simple value -- from C++ to QML. To do so, we obviously need a C++
application this time. So, in the previous module, we've seen all
the engine room stuff that needs to be going on to be able to do this. We've
seen the modules before that. We've seen a lot of stuff on the pure QML side where we had no C++
stuff going on. But now we'll merge the two, and that, of course, requires that we
have C++ in our application. Actually, this is likely the very last time where
you'll just create a pure QML application. You will, from this day on, very likely start having a C++
backend in your QML application. The reason for that is that your application is not going
to be a toy application. At least, that's my estimate. You've been hanging on so long. So, you're
likely not going to be just doing a very simple "Hello World" kind of application. You connect your
application to some backend code that is happening in C++. So, to do this, the first thing we need to do is
export the values that we want in QML, from C++. To export them, we need
to get to the rootContext, and the rootContext is like the global namespace in QML.
That's the last resort for your QML interpreter, when it's trying to look up a value, look in the
element, and look in the root element. And then, at the end, it will look at the rootContext. So,
that's your global variables that you have in QML. We get to that rootContext simply by going to the
engine and asking the engine for the rootContext. Today, you could even have setView.rootContext
because there is a method on the view that gives you the rootContext directly. But
I just wanted to show you that we have the engine in there and it's, of course, the engine
itself that knows about the rootContext. Now, when I have the root context, all I need to do to
export a value up to the C++ level is to call setContextProperty, give it a name, and give it
a QVariant value. So, the names, as you can see here, they all start with an underscore. That's
purely a naming convention, so that, when I'm in the QML file in which I see something starting
with underscoring, I know right away -- yes, that thing came from the C++ side. So, I don't start looking
around in my files for where aString is defined. The value over here, as I said, is a QVariant.
You've seen in a previous video what QVariant is about, and you also saw in those
videos that the QVariant class actually has a number of constructors taking different types
that are built into Qt Core. So, a QString, a QSize, a QColor -- I do not need to wrap
them in QArea and :: from value. I do not even need to wrap them in QArea and
QString ("KDAB"). That's all happening in C++. That's
just our regular type conversion in C++. That's all that you need to do on the
C++ side to export your new values into your QML. On our QML side, I don't
need to do anything special. I just use them as if they were regular properties that
were in scope, one way or the other. So, you can see I have a rectangle here. My width
and the height of the rectangle simply come from this size aSize that I exported
from C++. And aSize is actually a compound element that has sub-properties in it , namely,
width and height -- so, therefore width: _aSize.width
and similar for height. My background color is, on the other hand,
just a regular JavaScript color, when we we have it up in JavaScript -- so, color:
_background. Now, the key thing in this example really comes down on this line here,
where you can see that I print out aSize. So, you look up here aSize is actually a QSize
800, 600. We could dot our way into it, like we did on these two lines, but I can also
just print out aSize because, behind the scenes, C++ and Qt will know how to convert these
combined elements into something that we can use as a string. And let's just run the
example here, just to see that it actually does -- exactly what we had. So, here is my main, again.
You can see there isn't really much hidden in the slide I'm creating my Q application.
I'm setting my source and I'm showing my view, so that we can actually
see something, and my main.qml. There isn't anything hidden here, either, except
that I anchor my column inside my rectangle. Here, you can see the application
running. It is 800 by 600. You'd have to count the pixels on the screen for that to validate it. The
background color is gray, as I said in C++. And down here, the size: QSize 800, 600. So
that's the string version of that size. As I said, the simple values that we have in C++ map to similar simple values on the QML side. So,
a number or string will just be a number or string on the QML side. Our
compound values from C++, you can dot
your way into them, as long as Qt knows about them. If Qt doesn't know about them, then you can
still send them up there. But, all you can do on the QML side is give them back into C++, which
might still be useful in different situations. But, if you want to be able to use your compound
values on the C++ side, all you need to do is make them into gadgets. This is a place in the
training where I always ask myself if I am really the only one who thinks about inspector gadget when I see Q_GADGETS? So, "Go, Go Gadget arm." There we are. Gadgets are not QObjects. So,
let me move the cursor around for you. Observe here: class Person. And then, look at those pixels I'm
hovering over here. They do not say :public something. It's just a regular value class, a
value class that has these three parameters. As always, it needs a copy constructor, default
constructor, and an assignment operator. But once you have those, you can take that and give it to QML,
and QML will be able to access its properties. If this syntax here is new for you, then maybe you
should rewind and watch the video on properties that we did three videos ago. What it says here
is that this object that, on the QML side, is accessible has a name.
The name is QString. And, whenever you read that, it actually goes and reads the m_name.
You can even assign to it to the the gadget but, as we'll see in the example below, that value doesn't
make it into C++ land. Again, it's a copy that you get of gadgets onto your QML level. I got
another one here in age, again, MEMBER m_age. Do notice that, if you look at that line again --
don't look at my face, look at that line again -- notice it does not say anything about a notification
signal. That is only for objects. We'll see that in the next video. But, for gadgets, they are a pure
copy that you get from your C++ level up to the QML level. The QML level can
work with that. It can even update it. But if you want it back into the C++
level, you need to call a setter back on it. So, they are really just a copy that you get on. And,
of course, it also, without the notification signal, means that they will not be able to participate in
a property binding, simply because it's property binding requires that you have notification
signals for whenever the value changes. So, they really should just be thought of as your QSize
that we saw on the previous slide. QSize has a width and a height, and you
can take the size and give it up to the QML level, and think of it as if it was just a value.
And that is what your gadgets are. To support that the gadget will actually be able
to print out just like our QSize printed out (it printed QSize (800, 600
as a string), we need to create a two-string method that returns a QString.
If that method exists, the Qt QML engine will use that whenever you try to
stringify your QML value. And that's one thing here, on this example, we haven't seen before, namely, the
Q_INVOKABLE. What that means is that this method should be callable or invokable from
the QML level. On the QML side, I got a Person gadget here now, and because this method is
Q_INVOKABLE, I can call the toString on that object. Hadn't I had Q_INVOKABLE,
QML wouldn't even know about this because the meta-object information extracted from
moc and all of that stuff will only extract information about the Q_INVOKABLE
and...slots? I was just about to say slots, but slots is something that goes along with
QObjects, and our gadgets are not QObjects. Let's just see this example, here, in action. So, I got my main, my quick application, here. I
set a ContextProperty called _santa. That is a person that is called
Santa and is 256 years old. So, our personal constructor here,
as you can see, has a name and age. That person, of course, needs to go into a
QVariant. So, I'll say QVariant:: from Value. I couldn't just give it as I did with my
integer and the size, and so on, because there's no QVariant constructor that takes a person. So,
I need this QVariant::fromValue to get it up on the QML side. I also
export a database, here. So, my _db is database, and I'm a bit ahead of
myself because that one is a QObject. But it's simply to export something where I can
actually access the properties and method later on. On the QML side, it looks like this. Let me just run the
application, here. It's a pretty boring application; it has one text element that says "press
me" and, when I click it, it doesn't even do any animation or anything. And it
doesn't seem to be doing anything except that. Well, on standard output, it prints some stuff. So, let's
just resize this a bit. There we are. So, on standard output, it printed out this stuff. And that's
what you can see up here. I got my item here. It has a width and height and, inside, it has a text
element, and a mouse area where the unclicked says, "Well, okay." So, when I click this, I
will do a global lookup, initially. So, I'll simply console.log my _santa. My _santa
says Person(Santa, 256). So, that's printing out that my santa is a 256 year old. Now, let me
try and get a gadget from the C++ level onto the QML level. So, I call this _db,
which is my database. It has a method called "lookup," and I look up the fake santa. And I
have that as a variable here on my QML side. And
var santa equal to looking up fake santa. I can print him out too, and I do so here. My fake Santa
is 65 years old and is called Fake Santa. I can add a person to the database. So, I can create
new elements there, but I cannot create -- at least not yet (I haven't seen that and, for gadgets, I
wouldn't even be able to do that) -- elements by simply going to the database to ask
if it can create it and then give it back right away. So, here, I call db makePerson. So, I'm creating
an instance of Jesper that's 18 years old. If that's me, then I'm afraid that's
an earlier version of me, likely before QML even existed. I create that element, there,
and it returns that element to the QML level. I give it straight back into the
C++ level by simply calling addPerson with that gadget that I created, there. And now,
I can look up in the database. I look up Jesper and, looking him up, I print him out. And we can
see here that, in the database, we got this Jesper version that is 18 years old. On the
QML side, I can dock my way into gadgets, as we discussed: jesper.age = 44.
And we'll print that out and we see we have Jesper that is 44 years old.
Again, this is a slightly older example, I'm afraid. Finally, I will go to the database and I will ask
for Jesper, once again, just to validate that my updating him on the QML side didn't actually
touch the database. So, I'll ask for Jesper, again. I'll print that out, and we can see that the 18
year old version of Jesper still exists in the database. So, again, gadgets are really, truly just
values that you copy from the C++ side up to the QML side. You cannot do property bindings
with the properties of the gadgets. You can just dot your way into those elements. That's it
for this video. In the next video, we will see a QObject pointer being exported. And that's a completely different story on what you can do
with those. So, stay tuned. Until then, have a great day.