Extending QML ApplicationWindow in C++ - c++

How can you properly extend the QML ApplicationWindow type? According to the documentation, ApplicationWindow instantiates a QQuickWindow. So I tried sub classing from QQuickWindow and exposing the type to QML as MyWindow. The problem is that MyWindow doesn't actually extend the QML type ApplicationWindow, so you don't get all the properties like menuBar and toolBar. How can I extend ApplicationWindow in C++ and expose it to QML? Here is what I'm currently doing:
class MyQuickWindow : public QQuickWindow
{
//...irrelevant additions
}
int main()
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyQuickWindow>("MyExtensions", 1, 0, "MyApplicationWindow");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
Here is the QML file:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import MyExtensions 1.0
MyApplicationWindow {
width: 800
height: 600
visible: true
menuBar: MenuBar { // ERROR: menuBar is not a property
Menu {
title: "File"
MenuItem { text: "New" }
MenuItem { text: "Open" }
}
}
}
Note that I need to have my own additions in C++ to the QQuickWindow for other reasons.

It seems like the job for qmlRegisterType. And it is a bit hard to say if you miss something with your C++ part but registering the type should help. That is for exposing the type itself and should enable the derived QQuickWindow functionality (derived from ApplicationWindow actually). But for what you are adding you need to deal with Q_PROPERTY and Q_INVOKABLE (which is for functions) mechanism. See the whole bunch of Q_* QObject macro.
And if that was not enough then there is an example for such inheritance.
Correction: the author is dealing with QML-made type but he can still try to mimic the type on his own. The path to ApplicationWindow.qml source code is: C:\Qt\5.3\Src\qtquickcontrols\src\controls where C:\Qt\5.3\ is the root for selected Qt version.I would attempt that and that is feasible unless we want to find out about explicit QML inheritance. That file can also be found at Qt source code repository.

Related

Add a QTreeView in QML

I would like to register a QTreeView c++ object to QML.
I tried to register it like this:
main.cpp:
qmlRegisterType<QTreeView>("com.MyApp.QTreeView", 1, 0, "QTreeView");
relevant code in main.qml
import com.MyApp.QTreeView 1.0
QWindow {
QTreeView{
headerHidden: true
}
}
Result: it compiles. headerHidden property is found so it is registered correctly. However I have an error at runtime:
ASSERT: "!d->isWidget" in file kernel\qobject.cpp, line 2090
QWidgets are not directly compatible with QML such that they can be embedded in a QML view. They are two different UI technologies and cannot be used together in that fashion.
You can however embed a QML view inside of a QWidget hierarchy:
https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
Or just use the QML TreeView component instead:
https://doc.qt.io/qttreeview/qml-treeview.html

Tumbler in C++/Qt

