📜 ⬆️ ⬇️

C ++ integration with QML

Introduction


The QML language for Qt Quick makes it easy to do many things, especially with regard to animated user interfaces. However, not everything can be done or not everything fits the implementation in QML, in particular:


As you will see later, Qt easily displays C ++ code for QML. In this article we will create a small but functional application that does this. The example is written for Qt 5 and uses Qt Quick components, so you need at least Qt 5.1.0 to run the example.

Overview


Basic steps in order to show the type of C ++, with its properties, methods, signals and slots, QML environment:

Details are in the Qt documentation section, Mapping C ++ Attributes for QML and in the tutorial, Creating C ++ Extensions for QML .

Ssh key generator


For example, create a small application that will generate a pair of public / private SSH keys using the GUI. The user will be provided with controls for the corresponding parameters, then the ssh-keygen program will be launched to generate a key pair.
')
We implement the user interface using the new Qt Quick controls, since it was designed as a desktop application. Initially, to gain interaction experience, run qmlscene with QML source code. The screenshot is shown below:



The user interface prompts the user for information about the type of key, the file name for the generated private key, and, optionally, the secret phrase that needs to be confirmed.

C ++ class


Now, having the interface, we need to implement the internal functionality. We cannot call an external program directly from QML, so we have to write it in C ++ (which is the goal of our application).

First, we define a class that encapsulates the functionality for key generation (it will be displayed as a new KeyGenerator class in QML). This is done in the KeyGenerator.h header file:

#ifndef KEYGENERATOR_H #define KEYGENERATOR_H #include <QObject> #include <QString> #include <QStringList> // Simple QML object to generate SSH key pairs by calling ssh-keygen. class KeyGenerator : public QObject { Q_OBJECT Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged) Q_PROPERTY(QStringList types READ types NOTIFY typesChanged) Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) Q_PROPERTY(QString passphrase READ passphrase WRITE setPassphrase NOTIFY passphraseChanged) public: KeyGenerator(); ~KeyGenerator(); QString type(); void setType(const QString &t); QStringList types(); QString filename(); void setFilename(const QString &f); QString passphrase(); void setPassphrase(const QString &p); public slots: void generateKey(); signals: void typeChanged(); void typesChanged(); void filenameChanged(); void passphraseChanged(); void keyGenerated(bool success); private: QString _type; QString _filename; QString _passphrase; QStringList _types; }; #endif 

We need to inherit our class from QObject . We declare all the properties that we need and all methods associated with them, notification methods become signals. In our case, we want to have properties for the selected key type, a list of all valid types of SSH keys, a file name, and a secret phrase. The key type is arbitrarily made a string. It would be possible to make an enumeration, but the example would be more complicated.

By the way, the new macro feature Q_PROPERTY in Qt 5.1.0 is the argument MEMBER . It allows you to set a variable that is a member of the class that will be bound to the property, without implementing the get and set functions. This feature is not used here.

Next, we declare the methods for setting and receiving and for signals. We will also declare one slot named generateKey () . All this will be available for QML. If we wanted to make an ordinary method visible to QML, we would need to mark it as Q_INVOKABLE . In this case, it was decided to make the generateKey () method a slot, since it may be useful in the future, but it can easily be called as well.

At the end, we will declare all the closed class members we need.

C ++ implementation


Now, let's look at the implementation of the class in KeyGenerator.cpp . Here is the source code:

 #include <QFile> #include <QProcess> #include "KeyGenerator.h" KeyGenerator::KeyGenerator() : _type("rsa"), _types{"dsa", "ecdsa", "rsa", "rsa1"} { } KeyGenerator::~KeyGenerator() { } QString KeyGenerator::type() { return _type; } void KeyGenerator::setType(const QString &t) { // Check for valid type. if (!_types.contains(t)) return; if (t != _type) { _type = t; emit typeChanged(); } } QStringList KeyGenerator::types() { return _types; } QString KeyGenerator::filename() { return _filename; } void KeyGenerator::setFilename(const QString &f) { if (f != _filename) { _filename = f; emit filenameChanged(); } } QString KeyGenerator::passphrase() { return _passphrase; } void KeyGenerator::setPassphrase(const QString &p) { if (p != _passphrase) { _passphrase = p; emit passphraseChanged(); } } void KeyGenerator::generateKey() { // Sanity check on arguments if (_type.isEmpty() or _filename.isEmpty() or (_passphrase.length() > 0 and _passphrase.length() < 5)) { emit keyGenerated(false); return; } // Remove key file if it already exists if (QFile::exists(_filename)) { QFile::remove(_filename); } // Execute ssh-keygen -t type -N passphrase -f keyfileq QProcess *proc = new QProcess; QString prog = "ssh-keygen"; QStringList args{"-t", _type, "-N", _passphrase, "-f", _filename}; proc->start(prog, args); proc->waitForFinished(); emit keyGenerated(proc->exitCode() == 0); delete proc; } 

In the constructor, some variables, members of the class are initialized. For interest, the C ++ 11 initialization list feature is used to initialize the member variable of the _types class, which is of type QStringList . The destructor does nothing now, but is present for completeness and future expansion.

Retrieval functions such as type () simply return the value of the corresponding class member variable. The installation functions set the corresponding variables, taking care to check that the new value is different from the old one and, if so, emit the corresponding signal. Note that the signals created by MOC, as always, do not need to be implemented, they can only be emitted at the right time.

