Reputation: 6137
I am currently trying to develop a quite important application (OS-like) with Qt 5.2 and Qt Quick 2 ; what I would like to do is to have all the logic implemented in C++, the UI being declared thanks to QML. At this point, it seems logical and the way to get around. However, I can’t figure how to do it the clean way.. I’ve read a lot of documentation, tutorials and examples but nothing so big…
Let’s explain a little what I would like to put as an architecture ; let’s say we have a Scene object, which could contains an undefined number of Application objects. What I would like is to define the logic in CPP (how I load the applications from XML, what the scene should have as properties, …) and then show the scene with QML. Also, we have to notice that Scene and Application elements should be re-used as component ; so, here is the basic idea : I’d like to define graphical styles that are common to each object with a file in QML (extending the CPP type).
For example, I could create a file with this content :
Application {
Rectangle { ... }
}
Saying that an application should be representated as a Rectangle ; then, when I create a Scene object that have a list of Application (or one unique Application, to begin with), I would like it to be displayed automatically (‘cause this is a property of Scene object). Is it even possible ? How can I do that ?
I thought that if I extend the C++ object and declare some graphical elements for it, it would be automatic.. But actually it doesn’t look like that !
Maybe there is another way around ?
Thanks
Upvotes: 2
Views: 2577
Reputation: 515
I would advise against using C++ for the logic unless you really need to - use casese to use C++ for the logic are if you have high-performance requirements like realtime data that needs to processed like 10x per second.
As most of the use cases do not have this requirement, it is better to use QML also for application logic because it will save up to 90% source code (and time) compared with C++. Especially in the beginning of development, you are way faster to code the logic in QML and get results faster. You can later on still move the logic to C++ if needed.
There are 2 good guides about this topic available that explain this in more detail and come with source code examples:
Upvotes: 0
Reputation: 24396
I don't like this question too much, as it's not really asking anything in particular. The Qt documentation is very comprehensive, so I often find it strange when people say they've read documentation, tutorials and examples, and still haven't found what they're looking for. However, I think I understand the gist of what you're asking, and think the answer could be useful to some, so I'll try to answer it.
main.cpp
#include <QtGui/QGuiApplication>
#include <QtQml>
#include <QQuickItem>
#include "qtquick2applicationviewer.h"
class ApplicationItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QString title MEMBER mTitle NOTIFY titleChanged)
public:
ApplicationItem(QQuickItem *parent = 0) : QQuickItem(parent) {
}
public slots:
void close() {
emit closed(this);
}
signals:
void titleChanged(QString title);
void closed(ApplicationItem *app);
private:
QString mTitle;
};
class SceneItem : public QQuickItem
{
Q_OBJECT
public:
SceneItem() {
}
public slots:
void startApp(const QString &qmlFile) {
QQmlComponent *component = new QQmlComponent(qmlEngine(this), QUrl(qmlFile));
if (component->isLoading()) {
QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)),
this, SLOT(componentStatusChanged()));
} else {
// The component was synchronously loaded, but it may have errors.
if (component->isError()) {
qWarning() << "Failed to start application:" << component->errorString();
} else {
addApp(component);
}
}
}
void componentStatusChanged(QQmlComponent::Status status) {
QQmlComponent *component = qobject_cast<QQmlComponent*>(sender());
if (status == QQmlComponent::Ready) {
addApp(component);
} else if (status == QQmlComponent::Error) {
qWarning() << "Failed to start application:" << component->errorString();
}
}
void appClosed(ApplicationItem *app) {
int appIndex = mApplications.indexOf(app);
if (appIndex != -1) {
mApplications.removeAt(appIndex);
app->deleteLater();
}
}
private:
void addApp(QQmlComponent *component) {
ApplicationItem *appItem = qobject_cast<ApplicationItem*>(component->create());
appItem->setParentItem(this);
connect(appItem, SIGNAL(closed(ApplicationItem*)), this, SLOT(appClosed(ApplicationItem*)));
mApplications.append(appItem);
delete component;
}
QList<ApplicationItem*> mApplications;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer viewer;
qmlRegisterType<ApplicationItem>("Test", 1, 0, "ApplicationItem");
qmlRegisterType<SceneItem>("Test", 1, 0, "SceneItem");
viewer.setMainQmlFile(QStringLiteral("qml/quick/main.qml"));
viewer.showExpanded();
return app.exec();
}
#include "main.moc"
I represented both classes as QQuickItem subclasses. SceneItem
is composed of many ApplicationItem
instances, which are added to the scene by invoking startApp()
. This slot takes a path to a QML file as its argument. This file could be loaded over a network, or from a local file, hence we account for the possibility of both synchronous and asynchronous loading.
The QML file should describe the visual appearance of an application, and the scene expects its root type to be ApplicationItem
. As an example, here's MySweetApp.qml:
import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0
ApplicationItem {
id: someAppStyle
title: "My Sweet App"
width: 100
height: 100
MouseArea {
anchors.fill: parent
drag.target: parent
}
Rectangle {
radius: 4
color: "lightblue"
anchors.fill: parent
Text {
anchors.left: parent.left
anchors.right: closeButton.right
anchors.leftMargin: 4
anchors.top: parent.top
anchors.topMargin: 4
text: someAppStyle.title
}
Button {
id: closeButton
anchors.right: parent.right
anchors.rightMargin: 4
anchors.top: parent.top
anchors.topMargin: 2
onClicked: close()
text: "x"
width: 20
height: width
}
}
}
Applications can close themselves by invoking the close()
slot declared in ApplicationItem
.
Here's main.qml:
import QtQuick 2.0
import QtQuick.Controls 1.0
import Test 1.0
SceneItem {
id: scene
width: 360
height: 360
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: "Launch app"
onClicked: scene.startApp("qml/quick/MySweetApp.qml")
}
}
This is where the SceneItem
is declared, along with a simple interface to launch several instances of My Sweet App (it's a very useful app).
I believe this is the most appropriate way to do what you're asking. It avoids the hassle of setting up lists of ApplicationItems
in C++ which are exposed to QML (it's actually not that hard, but this is one area where the documentation could be more obvious), and allows the users of your OS freedom in how applications appear. If you want to be more strict in what can be styled, I'd suggest looking into how Qt Quick Controls does styling.
Upvotes: 1