Wie integriert man C++ und QML? − Registrierung eines C++-Enums in QML

Qt QML-Entwicklung
2022-07-13
11 Minuten
Wie integriert man C++ und QML? Registrieren von Enums

Wir setzen unsere Serie von Blogbeiträgen über die Integration von QML und C++ fort. Beim letzten Mal haben wir die Registrierung von C++-Typen als Singletons in QML behandelt.

 

Erfahren Sie, wie Sie C++ und QML durch die Registrierung von Enums integrieren

Heute beschäftigen wir uns mit einer weiteren häufig verwendeten Technik in der Qt-Softwareentwicklung ― der Nutzung von C++-Enums im QML-Code. In diesem Artikel verwenden wir weiterhin die neueste Methode zur Integration von C++-Code und QML ― den CMake-Befehl qt_add_qml_module().

In der Zwischenzeit können Sie sich ansehen, was wir Ihnen in unseren C++-Entwicklungsdienstleistungen anbieten können.

 

Enum vs. Enum-Klasse in C++

Wir gehen davon aus, dass die meisten Entwickler mit dem Konzept von Enums in den meisten Programmiersprachen vertraut sind, weshalb wir nicht zu lange darauf eingehen werden.

Falls Sie jedoch nicht sicher sind, was ein Enum ist, sollten Sie sich zunächst mit diesem Thema vertraut machen und dann mit dem Artikel fortfahren. Glücklicherweise ist es ein einfaches Konzept, das in wenigen Minuten verstanden werden kann.

Jedoch wissen nicht alle Entwickler, dass C++ nicht nur die üblichen Enums bietet, sondern auch etwas, das als Enum-Klasse in C++ bezeichnet wird.

Was sind diese und wofür benötigen wir sie?

Die Idee der Enum-Klasse in C++ stammt aus den Einschränkungen herkömmlicher Enums und deren unsicherem Gebrauch.

Beispielsweise können Sie mit einem regulären C++-Enum leicht folgenden Code schreiben:

 

#include <iostream>

enum Color { RED, BLUE, GREEN };
enum Position { TOP, BOTTOM, LEFT, RIGHT };

int main()
{
    auto color = RED;
    auto position = TOP;

    if (color == position) {
        std::cout << "equals" << std::endl;
    }
}
Although, most probably compiler would produce a warning about comparing different types, you will see a desired “equals” in the output, meaning that comparison resulted in true:

 