I'm trying to implement a tumbler to set the time for an alarm in C++. Yet I've only seen tumblers in Qt quick and therefore coded in QML. Now I've tried to get QML code in my C++ code by doing:
void SmartAlarm::showTumbler(){
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl(":/files/includes/AlarmTumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
rightLayout->addWidget(quickWidget);
}
My QML file looks like this:
import QtQuick 2.12
import QtQuick.Window 2.2
import QtQuick.Controls 2.12
import QtQuick.Extras 1.4
TumblerColumn{
id: weekdayTumbler
model: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
}
TumblerColumn {
id: hourTumbler
model: 24
}
TumblerColumn {
id: minuteTumbler
model: 60
}
All I get is a blank, white widget in my layout. What am I doing wrong? Is there a way to implement a tumbler in Qt without using QMLs?
I think it doesn't find the tumbler-file. You can check this the easiest by starting the program and check in the Application Output (Bottom Menu in QTCreator) for following message:
":/files/includes/AlarmTumbler.qml: No such file or directory"
If you can't find it, it might be because you use a Shadow Build and the actual execution-files are in a different folder than the QML Files. To solve this, you can go to "Projects" and deactivate "Shadow Build", rebuild and you should see the tumbler.
The implementation itself should work fine. I tested it locally, added everything to the MainWindow though cause I don't know where your "rightLayout" comes from.
ui->setupUi(this);
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl("tumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
this->ui->rightLayout->addWidget(quickWidget);

It is possible connect a QML Object existing signal in C++ QObject::connect()?

The QML TreeView has an signal named: doubleClicked(QModelIndex)
ref: https://doc.qt.io/qt-5.10/qml-qtquick-controls-treeview.html#doubleClicked-signal
Its possible connect that existing signal in a C++ QObject::connect() ??
I tried this:
QQmlApplicationEngine engine;
QObject *myTreeMenu = engine.rootObjects().at(0)->findChild<QObject*>("myTreeMenu");
connect(myTreeMenu , SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotModelClicked(QModelIndex)));
But i receive this return error:
QObject::connect: No such signal TreeView_QMLTYPE_63_QML_68::doubleClicked(QModelIndex) in '...'
QObject::connect: (sender name: 'myTreeMenu ')
The documentation for Qt 5.11 explains why this is not the best way to work with C++ and QML. I would suggest you instead expose a slot or Q_INVOKABLE in your C++ object and call that in the onDoubleClicked handler:
Q_INVOKABLE void doStuff();
in QML:
onDoubleClicked: cppObject.doStuff()
The documentation has a "before and after" example demonstrating this; here's the bulk of it:
With this approach, references to objects are "pulled" from QML. The
problem with this is that the C++ logic layer depends on the QML
presentation layer. If we were to refactor the QML in such a way that
the objectName changes, or some other change breaks the ability for
the C++ to find the QML object, our workflow becomes much more
complicated and tedious.
Refactoring QML is a lot easier than refactoring C++, so in order to
make maintenance pain-free, we should strive to keep C++ types unaware
of QML as much as possible. This can be achieved by "pushing"
references to C++ types into QML:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The QML then calls the C++ slot directly:
import QtQuick 2.11
import QtQuick.Controls 2.4
Page {
Button {
text: qsTr("Restore default settings")
onClicked: backend.restoreDefaults()
}
}
With this approach, the C++ remains unchanged in the event that the
QML needs to be refactored in the future.
In the example above, we set a context property on the root context to
expose the C++ object to QML. This means that the property is
available to every component loaded by the engine. Context properties
are useful for objects that must be available as soon as the QML is
loaded and cannot be instantiated in QML.
Integrating QML and C++ demonstrates an alternative approach where QML
is made aware of a C++ type so that it can instantiate it itself.

Signal-Slot connection after destruction of qml objects?

What happens with the connection of signals from c++ objects to qml objects slots after destruction of qml objects?
Item {
function qmlFunction() {
cppObject.cppObjectFunction()
}
Component.onCompleted: {
cppObject.someSignal.connect(qmlFunction);
}
Component.onDestruction: {
cppObject.someSignal.disconnect(qmlFunction);
}
}
Before I wrote Component.onDestruction with disconnect() the program displayed an error message:
qrc:/qml/xxxxxxxx.qml:77: TypeError: Cannot call method 'cppObjectFunction' of undefined
Is the disconnection of signals and slots not performed automatically?
Object cppObject "always" exist and passed to qml this way:
main.cpp
QQmlApplicationEngine engine;
Model* model = new Model::instance(&engine);
engine.setObjectOwnership(model, QQmlEngine::CppOwnership);
engine.rootContext()->setContextProperty("cppObject", model);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
QML item is loaded by the Loader and can be reloaded several times during the program. Naturally, the error occurs after the QML item has been reloaded and cppObject triggers a signal someSignal.
on windows: Qt 5.6.2 <- my program log error to debug console
on linux: Qt 5.9.2 <- my program crashes
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
File with error
import QtQuick 2.3
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.3
Easy solution is to :
istantiate the cppObject directly from QML to ensure that both objects are properly cleaned up by adding to your main.cpp file before you load the QML file
qmlRegisterType<Model>("com.your.app", 1, 0, "CppModel");
and then in your QML file include:
import com.your.app 1.0
....
CppModel {
id: cppObject
onSomeSignal: { cppObject.cppObjectFunction(); }
}
This way will make the extra connections and the extra qmlFunction() both unnecessary.
and maybe this too:
If cppObjectFunction() is a public slot from cppObject that is defined on the C++ side, you can try adding Q_INVOKABLE to the front of it in order to add it to the metaobject system.
Q_INVOKABLE void cppObjectFunction() { /* your code */ }

Use QQuickView or QQmlApplicationEngine to switch between pages from ApplicationWindow

I'd like to use an ApplicationWindow as a main file and be able to switch to other QML files from C++ with QQuickView::setSource(const QUrl & url). Basically it would do this:
start-up => loads main.qml (ApplicationWindow) => click on help button => C++ loads help.qml file => etc.
int main(int argc, char *argv[])
{
QApplication app{argc, argv};
CustomQQuickView view;
view.setSource(QUrl{"qrc:/main.qml"});
view->show();
return app.exec();
}
main.qml
ApplicationWindow
{
visible: true
width: 640
height: 480
Loader
{
anchors.fill: parent
id: mainPageLoader
}
Button
{
text: "Help"
onClicked: { mainPageLoader.source = "help.qml"}
}
}
(I am wondering if the Loader here is really necessary here)
However QQuickView only supports loading of root objects that derive from QQuickItem. Therefore it doesn't work with ApplicationWindow.
I'm thinking about using QQmlApplicationEngine instead of QQuickView but the usage seems different, this class being only equipped with QQmlApplicationEngine::load(const QUrl & url)
What would be the best course of action for my purpose? Do I really need an ApplicationWindow in my main.qml file?
Use QQmlApplicationEngine as you suggest, and with the main.qml as you say, but set a context property from C++ with the content page URL, e.g. help.qml - then bind to this context property in the Loader's source property.
This is the normal way of controlling QML from C++ - expose context properties or singleton objects with properties, drive them from C++, and have QML bindings respond to changes.