Sizlerin de takip ettiği üzere, bir süredir QT ve QML kullanımına ilişkin bir çok yazı ve kod paylaşıyorum. Bugün de sizlerle QML ile C++’ı nasıl kullanabileceğinize, bu kullanım sırasında dikkat etmeniz gereken hususlara, bir kaç örnek üzerinden bakacağız. Bu yazı ile birlikte, kendi QML ve C++ uygulamarınızı kolay bir şekilde geliştirebileceğinizi umuyorum.
Bu yazıda, QML ve C++ kullanımına ilişkin, temel ve gerekli olduğunu düşündüğüm bir takım konulara değineceğim, daha karmaşık konulara (C++ modelleri ile QML’i nasıl entegre edebiliriz, özellikle ListView, ComboBox gibi bileşenler için) ise farklı yazılarda eğileceğiz.
Bu yazıma belli başlı sorular ile yön vermek istiyorum. O zaman ilk soru ile başlayalım.
İçerik
Neden C++/QML kullanmalıyım?
Aslında bakarsanız bu soru, biraz da sizin ihtiyaçlarınıza bağlı. Normal şartlarda, sadece QML veya sadece C++ kullanarak da pekala bir çok ihtiyacınızı gerçekleştirebilecek uygulamalar geliştirebilirsiniz. Bana kalırsa bu birlikteliğin, en önemli iki avantajı, görsel kısımlar ile işlem yoğun kısımları birbirinden kolay bir şekilde ayırmak ve hali hazırda mevcut kodlarınızı kullanabilmek derim. Tabi, C++ kullanabilmek de, benim gibi yazılımperver dostlar için bir motivasyon olabilir. Bu hususları biraz daha anlamak adına C++ mı QML mi sorularına, ayrı ayrı bakalım.
Neden C++ kullanmalıyız?
Eğer uzun bir süredir sayfamı takip ediyorsanız artık C++’ı neden ve nerede kullanmanız gerektiğini biliyorsunuzdur diye umuyorum ama burada QML ve QT anlamında tekrar bakacak ve hatırlayacak olursak. Eğer veri yoğun ve performans kritik bir iş yapacaksanız. C++ sizin için doğru seçim olacaktır. QML ile karşılaştıracak olursak, C++ kodu, binary dosyalar oluşturacak ve QML ve benzeri kodlara göre daha tutarlı ve emniyetli bir seçenek olacaktır. Her ne kadar ben henüz bu özellikleri kullanmamış olsam da, QT C++ kütüphaneleri bazı ileri seviye kullanımlarda QML’den daha fazla özellik sunuyorlarmış.
Son olarak, QML, açılış zamanında bir takım hazırlıklar ve işler yaptığı için C++ uygulamasına göre biraz daha yavaş olacaktır. Bu konu ile ilgili aşağıdaki sayfada güzel bir karşılaştırma ve analiz bulabilirsiniz. Şunu da ifade etmeliyim ki, ilgili yazı Qt 5.8 için yazılmış, yani o zamandan bu yana performans anlamında ilerleme olduğunu muhakkak göz önünde bulundurmalısınız.
https://woboq.com/blog/qml-vs-cpp-for-application-startup-time.html
Şimdi, diğer soruya bakalım.
Neden QML kullanmalıyız?
Özellikle, çoklu platform destekleyecek arayüzlerin geliştirilmesi için QML kullanılmasını tavsiye ediyoruz. Aslında bu konuya aşağıdaki yazımda kısaca değinmiştim:
Burada bir kaç önemli hususun üzerinden geçeceğiz. Öncelikle QML ve Javascript hem öğrenmesi kolay hem de arayüz geliştirmek, bu sayede hem zamandan hem geliştirilecek kod miktarı ciddi anlamda azalmakta. QML ile gelen özelliklerin bağlanması, durum yönetimleri, sinyaller gibi bir çok mekanizma da size bu anlamdan kolaylık sağlayacaklar. Bir diğer önemli avantaj da, animasyonların çok kolay bir şekilde yapılabilmesi ve bunların mevcut bileşenler ile kolay bir şekilde ilişkilendirilebilmesi. Görselleştirme performans anlamında da QML’in avantajı mevcut. Arka planda OpenGL ve C++ kullanılması sebebi ile mobil aygıtlarda sizi pek üzmeyecektir.
Evet hızlıca C++ ve QML perspektifinden olaya baktıktan sonra artık bu ikisini birlikte nasıl kullanabileceğimize göz atalım.
C++/QML kullanımı
Evet gelelim bu işi nasıl yapacağımıza. Bu başlıktan itibaren, adım adım ve örnek bir kod üzerinden gidiyor olacağız. Burada geliştirdiğimiz kodlara aşağıdaki adresten de ulaşabilirsiniz:
https://github.com/yazilimperver/QmlCPlusPlusIntegration
Öncelikle, QML uygulamaları, bir çalışma zamanı ortamında çalıştırılmaktadır. Bu çalışma zamanı da C++ kullanılarak QtQml modülü içerisinde geliştirilmiştir. Bu anlamda, ihtiyaç duyacağımız ilk şey QML kodumuzu, bu çalışma zamanı motoruna sağlamak olacak. Bunun için de aşağıdaki kodu kullanacağız:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <QGuiApplication> #include <QQmlApplicationEngine> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); } |
QML, QT’in “Meta Object System”ini kullandığı için, QML içerisinde C++ kodunu çağırmak, oldukça kolay. Bunu gerçekleştirmek için öncelikle:
- İlgili C++ sınıfımız QObject‘ten türetilmeli, Q_OBJECT makrosu sınıf tanımı içerisinde olmalıdır:
1 2 3 4 5 6 7 8 9 10 |
// Our header file #pragma once #include <QObject> #include <QString> class BackEndClass : public QObject { Q_OBJECT public: explicit BackEndClass(QObject *parent = nullptr); }; |
- Daha sonra sınıfımızı main.cpp içerisinden aşağıda belirtildiği şekilde kayıt ettirmemiz gerekiyor. Bundan sonra artık BackEndClass bir tip olarak eklenmiş oldu, bu tipi de QML içerisinden kullanmak için “import yazilimperver.example.backend 1.0” eklememiz yeterli olacak.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "BackEndClass.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); // Sinifimizi kaydettirdigimiz yer qmlRegisterType<BackEndClass>("yazilimperver.examples.BackEndClass", 1, 0, "BackEndClass"); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); } |
- Son tahlilde bu örnek için aşağıdaki QML dosyasını kullanacağız. Burada BackEndClass sınıfımızın da nasıl QML içerisinde kullanabileceğimizi görebilirsiniz. Aslında diğer bileşenlerden pek bir farkı yok.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import yazilimperver.examples.BackEndClass 1.0 ApplicationWindow { width: 640 height: 480 visible: true BackEndClass{ id: myBackEndClass } ColumnLayout{ spacing: 2 anchors.fill: parent Rectangle { color: "green" Layout.fillWidth: true height: 40 Text { text: "QML/C++ Integration Example" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter anchors.top: parent.top height: parent.height width:parent.width font.family: "Helvetica" font.pointSize: 16 font.capitalization: Font.AllUppercase color: "white" } } Rectangle{ color:"blue" Layout.fillHeight: true Layout.fillWidth: true } } } |
Tabi ki işimiz burada bitmedi. Bilakis yeni başlıyoruz. Şimdi geldik, QML ve QT arasındaki etkileşimleri nasıl gerçekleştirebileceğimize. Aslına bakarsanız, “Meta Object System” sayesinde normal QObject‘ten türetilmiş C++ sınıfları ile benzer şekilde kullanabileceğiz. O zaman gelsin sorular. Öncelikle, verileri nasıl paylaşabiliriz?
QML/C++ veri paylaşımı
Qt bizler için temel, QT sınıflarını QML’e de sunmakta, yani QString ve benzeri tipleri kullanmak için özel bir şey yapmanıza gerek yok. Tabi bunları aynı isimler kullanamayabilirsiniz (temel tipler dışında). Bir fikir vermesi açısından temel QT veri tiplerini buraya ekliyorum. Daha detaylı bilgi için https://doc.qt.io/qt-5/qtqml-cppintegration-data.html sayfasına göz atabilirsiniz.
Qt Tipi | QML Karşılığı |
bool | bool |
unsigned int, int | int |
double | double |
float, qreal | real |
QString | string |
QUrl | url |
QColor | color |
QFont | font |
QDateTime | date |
QPoint, QPointF | point |
QSize, QSizeF | size |
QRect, QRectF | rect |
QMatrix4x4 | matrix4x4 |
QQuaternion | quaternion |
QVector2D, QVector3D, QVector4D | vector2d, vector3d, vector4d |
Enums declared with Q_ENUM() or Q_ENUMS() | enumeration |
Bunlar yanında, sıralı veri tipleri/dizileri de Javascript dizisi olarak kullanılabilmektedir.
Kendi tiplerimizi nasıl kullanacağımıza da zaten baktık. Bunları öncelikle .cpp dosyası içerisinde kaydettiriyoruz ve daha sonra “import” mekanizması ile bunu qml içerisinde de kullanabileceğiz.
QML/C++ arası etkileşim
QML ile C++ arasındaki etkileşimi bir kaç şekilde gerçekleştirebiliyoruz. Temel olarak bu etkileşim tanımladığımız C++ sınıfları üzerinden gerçekleştiriliyor. Bu sınıfı nasıl tanımlayacağımıza yukarıda baktık. QML kodu bu sınıflardaki aşağıdakilere erişebilmektedir:
- Uygun bir şekilde (bunun nasıl olacağına hemen bakacağız) tanımlanan değişkenler (“properties”),
- Uygun bir şekilde (bunun da nasıl olacağına hemen bakacağız) tanımlanan metotlar,
- Tanımlanmış olan slot’lara bağlantı kurup/çağırabiliriz,
- Tanımlanmış olan sinyalleri tetikleyebiliriz.
Şimdi gelelim bu “uygun bir şekilde” ibarelerini nasıl sağlayacağımıza 🙂
Öncelikle değişkenlere bakalım. Sınfılarınıza ilişkin değişkenlerin QML tarafından kullanılabilmesi için Q_PROPERTY makrosu ile tanımlanmış olmaları gerekmektedir. Hemen bir örneğe bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Our header file #pragma once #include <QObject> #include <QString> class BackEndClass : public QObject { Q_OBJECT Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) public: explicit BackEndClass(QObject *parent = nullptr); // Degiskenimizin "setter" metodu void setMessage(const QString &msg); // Degiskenimizin "getter" metodu QString message() const; signals: // Degiskenimize iliskin sinyal void messageChanged(); private: // Degiskenimiz QString mMessage; }; |
Bu tanımlama ile birlikte QML tarafından message değişkenine erişilebilir ve bu değişkene ilişkin değişiklikler de ilgili messageChanged sinyali ile QML tarafına bildirilebilecektir. Şimdi gelelim sinyal ve slot’lar dışındaki metotları QML tarafından kullanabilmek için yapmamız gerekenlere.
Aslında bu da oldukça kolay. Bunun için de Q_INVOKABLE makrosunu kullanıyoruz, basitçe ilgili public metodun önüne bunu eklemeniz yeterli. Bunun için de C++ içerisinde bir sayaç değişkeni ekliyoruz ve bunun arttırılması için bir metot ve sıfırlanması için bir slot tanımlıyoruz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// Our header file #pragma once #include <QObject> #include <QString> #include <QVector> class BackEndClass : public QObject { Q_OBJECT // Ilgili mesaj degiskenine erisim yontemi Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) public: explicit BackEndClass(QObject *parent = nullptr); // Mevcut sayac degerini don Q_INVOKABLE int incrementCounter(); // Degiskenimizin "setter" metodu void setMessage(const QString &msg); // Degiskenimizin "getter" metodu QString message() const; signals: // Degiskenimize iliskin sinyal void messageChanged(); public slots: void resetCounter(); private: // Degiskenimiz QString mMessage {"Hello from BackEndClass!"}; // Sayac int mCounter{0}; }; |
Son olarak QML tarafını da aşağıdaki gibi güncelleyerek örneğimizi tamamlıyoruz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import yazilimperver.examples.BackEndClass 1.0 ApplicationWindow { width: 640 height: 480 visible: true BackEndClass{ id: myBackEndClass } ColumnLayout{ spacing: 2 anchors.fill: parent Rectangle { color: "green" Layout.fillWidth: true height: 40 Text { text: "QML/C++ Integration Example" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width:parent.width height:parent.height font.family: "Helvetica" font.pointSize: 12 color: "white" } } // Mesaj satirir RowLayout{ Layout.fillWidth: true Rectangle { color: "red" width: 200 height: 40 Text { text: "Message: " horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width:parent.width height:parent.height font.family: "Helvetica" font.pointSize: 12 color: "white" } } Rectangle { color: "brown" Layout.fillWidth: true height: 40 Text { text: myBackEndClass.message horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width:parent.width height:parent.height font.family: "Helvetica" font.pointSize: 12 color: "white" } } } // Sayac satiri RowLayout{ Layout.fillWidth: true Rectangle { color: "red" width: 200 height: 40 Text { text: "Counter: " horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width:parent.width height:parent.height font.family: "Helvetica" font.pointSize: 12 color: "white" } } Rectangle { color: "brown" Layout.fillWidth: true height: 40 Text { id:counterText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width:parent.width height:parent.height font.family: "Helvetica" font.pointSize: 12 color: "white" } } Button{ text:"Update Counter" onClicked:{ counterText.text = String(myBackEndClass.incrementCounter()) } } Button{ text:"Reset Counter" onClicked:{ myBackEndClass.resetCounter() counterText.text = String(myBackEndClass.incrementCounter()) } } } Rectangle{ color:"blue" Layout.fillHeight: true Layout.fillWidth: true } } } |
Evet arkadaşlar, temel entegrasyon için gerekli olan kısımları sizlere aktarmaya çalıştım. Açıkçası bu zamana kadar, bu basit kullanım benim işimi gördü ama buna ilişkin daha bir çok husus var ki burada daha fazla değinmeyeceğim. Ama ilgili dostlar https://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html bağlantısına bir göz atabilirler.
Bitirmeden önce son bir husustan daha bahsetmek istiyorum. Burada takip ettiğim etkileşim yönteminin yanında https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html da da bahsedilen farklı yöntemler mevcut. Ben burada özellikle C++ tarafından, direk QML tarafına sinyal ve slot mekanizmaları dışında, fonksiyon ve benzeri mekanizmalarla çok erişmedim, fakat bunlar mümkün. Açıkçası ben, kendi uygulamalarımda sizler ile paylaşmış olduğum yaklaşımı takip ediyorum ama farklı kullanımlarından da haberdar olmakta fayda var.
Bu yazımızın da sonuna geldik. Kodun son haline https://github.com/yazilimperver/QmlCPlusPlusIntegration adresinden ulaşabilirsiniz.
Umarım, QML ile C++ kullanımına ilişkin sizlere bir yol göstermiştir. Bir sonraki yazımda görüşmek dileğiyle, kendinize iyi bakın.
Kaynaklar
- https://woboq.com/blog/qml-vs-cpp-for-application-startup-time.html
- https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
- https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html
- https://docs.ubuntu.com/phone/en/apps/qml/tutorials-add-cpp-backend-your-qml-app
- https://www.youtube.com/watch?v=9BcAYDlpuT8
Merhabalar,
Gerçekten de çok faydalandım.
Net ve açıklayıcı bir bilgi olmuş.
Faydalı bir eser. Çok teşekkürler.
Bu konuyu kurcalarken aşağıdaki videoya da ulaşmıştım. Bu da gayet faydalı. İlgilenenler için buraya koyayım:
https://www.youtube.com/watch?v=9BcAYDlpuT8
Geri bildirimleriniz için teşekkür ediyorum. İlgili bağlantıyı da yazıya ekledim.