Only one non-trivial method is the generateKey () slot. It does some argument checking and creates a QProcess to start the external ssh-keygen program. For simplicity, and since this is usually done quickly, we do this synchronously and wait for ssh-keygen to complete. When this program is completed, we emit a signal having an argument of type boolean, which indicates whether the key generation was successful or not.

QML code


Now take a look at the QML code in main.qml :

 // SSH key generator UI import QtQuick 2.1 import QtQuick.Controls 1.0 import QtQuick.Layouts 1.0 import QtQuick.Dialogs 1.0 import com.ics.demo 1.0 ApplicationWindow { title: qsTr("SSH Key Generator") statusBar: StatusBar { RowLayout { Label { id: status } } } width: 369 height: 166 ColumnLayout { x: 10 y: 10 // Key type RowLayout { Label { text: qsTr("Key type:") } ComboBox { id: combobox Layout.fillWidth: true model: keygen.types currentIndex: 2 } } // Filename RowLayout { Label { text: qsTr("Filename:") } TextField { id: filename implicitWidth: 200 onTextChanged: updateStatusBar() } Button { text: qsTr("&Browse...") onClicked: filedialog.visible = true } } // Passphrase RowLayout { Label { text: qsTr("Pass phrase:") } TextField { id: passphrase Layout.fillWidth: true echoMode: TextInput.Password onTextChanged: updateStatusBar() } } // Confirm Passphrase RowLayout { Label { text: qsTr("Confirm pass phrase:") } TextField { id: confirm Layout.fillWidth: true echoMode: TextInput.Password onTextChanged: updateStatusBar() } } // Buttons: Generate, Quit RowLayout { Button { id: generate text: qsTr("&Generate") onClicked: keygen.generateKey() } Button { text: qsTr("&Quit") onClicked: Qt.quit() } } } FileDialog { id: filedialog title: qsTr("Select a file") selectMultiple: false selectFolder: false nameFilters: [ "All files (*)" ] selectedNameFilter: "All files (*)" onAccepted: { filename.text = fileUrl.toString().replace("file://", "") } } KeyGenerator { id: keygen filename: filename.text passphrase: passphrase.text type: combobox.currentText onKeyGenerated: { if (success) { status.text = qsTr('<font color="green">Key generation succeeded.</font>') } else { status.text = qsTr('<font color="red">Key generation failed</font>') } } } function updateStatusBar() { if (passphrase.text != confirm.text) { status.text = qsTr('<font color="red">Pass phrase does not match.</font>') generate.enabled = false } else if (passphrase.text.length > 0 && passphrase.text.length < 5) { status.text = qsTr('<font color="red">Pass phrase too short.</font>') generate.enabled = false } else if (filename.text == "") { status.text = qsTr('<font color="red">Enter a filename.</font>') generate.enabled = false } else { status.text = "" generate.enabled = true } } Component.onCompleted: updateStatusBar() } 

The above code is a bit large, however, most of the work is related to the layout of the GUI components. This code should be extremely simple to move on.

Note that we import com.ics.demo version 1.0. Soon, we will see where the name of this module appears. This makes the new QML KeyGenerator type available and we declare its instance. Having access to its C ++ properties as QML properties, you can call its methods and work with signals, as is done with onKeyGenerated .

A more complete application should be done with additional error checks and meaningful error notifications if the key generation was unsuccessful (we could easily add a new method or property for this). The user interface can also be improved by making it more flexible.

Our core program is, in fact, a wrapper, such as qmlscene . All we need is to register our type, to access it in the QML engine:

 qmlRegisterType<KeyGenerator>("com.ics.demo", 1, 0, "KeyGenerator"); 

This makes the C ++ KeyGenerator type available as QML KeyGenerator type, in the module com.ics.demo version 1.0, which will be imported.

Typically, to run QML code from an executable file, in the main program you create QGuiApplication and QQuickView . Now, to use Qt Quick components, some extra work is needed if the top level element is ApplicationWindow or Window . You can see the source code for how this is implemented. This is a stripped-down version of qmlscene , the minimum necessary for an example.

Here is the full listing of the main program, main.cpp :

 #include <QApplication> #include <QObject> #include <QQmlComponent> #include <QQmlEngine> #include <QQuickWindow> #include <QSurfaceFormat> #include "KeyGenerator.h" // Main wrapper program. // Special handling is needed when using Qt Quick Controls for the top window. // The code here is based on what qmlscene does. int main(int argc, char ** argv) { QApplication app(argc, argv); // Register our component type with QML. qmlRegisterType<KeyGenerator>("com.ics.demo", 1, 0, "KeyGenerator"); int rc = 0; QQmlEngine engine; QQmlComponent *component = new QQmlComponent(&engine); QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit())); component->loadUrl(QUrl("main.qml")); if (!component->isReady() ) { qWarning("%s", qPrintable(component->errorString())); return -1; } QObject *topLevel = component->create(); QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel); QSurfaceFormat surfaceFormat = window->requestedFormat(); window->setFormat(surfaceFormat); window->show(); rc = app.exec(); delete component; return rc; } 


If this is not obvious, when using modules written in C ++ with QML, you cannot use the qmlscene program, because C ++ code will not be linked. If you try this, you will receive an error message that the module is not installed.

findings


This example shows how easy it is to create new QML components in C ++ and display properties, signals and slots. Although much can be done with QML, C ++ is still useful and, as a rule, will be used in combination with QML in any non-trivial application.

You can download the entire sample source code from here .

Source: https://habr.com/ru/post/197374/


All Articles