QML global access to C++ class - c++

My app has 3 level:
- Main Form
- Settings
- Device searching
From the main form I open others using:
var component = Qt.createComponent("qrc:/touch/content/SettingsMain.qml");
win = component.createObject(rootWindow2);
In main form I created object Network (it is C++ class)
Network{
id: net1
}
Object "net1" is accesible by other QML objects which were not invoked by above code creating component. Unfortunately, all QML objects created by using the code above do not see "net1".I need something like global object for all QML files. Any ideas?

You should use a singleton for that, it exists for that exact purpose:
// in main.cpp
qmlRegisterSingletonType(QUrl(QStringLiteral("qrc:/touch/content/SettingsMain.qml")), "Core", 1, 0, "Settings");
Then you can access that from every QML file by importing:
import Core 1.0
//.. and use it
Settings.someProperty
Settings.someFoo()
You would also have to add a pragma Singleton line in the beginning of SettingsMain.
You can also skip registering the singleton from C++ if you implement a qmldir file, but IMO registering in C++ is better when the singleton is an integral part of the application.
When using qml singletons, you don't need to create the instance yourself, it will be automatically created.
Your question is ambigious as to what you actually want to be "global", I assume settings is one thing you would like to be global.
You can also register C++ objects as singletons in QML, for example:
qmlRegisterSingletonType<Network>("Core", 1, 0, "Network", someFooReturningValidNetworkPtr);

Singleton is not the only way. QML provides lots of ways to get same results.
Another way is to pass the net1 id in as a property when calling Qt.createObject(). Example below:
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Item {
id: rootWindow2
property Item settingsMain
Network{
id: net1
}
Component.onCompleted: {
var component = Qt.createComponent("qrc:/touch/content/SettingsMain.qml");
settingsMain = component.createObject(rootWindow2, {"net1": net1});
}
}
}
SettingsMain.qml
import QtQuick 2.0
Item {
property Item net1
Component.onCompleted: {
console.log("SettingsMain.qml: can I see net1? %1".arg(net1 ? "yes" : "no"))
}
}

Related

How to make QFileSystemModel work with a ListView

I am unable to make the QFileSystemModel go along with the ListView. All the examples and documentation that I found would use QFileSystemModel with the TreeView (which works fine), but this is not what I need.
I do not want to use FolderListModel QML type either because I want to add extra roles to my model later and edit/delete files and folders from the application.
I used DelegateModel to be able to set an other root index. For some reason, my files are correctly displayed for a fraction of a second and then the view jumps to the root of my filesystem. I'm not sure what happens, maybe the indexes are invalidated somehow.
Below is a sample of my code:
main.cpp
QFileSystemModel *fsm = new QFileSystemModel(&engine);
fsm->setRootPath(QDir::homePath());
fsm->setResolveSymlinks(true);
engine.rootContext()->setContextProperty("displayFileSystemModel", fsm);
engine.rootContext()->setContextProperty("rootPathIndex", fsm->index(fsm->rootPath()));
fsview.qml
ListView {
width: 300
height: 400
model: DelegateModel {
model: displayFileSystemModel
rootIndex: rootPathIndex
delegate: Rectangle {
width: 200; height: 25
Text {
text: model.filePath
Component.onCompleted: {
console.log(text)
}
}
}
}
}
What am I doing wrong?
UPDATE
So, apparently QFileSystemModel uses a separate thread to populate itself and shoots a directoryLoaded signal once the the thread has finished to load the path.
I tried connecting my view to only set the root index once the path has been properly loaded by adding the following code to my listview
Connections {
target: displayFileSystemModel
function onDirectoryLoaded(path) { delegatemodel.rootIndex = rootPathIndex }
}
However, this does not solve the problem. A dirty workaround was to set a timer to try set the rootIndex after a period of time.
Timer {
interval: 100
running: true
onTriggered: delegatemodel.rootIndex = examPathIndex
}
And this "solves" the problem but of course is far from a satisfactory solution. Any idea?
UPDATE 2
Turns out every time the model is updated (and the directoryLoaded signal is triggered), the view resets. Even trying to reassign the rootIndex every time new data is loaded doesn't work.

QML TableView Model Silently Failing?