main.cpp: In function 'int main()':
main.cpp:11:18: warning: comparison between 'enum Color' and 'enum Position' [-Wenum-compare]
     if (color == position) {
                  ^~~~~~~~
equals

Um dieses Problem zu lösen, können Sie eine Enum-Klasse verwenden, da es bei Enum-Klassen keine implizite Konvertierung in Ganzzahlen gibt, wie dies bei regulären Enums der Fall ist. Tatsächlich ist dies ein weiterer Vorteil der Enum-Klasse, da sie eine höhere Typsicherheit bietet.

Wenn Sie jedoch eine Enum-Klasse wirklich in eine Ganzzahl konvertieren möchten, können Sie dies explizit tun:

 

static_cast<int>(Color::RED);

Eine weitere Situation, in der eine Enum-Klasse nützlich ist, ist, wenn Sie dieselben Werte in verschiedenen Enums verwenden möchten. Ein Code mit der folgenden Definition regulärer Enums würde nicht kompiliert werden:

 

enum CarColor { RED, BLUE, GREEN };

enum LightColor { RED, BLUE, GREEN };
Compiler would produce an error:

 

main.cpp:5:19: error: redeclaration of 'RED'
 enum LightColor { RED, BLUE, GREEN };
                   ^~~
main.cpp:3:17: note: previous declaration 'CarColor RED'
 enum CarColor { RED, BLUE, GREEN };
                 ^~~
main.cpp:5:24: error: redeclaration of 'BLUE'
 enum LightColor { RED, BLUE, GREEN };
                        ^~~~
main.cpp:3:22: note: previous declaration 'CarColor BLUE'
 enum CarColor { RED, BLUE, GREEN };
                      ^~~~
main.cpp:5:30: error: redeclaration of 'GREEN'
 enum LightColor { RED, BLUE, GREEN };
                              ^~~~~
main.cpp:3:28: note: previous declaration 'CarColor GREEN'
 enum CarColor { RED, BLUE, GREEN };
 ^~~~~

Normalerweise wurden solche Probleme gelöst, indem unterschiedliche Namen für die Enum-Werte verwendet wurden, z. B. C_RED und L_RED.

Wenn Sie jedoch eine Enum-Klasse verwenden, können Sie dieselben Namen für die Werte verwenden, und alles wird erfolgreich kompiliert:

 

enum class CarColor { RED, BLUE, GREEN };

enum class LightColor { RED, BLUE, GREEN };

Enums in QML bereitstellen

Obwohl QML es ermöglicht, Enums direkt in .qml-Dateien zu deklarieren, werden wir heute einen anderen Ansatz besprechen.
Es ist möglich, ein Enum im C++-Teil der Anwendung zu deklarieren und sowohl in C++ als auch in QML zu verwenden.

Wir werden den Befehl qt_add_qml_module verwenden, sodass der Prozess ähnlich aussieht wie in unseren vorherigen Blogbeiträgen dieser Serie.

Zuerst müssen wir ein Enum in einer .cpp-Datei deklarieren. Lassen Sie uns unsere bekannte ImportantClass verwenden. Wir nehmen ein klassisches Beispiel für Enums ― die Wochentage.

 

#include <QObject>
#include <QtQml/qqml.h>

class ImportantClass : public QObject
{
    Q_OBJECT
    QML_ELEMENT
public:
    explicit ImportantClass(QObject *parent = nullptr);

    enum Day {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY,
    };
    Q_ENUM(Day)

public slots:
    void myFun(ImportantClass::Day day) const {
        qDebug() << day;
        // ... very smart code
    }
};

Wie Sie sehen können, fügen wir das Makro QML_ELEMENT hinzu, um die C++-Klasse für QML verfügbar zu machen.
Zusätzlich müssen wir das Makro Q_ENUM mit dem Namen des zuvor definierten Enums verwenden:

 

enum Day {
       MONDAY,
       TUESDAY,
       WEDNESDAY,
       THURSDAY,
       FRIDAY,
       SATURDAY,
       SUNDAY,
   };
   Q_ENUM(Day)
The following steps are similar to ones we had in our previous articles. We need to use qt_add_qml_module in CMakeLists.txt file.

 

qt_add_qml_module(appEnumExample
    URI EnumExample
    VERSION 1.0
    QML_FILES
        main.qml
    SOURCES
        ImportantClass.h
        ImportantClass.cpp
)

Vergessen Sie nicht, eine korrekte URI anzugeben, damit die Importe ordnungsgemäß funktionieren. Der letzte Schritt besteht darin, diese Klasse mithilfe der angegebenen URI zu importieren.

Wie üblich haben wir ein einfaches Beispiel erstellt, um zu zeigen, wie all die genannten Dinge in der Praxis funktionieren.

Hier ist ein Beispiel für ein Programm mit Buttons, die den Text eines Labels ändern:

 

import QtQuick
import QtQuick.Controls

import EnumExample

Window {
  id: root

  signal newDaySelected(var day) // day - enum Day.

  width: 640
  height: 480
  visible: true
  title: qsTr("Hello World")

  Text {
    id: selectedDay

    anchors {
      bottom: row.top
      horizontalCenter: row.horizontalCenter
      bottomMargin: 30
    }

    text: "Select day"
    font.pixelSize: 20
  }

  ListView {
    id: row

    height: 30
    width: contentWidth

    anchors.centerIn: parent

    model: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]

    delegate: Button {
      id: rowDelegate
      height: 30
      width: 80

      text: modelData

      onClicked: {
        root.newDaySelected(index) //<-- Remember that enum can be converted to int.
        importantObject.myFun(index)
      }
    }

    orientation: ListView.Horizontal
  }

  ImportantClass {
    id: importantObject
  }

  onNewDaySelected: function(day) {
    switch (day) {
    case (ImportantClass.MONDAY):
      selectedDay.text = "Selected day: Monday"
      break
    case (ImportantClass.TUESDAY):
      selectedDay.text = "Selected day: Tuesday"
      break
    case (ImportantClass.WEDNESDAY):
      selectedDay.text = "Selected day: Wednesday"
      break
    case (ImportantClass.THURSDAY):
      selectedDay.text = "Selected day: Thursday"
      break
    case (ImportantClass.FRIDAY):
      selectedDay.text = "Selected day: Friday"
      break
    case (ImportantClass.SATURDAY):
      selectedDay.text = "Selected day: Saturday"
      break
    case (ImportantClass.SUNDAY):
      selectedDay.text = "Selected day: Sunday"
      break
    }
  }
}

Schauen wir uns die spezifischen Teile des Programms genauer an.

Zuerst müssen wir unsere Klasse mit der angegebenen URI importieren:

 

import EnumExample

Und dann erstellen wir eine Instanz von ImportantClass, um sie zu verwenden:

 

ImportantClass {
  id: importantObject
}

Läuft ziemlich gut! Jetzt können wir unser importantObject verwenden. Es gibt eine Reihe von Buttons, die den Text oben abhängig davon ändern, welchen Button Sie anklicken.

 

