QML: Communicating with great grandparents through signals - qt

I'm currently in the process of learning Qt for some cross platform development, and I'm trying to do everything using QML. I know there are lots of ways to solve my problem using C++, but I want to stay true to the model and use QML.
Here it is: If I am using a loader to display qml files as seen in the following code, how do I communicate with main.qml from secondPage.qml?
I assumed this would be through signals, but upon further reading it seems all actions on a signal are within the class that sent it ( using the connected method). Here is my resource on this: http://qt-project.org/doc/qt-4.8/qmlevents.html#connecting-signals-to-methods-and-signals
Alternatively, this may be the wrong design for a QML application. I'm trying to break away from using a single source file before things get too out of hand...
main.qml:
Rectangle {
id: background
...
Item{
id: item1
Loader {
....
id:pageLoader;
source : "secondPage.qml"
focus:true;
}
}

You can declare some signal in SecondPage.qml and connect those to Main.qml's function.
Like below, suppose you have secondPageSignal() defined in secondPage.qml and you want to call doSomething() function in Main.qml on secondPage's signal, you can connect that in onLoaded handler of Loader.
Rectangle {
id: background
...
function doSomething() {
}
Item{
id: item1
Loader {
....
id: pageLoader;
source : "secondPage.qml"
focus:true;
onLoaded:{
pageLoader.item.secondPageSignal.connect(background.doSomething);
}
}
}
}

In secondPage.qml you have access to your background element directly because children have access to any of its parents in the hierarchy.
Another possibility is to declare a signal in your secondPage component, and then to connect the signal from this component to a signal in your great-grandparent in the onLoaded handler of your Loader. It makes it easier and cleaner if you want your component to be re-usable multiple times in your app without assu,ing what is parent is.

Related

How to inject data into QT item shown with loader

In my application I have a global system that handles navigation between "screens". In QML I can simply call something like:
appNavigation.show("MyScreen.qml", NavigationType.FADE)
this calls a C++ part of the code which handles the current stack of screens and uses signals to report back to QML to do the actual animation. At the end in QML some Loader will load the input qml ("MyScreen.qml" in this case) and show it as defined.
My issue here is how to inject data into newly loaded screen. Essentially I would like to do something like the following:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE)
screen.someData = "some data here"
}
but is this possible? Could I somehow return the screen that is loaded by the loader?
I am guessing not so I would satisfy with sending the data with the navigation itself like:
function showMyScreen() {
MyScreen screen = appNavigation.show("MyScreen.qml", NavigationType.FADE, "some data here")
}
I could forward the data to the point where I set source to the loader but still what then? How or where would that specific screen that is going to be loaded get the data. To reduce is this is what I get:
function setNewItemWithData(newItem, data) {
loader.source = newItem
loader.concreteScreen.data = data // Not really doable
}
again I assume this is not doable and I need to forward the data down to loader and use onLoaded event. So what I would do is something like:
onLoaded: {
myLoadedScreen.data = data
}
I assume something like this is possible but how? What am I missing here, how do I get myLoadedScreen and how to access its properties?
What I am currently doing now is dumping the data in C++ part and then collecting it in the loaded QML. So like the following:
appNavigation.injectedData = "some data here"
and then in the newly loaded item:
property data = appNavigation.injectedData
It works but this seems like extremely poor coding. Any of the alternatives would be helpful.
Thank you for your patience.
Since the request for MCVE was made:
This is a general problem and I expect it to have multiple solutions. I would be looking forward to any of them. However the minimal example to produce this is creating a new project and adding a loader with another qml to which some property should be changed:
main:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Loader {
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
// TODO: make screen green (loadedScreen.color = "green")
}
}
MyScreen:
import QtQuick 2.0
Rectangle {
color: "red"
}
Current result is seeing a red rectangle and desired result is to see a green one. The point being that the main screen needs to tell what color the loaded screen needs to use.
You have to use the item property of the Loader to get the object loaded:
Loader {
id: loader
anchors.fill: parent; anchors.margins: 20
source: "MyScreen.qml"
onLoaded: loader.item.color = "green"
}
To do that, you might as well use Component (If you use it when reacting to an event)
Component {
id: myScreenComponent
MyScreen {
anchors.fill: parent
}
}
function showMyScreen() {
myScreenComponent.createObject(this, {"color: "green"});
}
Alternatively, given your first code, I would recommend you to use StackView.
The push method seems to be similar to your appNavigation.show one.
You can give it an url, some properties, and a transition type (that you can customize).

Qt/QML: Trying to create a log file view with TextEdit, result is really slow

I am working on an Android App. I need to display a console like type of log, which will be written to by the C++ back end. I have tried doing this combining a TextEdit and ScrollView, but the result is really slow. As soon as my log goes beyond ~50 lines, adding a few lines slows down (locks) the interface for a few seconds.
Trimming down the source code, this is the log view section:
property int logMaxLines: 50
ScrollView {
id: logScrollView
anchors.fill: parent
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextEdit {
id: logTextEdit
anchors.fill: parent
readOnly: true
color: "darkgreen"
property int linesTrimmed: 0
}
}
Connections{
target: gate
onNewMessageLineAdded :
{
logTextEdit.append(gate.newMessageLine)
if (logTextEdit.lineCount > logMaxLines) {
while (logTextEdit.lineCount >= logMaxLines) {
logTextEdit.text = logTextEdit.text.slice(logTextEdit.text.indexOf('\n')+2)
logTextEdit.linesTrimmed++
}
logTextEdit.insert(0, "[... trimmed " + logTextEdit.linesTrimmed + " lines ...]\n")
}
}
}
I picked a ScrollView as I'd like to have the vertical scroll bar. Lines are added one at a time by the C++ code, when it emits the newMessageLineAdded signal. This is coming from a class which includes this Q_PROPERTY, used to pass the new line content:
Q_PROPERTY(QString newMessageLine READ newMessageLine NOTIFY newMessageLineAdded)
the signal is declared as:
void newMessageLineAdded();
I have added the small bit of java to trim the log when it grows too long, as the issue is there even when this trimming code is not present.
Am I doing something very clunky here? Should I use another type of object to replace the TextEdit, knowing that it is not used at all to edit text, but only as a display?
Thanks.
I recommend you to use ListView instead of TextEdit. And use QStringListModel as model declared in C++ code and added to QML as context property. Read Embedding C++ Objects into QML with Context Properties. It is recommended for better perfomance to have most of logic in C++ code.

Qt 5 QML app with lots of Windows or complex UIs

In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loaders to get around this, but as you can see it is not a very nice solution.
Edit 1 - Why is Loader suboptimal?
Ok, to show you why Loader is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask to be loaded and have all its declarative relationships activated when componentLoaded is set to true, and then have the relationships disconnected and unload the component when componentLoaded is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView.
Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):
// Message dialog box component.
Component {
id: messageBoxFactory
MessageDialog {
}
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
if (typeof modal === 'undefined')
modal = true;
// mainWindow is the parent. We can also specify initial property values.
var messageDialog = messageBoxFactory.createObject(mainWindow, {
text: text,
title: title,
visible: true,
modality: modal ? Qt.ApplicationModal : Qt.NonModal
} );
messageDialog.accepted.connect(messageDialog.destroy);
messageDialog.rejected.connect(messageDialog.destroy);
}
I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.
And do you think your app can take more than even 512 MB ram? I doubt it.
You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.
If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.
Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk
I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

Does qml supports object oriented programming

I'm using QML for my project, I want to know if am instantiating a file in another file, is it like instantiating object for a c++ class?
File.qml
Rectangle {
id: idRect1
.
.
}
File2.qml
Rectangle {
id: idRect2
File1 {
id:idFile1
.
.
}
}
In File2.qml i have initialized File1, does it means i have created an object of type File1? Please share some knowledge(links) on how all this mechanism works. Thanks in Advance
In QML when creating a file with first letter uppercase, you're creating a component. Components are implemented using OOP aggregation (not subclassing). That mean if I write
// MyButton.qml
import QtQuick 2.0;
Rectangle {
id: base;
width: 120;
height: 40;
color: "lightgray";
Text {
text: "foobar";
anchors.centerIn: parent;
}
}
... I haven't subclassed Rectangle, I just created a component that contains a Rectangle as root object, and configurates it in a certain way, and adds a Text object in it.
As soon as a component is created, it can be instanciated by simply writing :
MyComponent { id: myNewInstance; }
Because that's a way it works in QML.
The component name is a kind of class (but not in the C++ or JS way to define it) and it can also be used as a type for a property :
property MyComponent theComponent : myNewInstance;
Then it can hold the ID of an object created with the given component, acting somewhat like a C/C++ pointer : the property holds a link to the actual object.
But because of the way QML was designed, even if it's more aggregating than subclassing, a property of the type of the root object of a custom component can also hold ID of a derived component, in my case :
property Rectangle theComponent : myNewInstance;
Will work, but if I try to put an ID of an Image or Text or something else, QML engine will throw incompatible types error.
I hope it helps you.

asynchronous (kind of) animation in qml

let's say i have the following QML Components:
Foo.qml
import Qt 4.7
Rectangle {
Repeater {
model: myModel
delegate: Bar {
barProp: elemProp
}
}
}
Bar.qml
import Qt 4.7
Rectangle {
property string barProp: ""
Text {
text: barProp
NumberAnimation on x {
from: 0; to: 100
duration: 1000
loops: Animation.Infinite
}
}
}
I maintain myModel from C++, it has the following Q_PROPERTY declaration:
Q_PROPERTY (QDeclarativeListProperty <Bar> myModel READ myModel
NOTIFY myModelChanged)
Now, my problem is that every time I add a new element to the underlying QList, the animation specified in Bar resets, so in practice, the elements always completely overlap. What I want is that the element animations are not synchronous, and each can continue seamlessly regardless of the rest. Is this possible to do?
Cheers
You should use a QAbstractItemModel (QStandardItemModel may be easiest) rather than a QList. QAbstractItemModel notifies the view when new items are inserted/removed/moved and the view reacts appropriately by modifying its content. In contrast, the view knows nothing about the changes made to a QList; only that something has changed. This means that the list has no choice but to destroy and recreate all delegates.

Resources