edit: added some clarification
Using QML 5.14
It seems the model attribute of TableView does not want to display a QList<int>, or any variation of int, be it qint8, qint32, etc. I can make it work with unsigned QList<uint>, however I need to keep the negative range of values in my application.
I've found that the information is making it to the qml layer, because when i call:
console.log("cfs.amounts is " + cfs.amounts)
console.log("model is " + model)
console.log("modellength is " + model.length)
I actually am getting the expected console output of:
qml: cfs.amounts is 11,12
qml: model is 11,12
qml: modellength is 2
I've ensured the TableView is functional by directly passing it data, i.e. model: [11, 22] and it displays correctly, i.e. it displays the indexes 0, 1. However I can't get it to display anything at all when I pass it the cfs.amounts, which is a QList<int> in c++. So according to the console.log, the model data is there, it is correct, it's getting passed from c++ to qml without issues, and the length is good -- the TableView is just failing to display it.
The only thing I can think of, is that the TableView is silently failing to display arrays of signed integers. However I may also be completely wrong, because I can't get a Repeater item to recognize it in its model, neither. I've searched but I can't find any bug reports on this subject. Does anyone have any suggestion on how to get the qml model to recognize the passed QList<int> ? This is all in QML 5.14.
cashflowschedule.h
#ifndef CASHFLOWSCHEDULE_H
#define CASHFLOWSCHEDULE_H
#include "QObject"
#include "QList"
class CashFlowSchedule : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<int> amounts READ amounts)
public:
CashFlowSchedule() {};
QList<int> amounts() { return {11,12}; }
};
#endif // CASHFLOWSCHEDULE_H
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import cpps 1.0
Window {
visible: true
width: 640
height: 480
CashFlowSchedule { id: cfs }
TableView {
anchors.fill: parent
model: cfs.amounts
delegate: Text { text: index }
Component.onCompleted: {
console.log("cfs.amounts is " + cfs.amounts)
console.log("model is " + model)
}
}
}
included in the main.cpp
#include "cashflowschedule.h"
...
qmlRegisterType<CashFlowSchedule>("cpps", 1, 0, "CashFlowSchedule");
...
QList<int> is not one of the official supported C++ types used for models (see the list here). A bug report exists to clarify the documentation on that point. A QVariantList is a good alternative to use.

QML Listview and c++: ReferenceError Model not defined

I want to display my C++ class as a ListView in QML. I have an empty vector, which will later be filled:
QList<QObject*> _myList;
and set the context
QQmlContext *ctxt = _eng->rootContext();
ctxt->setContextProperty("modelName",QVariant::fromValue(_myList));
In the qml file I have
ListView {
id: listView
model: modelName
delegate: myDelegate {}
}
But when starting the application I get the following error
qrc:/screen2.qml:252: ReferenceError: modelName is not defined
What am I doing wrong? Strangly, the error does not prevent the list from being correctly displayed once it is filled.
Call setContextProperty before loading your QML file.
When you load your QML file, the engine evaluates its bindings, since you haven't yet set the context property for modelName, it outputs a warning.
When you set it afterwards, this binding is reevaluated and that's why the list is eventually correctly displayed in your case.

Can not initialize UI with bound object in QML/C++

I put a RangeSlider which is from QtQuick.Controls 2.x into a Component. I've bound its min.value to my model object, which is a subclass of QObject and has been exposed to QML context.
I can access it from QML using the name: "settings". The control is supposed to read settings in the Loader's onLoaded:{...} to initialize itself. I have a Binding-Object outside of the Loader to write back any changes of the min.value.
But the control always set the settings' properties first, so I can not initialize it with settings's perperties.
Loader {
id: loader
sourceComponent: ctrl
onLoaded: {
loader.item.min = settings.min
}
}
Binding {
target: settings
property: "min"
value: loader.item.min.value
}
Component {
id: ctrl
Item {
property alias min: slider.first
RangeSlider {
id: slider
...
}
}
}
I want to assign loader.item.min.value (i.e. slider.first.value) the value of settings.min, but before this assignment happens, the settings.min is changed to slider.first.value. After the user set a new value, I want the settings.min to be set to slider.first.value.
Is there anybody know how to implement this correctly?
You can use the when-property of the the Binding-Object to deactivate the binding, until you have set the inital value. Set it initally to false, and change it to true in the first line of your onLoaded-handler
Also very interesting is the delayed-property. I can't guarantee you that this will work, but it might be an elegant solution. I have not tried it out yet.

Settings menu UI implementation

I'm trying to implement a BB10 settings menu, looking like the one in the Calendar app for example. The question here is, which components should I use? Using a ListView with an XML model looks great, but is incompatible with translation. Using a C++ model looks overkill for a simple menu with a couple of entries…
There's probably an established pattern somewhere, but I can't find it.
Screenshot of the Calendar app settings view
What you want is the expendable content property of the title bar:
I would create a QML object that you can re-use for each entry with properties for title and image.
So for example, something perhaps like this:
SettingEntry.qml
Container {
property alias title:title.Text
signal click()
TextView {
id: title
text: "[title goes here]"
}
gestureHandlers: [
TapHandler {
onTapped: {
click();
}
}
]
}
Then in your settings page you would use it like a normal object:
Page {
Container {
SettingEntry {
title: "General"
onClick: {
//open general page
}
}
SettingEntry {
title: "Invitation Settings"
}
}
}
The above is obviously very simplified, you would want to include an icon image, add translation code and add visual adjustments like filling the width and padding.
It should however give you a good idea of where to start.
I also included a gesturehandler and signal to show you how to handle events such as a click.