Creating C++ plugins for QML with CMake
How to create C++ plugins for QML with CMake
Why this post?
So far, Qt Creator allows you to create a new C++ plugin project for QML, but with qmake. This post will show you how to do it with CMake.
I recommend reading the following links for more details:
Starting
In:
- Welcome - Projects - New or File Menu - New File or Project
- Choose Library, C++ Library
- Choose where the project will be created and project name: UserRegister
- In Define Build System
    - Build System: CMake
 
- In Define Project Details
    - Type: Shared Library
- Class name: UserRegisterPlugin
- Header file: userregister_plugin.h
- Source file: userregister_plugin.cpp
- Qt module: Core
 
- Kit Selection
    - Choose Qt version
 
Qt Creator creates a very basic project. We’ll do some customizations.
Remove the UserRegister_global.h file
In CMakeLists.txt, it looked like this:
cmake_minimum_required(VERSION 3.14)
project(Register VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Qml REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick QuickControls2 REQUIRED)
set(PROJECT_SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/userregister_plugin.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/src/userregister_plugin.h
    ${CMAKE_CURRENT_SOURCE_DIR}/src/register.h
    ${CMAKE_CURRENT_SOURCE_DIR}/src/register.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/resources/qml/qmldir
)
add_library(${PROJECT_NAME}
    SHARED
        ${PROJECT_SOURCES}
)
target_compile_definitions(${PROJECT_NAME}
    PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>
)
target_link_libraries(${PROJECT_NAME}
    PRIVATE
        Qt${QT_VERSION_MAJOR}::Core
        Qt${QT_VERSION_MAJOR}::Quick
        Qt${QT_VERSION_MAJOR}::QuickControls2
        Qt${QT_VERSION_MAJOR}::Qml
)
set(PLUGIN_PATH ${QT_DIR}/../../../qml/User/${PROJECT_NAME})
install(TARGETS ${PROJECT_NAME} DESTINATION ${PLUGIN_PATH})
install(FILES resources/qml/qmldir DESTINATION ${PLUGIN_PATH})
Note the structure of the files and the
qmldirfile created.
Contents of qmldir file:
module User.Register
plugin Register
In the class created by Qt Creator, in my case MyPlugin, leave it as:
File: userregister_plugin.h
#pragma once
#include <QQmlExtensionPlugin>
class UserRegisterPlugin : public QQmlExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
    void registerTypes(const char* uri) override;
};
File: userregister_plugin.cpp
#include "userregister_plugin.h"
void UserRegisterPlugin::registerTypes(const char* /*uri*/)
{
}
This is the basic structure of the files. But the plugin won’t be recognized yet because it doesn’t have any modules registered.
Create a class called Register
File: register.h
#pragma once
#include <QObject>
#include <QString>
class Register : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString firstName READ getFirstName WRITE setFirstName NOTIFY firstNameChanged)
    Q_PROPERTY(QString lastName READ getLastName WRITE setLastName NOTIFY lastNameChanged)
    Q_PROPERTY(QString nickname READ getNickname WRITE setNickname NOTIFY nicknameChanged)
public:
    explicit Register(QObject* parent = nullptr);
    const QString& getFirstName() const;
    void setFirstName(const QString& newFirstName);
    const QString& getLastName() const;
    void setLastName(const QString& newLastName);
    const QString& getNickname() const;
    void setNickname(const QString& newNickname);
signals:
    void firstNameChanged();
    void lastNameChanged();
    void nicknameChanged();
private:
    QString m_firstName;
    QString m_lastName;
    QString m_nickname;
};
File: register.cpp
#include "register.h"
Register::Register(QObject* parent) : QObject(parent) { }
const QString& Register::getFirstName() const { return m_firstName; }
void Register::setFirstName(const QString& newFirstName)
{
    if (m_firstName == newFirstName)
        return;
    m_firstName = newFirstName;
    emit firstNameChanged();
}
const QString& Register::getLastName() const { return m_lastName; }
void Register::setLastName(const QString& newLastName)
{
    if (m_lastName == newLastName)
        return;
    m_lastName = newLastName;
    emit lastNameChanged();
}
const QString& Register::getNickname() const { return m_nickname; }
void Register::setNickname(const QString& newNickname)
{
    if (m_nickname == newNickname)
        return;
    m_nickname = newNickname;
    emit nicknameChanged();
}
To register the class as a component for QML
File: userregister_plugin.cpp
#include "userregister_plugin.h"
#include "register.h"
#include <QtQml>
void UserRegisterPlugin::registerTypes(const char* uri)
{
    qmlRegisterType<Register>(uri, 1, 0, "Register");
}
In Projects in the kit you are using -> build -> Build Steps, set the target install. Build the project.
To test it, create a qml file with the content below and let’s run it with qmlscene
File: /tmp/UserRegisterApp.qml
import QtQuick
import QtQuick.Controls
import User.Register
ApplicationWindow {
    id: window
    height: 640
    width: 480
    visible: true
    title: qsTr("Test User Register")
    Register {
        id: register
        onFirstNameChanged: console.log("First name:", firstName)
        onLastNameChanged: console.log("Last name:", lastName)
        onNicknameChanged: console.log("Nickname:", nickname)
    }
    Column {
        anchors.centerIn: parent
        spacing: 10
        TextField {
            id: fieldFirstName
            placeholderText: qsTr("First Name")
        }
        TextField {
            id: fieldLastName
            placeholderText: qsTr("Last Name")
        }
        TextField {
            id: fieldNickname
            placeholderText: qsTr("Nickname")
        }
        Button {
            id: buttonRegister
            text: qsTr("Register")
            onClicked: {
                register.firstName = fieldFirstName.text
                register.lastName = fieldLastName.text
                register.nickname = fieldNickname.text
                fieldFirstName.text = ""
                fieldLastName.text = ""
                fieldNickname.text = ""
            }
        }
    }
}
Running
/path/to/Qt/bin/qmlscene /tmp/UserRegisterApp.qml
Ready! Now you can migrate your QML plugins to CMake! 