Hier ist die Implementierung jedes Button-Klicks. Sie können sehen, dass ein Signal mit dem Index des Buttons ausgelöst wird.

Obwohl das Signal ein Enum als Parameter akzeptiert, können Enums in Ganzzahlen umgewandelt werden. Außerdem wird eine Methode von importantObject aufgerufen, die einfach den Wert des übergebenen Enums ausgibt.

 

onClicked: {
    root.newDaySelected(index) //<-- Remember that enum can be converted to int.
    importantObject.myFun(index)
}

Ausgabe:

 

ImportantClass::FRIDAY

Das war’s eigentlich schon. Dies ist ein sehr primitives Beispiel für die Nutzung von Enums, aber Sie können eine komplexe Logik entwerfen, und sie würde perfekt in Qt funktionieren.

 

Vorteile der Programmierung in QML

QML (Qt Modeling Language) hat sich aufgrund seiner schnellen Entwicklungszeit und leistungsstarken Funktionen zu einer beliebten Wahl für Entwickler entwickelt.

 

Diese Sprache ermöglicht es, den benötigten Code im Vergleich zu anderen Programmiersprachen um bis zu 60 % zu reduzieren. Schauen wir uns nun einige der Vorteile des Programmierens mit QML an.

Das Programmieren in QML bietet Entwicklern im Vergleich zu anderen Sprachen mehrere Vorteile:

 

Einfachheit

Das Erlernen von QML + JavaScript ist wesentlich einfacher als das Schreiben von Code in C++ oder anderen Programmiersprachen. Darüber hinaus reduziert das Programmieren mit QML den benötigten Code erheblich dank seiner einfachen Syntaxstruktur.

 

Zeitsparende Funktionen

Funktionen wie States, Signals und Slots sowie Property Bindings sind enorme Zeitsparer beim Programmieren mit QML.

Diese Funktionen können mit nur wenigen Codezeilen aktiviert werden und ermöglichen es Entwicklern, komplexe Anwendungen einfach zu erstellen, ohne umfangreiche Codes schreiben zu müssen.

 

Einfachheit bei Animationen

Das Animieren verschiedener Elemente innerhalb einer Anwendung war dank der Animation-Komponenten von QML noch nie so einfach.

Eigenschaften in Ihren Elementen können mit diesen Komponenten schnell und präzise animiert werden, was während des Entwicklungsprozesses Zeit und Geld spart.

 

Flexibilität und Erweiterbarkeit

Müssen Sie eine Funktion oder ein Feature erstellen?

Mit QML können Sie problemlos C++-Objekte innerhalb Ihrer App erweitern, ohne einen völlig neuen Typ erstellen zu müssen – das spart Ihnen noch mehr Zeit!

 

Hochleistungsfähige Rendering-QML-Engine

Der Renderer, der von Qt Quick verwendet wird, bietet eine hervorragende Leistung, da er die Hardwarebeschleunigung über einen Scene Graph nutzt, der sowohl Kits als auch leistungsstarke Spiele antreibt.

 

Wann sollte man C++ anstelle von QML verwenden?

In bestimmten Szenarien müssen Sie möglicherweise auf nativen C++-Code zurückgreifen, anstatt interpretierten QML/JavaScript-Code zu verwenden.

Der Hauptvorteil hierbei ist, dass nativer Code unter datenintensiven Bedingungen eine überlegene Leistung bietet im Vergleich zu interpretierten Sprachen wie QML oder JavaScript.

Zusätzlich wird der in C++ geschriebene Code in Objektcode kompiliert und nicht interpretiert, was die Sicherheits- und Stabilitätsniveaus erhöht, da keine Fehler durch Interpretationsfehler während der Laufzeit eingeführt werden.

Ein weiterer Vorteil ergibt sich aus der Flexibilität der Qt-Komponenten, wenn diese in C++ entwickelt werden. Sie bieten oft unterschiedliche (und manchmal mehr) Funktionen als ähnliche Typen, die in QML verfügbar sind.

Zum Beispiel könnten fortgeschrittene Netzwerkmöglichkeiten nur in diesen Komponenten vorhanden sein, wenn sie mit nativem Code entwickelt wurden, wie beispielsweise in C++ oder Objective-C/Swift-Frameworks für iOS-Projekte.

Schließlich ermöglicht das Mischen von nativem Code (z. B. Java Native Invocation auf Android) und objektorientierten Klassen nicht nur aus Qt, sondern auch aus anderen Frameworks eine plattformübergreifende Kompatibilität. Dies unterstützt zusätzliche Funktionen wie GPS-Tracking, Bewegungssensoren und Kameras, indem sie nahtlos über alle unterstützten Plattformen hinweg in einer Anwendung zusammengeführt werden.

