Send a signal between two object instances in different QML files - qt

I'm trying to send a signal from an object in one QML file to another object in a different QML file, but can't seem to find any good resources to use as a guide. Most of the examples I have come across show signals and slots being used to communicate between either two objects implemented in the same QML file (i.e. inside the same component), or in two different component files that come together inside a third QML file, which differs from my use case.
I need to send a string value from an object in a QML file (which represents a screen) to another object in a different QML file (representing yet another screen). The way the screens are linked currently is via StackView QML type in the main.qml file.
The closest I have seen the same problem described is here. The problem with the accepted answer in my case is the fact that the objects Rect1 and Rect2 are later defined in the same file. This means that they can be given an id and the signal and slot can be connected together, something I'm unable to do on my side.
Here's some code to demonstrate the problem.
main.qml:
ApplicationWindow {
id: app_container
width: 480
height: 600
visible: true
StackView {
id: screen_stack
anchors.fill: parent
initialItem: Screen1 {
}
}
}
Screen1:
Item {
id: screen_1
width: 480
height: 600
property var input
TextField {
id: user_input
width: parent.width
height: parent.height - 100
anchors.horizontalCenter: parent.horizontalCenter
placeholderText: qsTr("Enter your name")
onEditingFinsihed: {
input = user_input.text
}
}
Button {
width: parent.width
height: 100
anchors.top: user_input.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
console.log("Moving to Screen2")
screen_stack.push("qrc:/Screen2.qml")
}
}
}
Screen2:
Item {
id: screen_2
width: 480
height: 600
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
id: txt_rect
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: qsTr("")
}
}
}
What I would like to be able to do is send the user input from TextField user_input in Screen1 to Text txt_rect in Screen2. How can I achieve this?

You can push properties:
screen_stack.push("qrc:/Screen2.qml", {"inputText": user_input.text})
Screen2:
Item {
id: screen_2
width: 480
height: 600
property var inputText
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
id: txt_rect
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: screen_2.inputText
}
}
}

Related

QML StackView, how to navigate within the pushed item?

My app has a stackview that has two QML files.
And I need to navigate from one QML file to another from inside that QML file itself where I won't have access to stackview to push or pop.
How should I design this?
Main.qml
Rectangle
{
id: mainBkg
color: "white"
width: Screen.width
height: Screen.height
//Tickes every globalTimer.interval
property int globalTick: 0
Statusbar
{
width: Screen.width
height: Screen.height*0.2
id: statusBar
Rectangle
{
width: parent.width
height: parent.height*0.2
anchors.fill: parent
color: "green"
Text {
id: t
text: qsTr("QMLfile1")
}
}
}//end of statusbar
StackView {
id: stackView
anchors.top: statusBar.bottom
anchors.centerIn: parent
//What should be here? So that stackView
//will be available inside the loaded items.
initialItem:
}
}
Following are the two QML files:
QMLfile1.qml
Rectangle
{
width: parent.width
height: parent.height*0.2
anchors.fill: parent
color: "green"
Text {
id: t
text: qsTr("QMLfile1")
}
MouseArea:{
onClicked: //move to other QML file in stackview
}
}
I have another QML file like the above one.
You have tons of options.
One would be, to add a signal to the root-element of the QMLfile1.qml, and connect to it in the main.qml. The change will be performed there.
You can also add a property var callback to the root-element of the QMLfile1.qml where you inject the push-method of the StackView when instantiating it.
Finally you can just not shadow the id of the StackView and access it across file boundaries. I generally don't like that.

Closing qml dialog properly

I've been playing around with dialogs and there is something that bothers me.
I have the following code:
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: dlgTest.open()
}
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: dlgTest.close()
text: "Close"
}
}
}
}
When I open it the first time I add some text to the TextField and then I close it. However, If I open it again, the text will still be there. What I want is to "reset" the dialog to it's original state when I opened it the first time (with an empty TextField). It seems that calling the method "close" is exactly the same as changing visible to false.
Is there a way of doing this "reset"?
I have an other dialog with a lot of controls and it's annoying having to restore everything manually.
In your code you create the Dialog once, as a child of the ApplicationWindow.
To 'reset' it, you have two options:
Have a reset-function, that you call, and restores everything. You can use this to set it up in the first place as well
Create a new Object with everything set in place.
For the latter you can either use JavaScript Dynamic Object Creation or a Loader.
JavaScript Dynamic Object Creation:
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
var d = diaComp.createObject(null)
d.open()
}
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
dlgTest.destroy()
}
text: "Close"
}
}
}
}
However, as you destroyed the Object, the contents of your properties are lost, and you can't access them anymore. So you need to make sure, to copy them (not bind them) to some property that is not destroyed, first.
With the Loader you have the posibility to unload the Dialog right before you load it again, which basically resets it. But until you unloaded it, you can still access it's values, as you can see in the Buttons onClicked-handler.
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
console.log((dlgLoad.status === Loader.Ready ? dlgLoad.item.value : 'was not loaded yet'))
dlgLoad.active = false
dlgLoad.active = true
dlgLoad.item.open()
}
}
Loader {
id: dlgLoad
sourceComponent: diaComp
active: false
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
property alias value: tfText.text
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
}
text: "Close"
}
}
}
}
Of course, you could also copy the values from the Loader's item as well, and then unload it earlier, to possible free the memory.
But if the Dialog is frequently (most of the time) shown, it might be the wisest to avoid the creation and destruction of the objects, by reusing it and resetting it manually.

I get an error "Attempting to play invalid Qt resource" when I try to play a mediafile which is not in the qrc

