QObject
So, what is this funky QObject thingy we are inheriting from which keeps popping up? Well, it’s the base class for all Qt objects, and it gives us some powerful features for free.
QObjects organize themselves into object hierarchies with a parent object assuming ownership of their child objects, which means we don’t have to worry (as much!) about memory management. For example, if we have an instance of a Client class derived from QObject that is the parent of an Address also derived from QObject, then the address is automatically destroyed when the client is destroyed.
QObjects carry metadata that allows a degree of type inspection and is the backbone for interaction with QML. They can also communicate with each other via an event subscription mechanism where the events are emitted as signals and the subscribed delegates are known as slots.
All you need to remember for now is that for any custom classes you write where you want to interact with it in the UI, ensure that it derives from QObject. Whenever you do derive from QObject, ensure that you always add the magical Q_OBJECT macro to your class before you do anything else. It injects a bunch of super complicated boilerplate code that you don’t need to understand in order to use QObjects effectively.
We are now at the point where we need to reference code from one subproject (MasterController in cm-lib) in another (cm-ui). We first need to be able to access the declarations for our #include statements. Edit the INCLUDEPATH variable in cm-ui.pro as follows:
INCLUDEPATH += source ../cm-lib/source
The symbol is a “continue on to the next line” indicator, so you can set a variable to multiple values spanning several lines. Just like console commands, ‘..’ means traverse up a level, so here we are stepping up out of the local folder (cm-ui) and then down into the cm-lib folder to get at its source code. You need to be careful that the project folders remain in the same location relative to each other, else this won’t work.
Just below this, we’ll tell our UI project where to find the implementation (compiled binary) of our library project. If you take a look at the filesystem alongside the top-level cm project folder, you will see one or more build folders, for example, build-cm-Desktop_Qt_5_9_0_MinGW_32bit-Debug. Each folder is created when we run qmake for a given kit and configuration and is populated with the output when we build.
Next, navigate to the folder relevant to the kit and configuration you are using, and you will find a cm-lib folder with another configuration folder inside it. Copy this file path; for example, I am using the MinGW 32 bit kit in Debug configuration, so my path is <Qt Projects>/build-cm-Desktop_Qt_5_10_0_MinGW_32bit-Debug/cm-lib/debug.
In that folder, you will find the compiled binaries relevant for your OS, for example, cm-lib.dll on Windows. This is the folder we want our cm-ui project to reference for the cm-lib library implementation. To set this up, add the following statement to cm-ui.pro:
LIBS += -L$$PWD/../../build-cm-Desktop_Qt_5_10_0_MinGW_32bit-Debug/cm-lib/debug -lcm-lib
LIBS is the variable used to add referenced libraries to the project. The -L prefix denotes a directory, while -l denotes a library file. Using this syntax allows us to ignore the file extensions (.a, .o, .lib) and prefixes (lib...), which can vary between operating systems and let qmake figure it out. We use the special $$ symbol to access the value of the PWD variable, which contains the working directory of the current project (the full path to cm/cm-ui in this case). From that location, we then drill up two directories with ../.. to get us to the Qt projects folder. From there, we drill back down to the location where we know the cm-lib binaries are built.
Now, this is painful to write, ugly as hell, and will fall over as soon as we switch kits or configurations, but we will come back and tidy up all this later. With the project references all wired up, we can head on over to main.cpp in cm-ui.
To be able to use a given class in QML, we need to register it, which we do in main() before we create the QML Application Engine. First, include the MasterController:
#include <controllers/master-controller.h>
Then, right after the QGuiApplication is instantiated but before the QQmlApplicationEngine is declared, add the following line:
qmlRegisterType<cm::controllers::MasterController>("CM", 1, 0, "MasterController");
What we are doing here is registering the type with the QML engine. Note that the template parameter must be fully qualified with all namespaces. We will add the type’s metadata into a module called CM with a version number 1.0, and we want to refer to this type as MasterController in QML markup.
Then, we instantiate an instance of MasterController and inject it into the root QML context:
cm::controllers::MasterController masterController; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("masterController", &masterController); engine.load(QUrl(QStringLiteral("qrc:/views/MasterView")));
Note that you need to set the context property before loading the QML file, and you will also need to add the following header:
#include <QQmlContext>
So, we’ve created a controller, registered it with the QML engine, and it’s good to go. What now? Let’s do our first bit of QML.