Dies ist nur möglich, weil der native Quellcode in jeder Sprache kompiliert wird, die das Zielgerät akzeptiert, einschließlich Java/Kotlin auf Android, Swift/Objective-C auf iOS oder Windows Runtime Component (Object Pascal) auf Windows 10 Mobile-Geräten.

 

3 Gedanken zum Übergeben von Enum-Eigenschaften zwischen QML und C++

Enums sind eine beliebte Möglichkeit, Mengen diskreter Werte darzustellen. Sie werden sowohl in C++ als auch in QML häufig verwendet, um die Lesbarkeit und Wartbarkeit des Codes zu verbessern.

 

Wenn Sie jedoch versuchen, Enum-Eigenschaften von C++ nach QML zu übergeben, können einige Komplikationen auftreten, die berücksichtigt werden sollten.

Schauen wir uns nun drei Hauptprobleme an, auf die Sie achten sollten, und besprechen wir, wie sie überwunden werden können.

 

Verschachteln von Enums in Klassen

Ein Ansatz zur Übergabe von Enum-Eigenschaften von C++ nach QML besteht darin, das Enum innerhalb einer Klasse zu verschachteln und dann das Klassenobjekt direkt über das Makro `Q_PROPERTY` an QML zu übergeben.

Obwohl dieser Ansatz es Entwicklern ermöglicht, das Enum von beiden Seiten zu verwenden, führt er auch einige Probleme ein.

Einerseits ist es nicht ideal, eine zusätzliche Klasse nur zu erstellen, um eine Enum-Kontexteigenschaft exportieren zu können – es wäre wünschenswert, wenn diese Klasse mehr Zweck oder Bedeutung hätte.

Außerdem kann ein Enum, das innerhalb einer Klasse deklariert wurde, nicht mehr vorwärts deklariert werden, was bei größeren Codebasen erhebliche Abhängigkeiten zwischen Headern schaffen kann, wenn dies mehrfach durchgeführt wird.

 

Verwendung des Makros `Q_ENUM_NS`

Glücklicherweise bietet Qt eine weitere Option: Anstatt Enums in Klassen zu verschachteln, können Entwickler spezielle Namespaces für die Deklaration von Enums verwenden.

Dadurch können Enums sowohl in C++ als auch in QML verwendet werden, während sie gleichzeitig die Möglichkeit behalten, leicht vorwärts deklariert zu werden – was die Wartung eines großen neuen Projekts erheblich erleichtert, wenn später Änderungen erforderlich sind.

Das Makro `Q_ENUM_NS` ermöglicht es Entwicklern, einen Namespace-Enum-QML-Typ zu deklarieren, der sofort von beiden Sprachen erkannt wird – und letztendlich die Integration erheblich reibungsloser macht, als es mit verschachtelten Klassen allein möglich wäre.

 

Meta-Objekt & Reflection

Zuletzt bietet Qt Entwicklern zusätzliche Flexibilität durch das Meta Object System (MOS).

Mit MOS erhalten Qt-Anwendungen zur Laufzeit Zugriff auf Meta-Informationen über ein QML-Objekt, die dann zusammen mit Reflexionsfunktionen wie `property()` oder `invokeMethod()` verwendet werden können.

Diese Funktionen erleichtern es Entwicklern, die mit den Reflexionsfähigkeiten von C++ im Allgemeinen oder den MOS-Funktionen von Qt im Besonderen vertraut sind, neue Wege zu finden, Daten zwischen verschiedenen Teilen ihrer Anwendung auszutauschen – einschließlich der Übergabe von Enum-Typen von C++ an QML-basierte Benutzeroberflächen.

 

Zusammenfassung

Nutzen Sie unsere plattformübergreifenden Lösungen und unsere Erfahrung! Qt ist ein innovatives und stabiles Framework, das es Ihnen ermöglicht, schnelle, zuverlässige und reaktionsschnelle Software für Desktop-, Mobile- und Embedded-Umgebungen zu entwickeln.

Scythe Studio bietet umfassende Software-Dienstleistungen – von Architekturdesign bis hin zu Body- und Team-Leasing – damit Sie Ideen ohne Sorgen in die Realität umsetzen können.

Kontaktieren Sie uns noch heute mit Ihren Fragen oder brillanten Ideen, um loszulegen!

 

Scythe-Studio - Blog Redactors

Scythe Studio Blog Redactors

Brauchen Sie Qt QML-Entwicklungsdienste?

service partner

Kommen wir zur Sache: Es ist eine Herausforderung, Top-Qt-QML-Entwickler zu finden. Helfen Sie sich selbst und starten Sie die Zusammenarbeit mit Scythe Studio – echten Experten im Qt C++ Framework.

Entdecken Sie unsere Fähigkeiten!

Neueste Artikel

[ 156 ]