Of course, if I include the particular mediafile in qrc file, then the following qml program seems to detect it.
I want the user to select the media file he wants through a file browser, and play it.
So, currently, I passed the file path in the form of a string as follows:
import QtQuick 2.0
import QtMultimedia 5.5
Rectangle
{
id: head
width: 500
height: 500
property int heightOfButtons: 50
property int widthOfButtons: 50
property string videoSource: "/home/*****/1.mp4"
Video
{
id: video
width : head.width
height : head.height
source: head.videoSource
MouseArea
{
anchors.fill: parent
onClicked: { video.play() }
}
onErrorStringChanged: console.log("errorstring: " + errorString)
focus: true
}
Grid
{
columns: 1; columnSpacing: 10
Rectangle
{
height: head.heightOfButtons; width: head.widthOfButtons; color: "green"
id: playStop
MouseArea {anchors.fill: parent; onClicked: video.playbackState == MediaPlayer.PlayingState ? video.pause() : video.play()}
Text {color: "white"; text: "Play/Pause"; anchors.right: parent.right; anchors.horizontalCenter: parent.horizontalCenter}
}
Rectangle
{
height: rightGrid.heightOfButtons; width: rightGrid.widthOfButtons; color: "red"
id: stop
MouseArea {anchors.fill: parent; onClicked: video.stop()}
Text {color: "white"; text: "Stop"; anchors.right: parent.right; anchors.horizontalCenter: parent.horizontalCenter;}
}
}
}
File exists, but the programs shows the error:
qml: errorstring: Attempting to play invalid Qt resource
How to play the file which the user selects dynamically?
Solution is to give the path of the mediafile as:
property string videoSource: "file:///home/***/1.mp4"
http://doc.qt.io/qt-5/qml-url.html

how to load a new screen in QML showing a new list based on previous user click input?

In my application i show a nested list, that shows groups and folders as its children. I have built the functions necesary to generate a new list in the backend in c++ based on which item is clicked by the user.
I allready have the necesary functionality to pass the list to qml through QProperty.
so my question is, how do i previous listviews and show new ones dynamically. Considering it should also be possible to click the button "back", which should load the previous page again showing the groups and the folders.
this is the code i have now, showing the groups and its children(folders)
import QtQuick 2.4
import QtQuick.Window 2.2
//import ListMode 1.0
Rectangle {
height: 250
width: 140
color: "pink"
//property var aNum: 0
Component {
id: folderDelegate
Item {
width: 140
height: col2.childrenRect.height
Column {
id: col2
anchors.left: parent.left
anchors.right: parent.right
Rectangle {
height: 20
width: parent.width
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: treemodel.getObject(model.ID + ":" + model.Name)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
id: name1
text: model.Name
}
}
}
}
}
ListView {
id: outer
model: myModel
delegate: groupsDelegate
anchors.fill: parent
}
Component {
id: groupsDelegate
Item {
width: 140
height: col.childrenRect.height
Column {
id: col
anchors.left: parent.left
anchors.right: parent.right
Text {
anchors.horizontalCenter: parent.horizontalCenter
id: t1
font.bold: true
font.underline: true
font.pointSize: 9
text: model.Name
}
ListView {
id: folderlist
model: treemodel.lists[treemodel.modIndex]
delegate: folderDelegate
contentHeight: contentItem.childrenRect.height
height: childrenRect.height
anchors.left: parent.left
anchors.right: parent.right
clip: true
}
}
}
}
}
i have been reading documentations and searching forums, but the information is pretty overwhelming. So a pointer in the right direction would be appreciated.
the main model is setup for each item to have its own unique ID. So when an item is clicked, i run a function that grabs and stores the item based on the ID + name that was clicked
MouseArea {
anchors.fill: parent
onClicked :{
treemodel.getObject(model.ID + ":" + model.Name)
stackView.push(Qt.resolvedUrl("content/ButtonPage.qml"))
}
}
next, based on the item that was clicked i have functions that fill different QList items which are loaded into the ButtonPage.qml.
the function in c++ that is invoked is:
Q_INVOKABLE void getObject(QString index) {
clickedItemID = index;
getClickedItem();
getFilesByFolder();
}
now, i am not sure if this is a good solution. But for me it works. Maybe it will work for someone else too.

QML ListModel: are multiple ListModels allowed to live on screen?

GridView {
id: gridv
model: ListModel {
id: modelone;
}
delegate: componentId
}
Rectangle {
id: whattheproblem
color: red
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: anotherComponentId
}
}
I can do gridv.model.append(element), it adds elements to displayed GridView.
But, I can't do listv.model.append(element), it doesn't draw anything (the component code is valid, though), but at the same time, modeltwo.count shows that element is added to model. Rectangle was added to check the layout (it's managed by RowLayout currently), and it seems to be working; other layout things (think anchor, x/y/z) do not help.
QT 5.3, QtQuick 2..
From my point of view, I can only think now, that modelone associates all the ListModel logics to GridView it's created from, so ListModel can't work with ListView anymore. Sounds illogical, but already spent two hours on this.
is there a necessity to create custom Model's, when dealing with multiple views?
I think the problem is with the delegate, since i tried your example with some modifications and it worked.
Following is the code:
Item {
width: 200
height: 200
GridView {
id: gridv
width: 200
height: 100
model: ListModel {
id: modelone;
}
delegate: Text { text: name }
}
Rectangle {
id: whattheproblem
anchors.top: gridv.bottom
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: Text { text: name }
}
}
Button {
anchors.left: parent.left
anchors.bottom: parent.bottom
text: "Add to Grid"
onClicked: gridv.model.append({name: "grid"})
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: "Add to List"
onClicked: listv.model.append({name: "list"})
}
}
I tried it with Qt 5.3.1

Resources