I cannot find a way to communicate from one qml file to the other one. I know there are many ways to send signals from qml to C++ slots and reverse, but all my research about signals between two different qml files failed.
So I would be glad if someone can tell me, how I have to solve this problem.
First of all a little abstracted example to understand the problem in a better way...
The first QML in basics looks like that:
//MyQML1.qml
Rectangle
{
id: idMyRec1
signal mySignalFromQML1()
Button
{
id: idMyButton1
onClicked:
{
idMyRec1.mySignalFromQML1(); //to send the signal
}
}
}
The 2nd one looks like this:
//MyQML2.qml
Rectangle
{
id: idMyRec2
Text{
id: idMyText2
text: "Hello World!"
onMySignalFromQML1: //to receive the signal from the other qml
{
idMyText2.text = "Good Bye World!";
}
}
}
So this button should change the text in my 2nd QML to "Good Bye World!" when clicked...but this doesn't work...are there any other ways to send signals from QML to another QML?! Or am I doing something wrong?
You don't communicate between qml files, the QML file is just a prototype, you communicate between the object instances.
// Rect1.qml
Rectangle {
id: rect1
signal mySignal
Button {
onClicked: rect1.mySignal()
}
}
// Rect2.qml
Rectangle { // Rect1.qml
property alias text: txt.text
Text {
id: txt
}
}
And then you create the objects:
Rect1 {
onMySignal: r2.text = "Goodbye world!"
}
Rect2 {
id: r2
}
There are other ways to make a connection, however, connections happen between object instances, not qml files. The objects don't have to be in the same qml file too, but initially for simple things they will rarely be in different files.
For me this works with Connections and signal in one qml file as follow:
import QtQuick 2.4
import QtQuick.Controls 1.2
Item {
id: item
width: 200
height: 200
signal sendMessage(string msg, int compId)
Button {
text: "SendMessage"
onClicked: sendMessage("hello",1)
}
Item {
id: item1
Connections {
target: item
onSendMessage: if(compId==1) { console.log("Hello by Comp 1") }
}
}
Item {
id: item2
Connections {
target: item
onSendMessage: if(compId==2) { console.log("Hello by Comp 2") }
}
}
}
Of course the items with the Connections can be also in different files.
Related
I have 3 nested ListViews.The first is the month, second is day and third is hour of day.All of them constructs a calendar.There is a loader that loads a window when I click in the hour ListView, and set text in a Label.This text can be displayed in the hour view and can be deleted or edited through the above window .Unfortunately the text can not be saved because when I scroll the ListViews, delegates changing and not keeping the context of the label(That is normal I suppose).The objective is to be able to save those texts in the label(store the data) and restore them when the application is closed and re-opened.
Below is a generic code sample for 3 ListViews:
ApplicationWindow{
id:appwindow
............
Item{
id:dayView
...........
ListView{
id:monthofdayCalendar
orientation:Qt.Horizontal
model:12
delegate: Item{
ListView{
id:dayCalendar
orientation: Qt.Horizontal
model:32
delegate: Item{
...............
ListView{
id:daylistView
orientation: Qt.Vertical
model:24
delegate:Item{
id:hourItem
property string hourTime:hourweeklistviewLabel
property string notetaking:notesLabe
.............
MouseArea{
anchors.fill:parent
onClicked:{
windowLoader.active =true
daylistView.currentIndex=index
}
}
Rectangle{}
Label{
id:hourweeklistviewLabel
}
Label{
id:notesLabel
anchors.left:hourweeklistviewLabel.right
anchors.leftMargin: 30
text:""
}//Label
}//delegate:Item
}//ListView
} //delegate:Item
}//ListView
}//delegate:Item
}//Listview
}//Item
Below is the code of loader:
Loader {
id:windowLoader
focus: true
active:false
sourceComponent: Window{
id:inputWin
title:"Enter Note"
width:500
height:300
visible:true
onClosing:{
windowLoader.active=false
monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentIndex = calendarMonth.selectedDate.getDate() === new Date().getDate()
&& calendarMonth.selectedDate.getDay() === new Date().getDay()
&& calendarMonth.selectedDate.getMonth() === new Date().getMonth()?getHour():12
}
TextField {
id:title
x:50
y:20
placeholderText :'Enter Note'
text:monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text
}
TextField{
id:timeDate
anchors.horizontalCenter: title.horizontalCenter
anchors.top:title.bottom
anchors.topMargin:10
placeholderText : calendarMonth.selectedDate.getDate() +"-"
+ (calendarMonth.selectedDate.getMonth()+1)+"-"
+ calendarMonth.selectedDate.getFullYear() + " "
+ monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.hourTime.text
}
Button {
id: button
text: qsTr("Add Note")
anchors.centerIn:parent
onClicked: {
if (title.text !==""){monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text= title.text}
else{}
}
}
}
}
The big question is how to save (store) the data of notesLabel.text and be able to display it and restore it every time I close and re-open the application.
As you can see the model for each ListView is not a ListModel so I think I can not use those models to save the data if I am right.If I am wrong please advise.
Anyway your help will be appreciateed.
EDIT
I've changed the integer models with ListModel dynamically created.The code of the ListModels is below:
ListModel{
id:hourlistModel
Component.onCompleted:{
for (var i = 0; i <25; i++){
append(createListElement())
}
}
property int h:0
function createListElement(){
return {
hour : h++
}
}
}
ListModel{
id:daylistModel
Component.onCompleted:{
for (var j=0; j <= 31; j++){
append(createListElement())
}
}
property int dD:0
function createListElement(){
return {
day : dD++
}
}
}
ListModel{
id:monthlistModel
Component.onCompleted:{
for (var k=0; k <=11; k++){
append(createListElement())
}
}
property int mN:0
function createListElement(){
return {
monthName : mN++
}
}
}
Can I store the data from Label notesLabel, now I've changed the models of ListViews with ListModels?
Thanks in advance.
I would create an exposed C++ class.
Using the exposed C++ class you have a range of options to pass data from the front/backend
Q_Property/Member
Q_Invokable
Signal/Slots
Given that you are using strictly strings, I would use an exposed series of QStrings or a QStringList.
QString
QStringList
To tackle to file read/write use your now exposed C++ class. You can either stick to file I/O via standard c++ or QFile system.
Constructor - Read the .txt file and save the data to your property data.
Exchange data as needed updating either the QML or C++ property member
Deconstructor - save the property member data back to file.
Brief example code:
someQML.qml
Import MyExposedClass 1.0
Item {
MyExposedClass {
id: myExposedClassID
text: myExposedClassID
}
Text{
id: yearTextID
text: myExposedClassID.year
}
Text{
id: monthTextID
text: myExposedClassID.month
}
Text{
id: dayTextID
text: myExposedClassID.day
}
Button {
id: myButtonID
onButtonPressed {
var finalStr = yearTextID + monthTextID + dayTextID
// If you used a QMember of qstring lets say
myExposedClassID.saveFile = finalStr
// If you used a QInvokable
myExposedClassID.saveFile_INVOK(finalStr)
}
}
}
myClass.h
class myClass : public QObject {
Q_OBJECT
// Serial Dev
Q_PROPERTY(QString day READ getDay WRITE setDay NOTIFY dayChanged)
Q_PROPERTY(QString month READ ... WRITE ... NOTIFY ...)
Q_PROPERTY(QString year READ ... WRITE ... NOTIFY ...)
...
// On construction, read the text file and update the qproperty variables
//implement getters and setters for qproperties
// On deconstruction, write the properties to file
}
If you have issues with MVC and QStrings/QStringlist. You may have to look into QVariants or QAbstracts.
I find QML to be a risky hole to dive into. Its easy to add more and more functionality to a QML file. But if you try and redesign it or change up some logic, it can very quickly ruin the QML. Splitting the QML into a QML & C++ is a nice way to achieve modularity and control.
I need to perform some actions in let's say main.qml according to button press in button.qml. Button inside of the button QML is inside of some custom object. Let's give it a name customObject. So customObject in button.qml looks like this:
customObject {
id:trialObject
signal backPagePressed()
Button {
id:button1
MultitouchArea {
onPressed:
{
trialObject.backPagePressed()
}
Now when I press the button, it emits backPagePressed(). My question is: How can I make a slot for this signal in main QML? I'm familiar to signal and slot mechanism in C++, but that does not work for QML. I made something like this in main.qml:
Loader
{
id:pageLoader
onBackPagePressed:
{
pageLoader.source =""
}
}
That part needs to delete the Loader's source so that it will go back to page before. However, I'm not sure about onBackPagePressed: ... How can I connect my signal backPagePressed, to the related part in my loader?
You should use a Connections object (documented here) together with the item property of the Loader:
Loader {
id: pageLoader
source: "CustomObject.qml"
}
Connections {
target: pageLoader.item
//Qt < 5.15
onBackPagePressed: pageLoader.source = ""
//Qt >= 5.15
function onBackPagePressed()
{
pageLoader.source = ""
}
}
main.qml:
import QtQuick 2.11
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
x: 200
y: 200
visible: true
Component {
id: firstViewComponent
FirstView {
id: firstView
}
}
StackView {
id: stackView
anchors.fill: parent
Component.onCompleted: push(firstViewComponent)
}
Timer {
interval: 1000
running: true
onTriggered: stackView.pop()
}
}
FirstView.qml:
Rectangle {
id: view
StackView.onDeactivating: console.log('view: view is deactivating')
ListModel {
id: aModel
ListElement {
name: 'Element 0'
}
ListElement {
name: 'Element 1'
}
}
ListView {
id: listView
model: aModel
delegate: Rectangle {
id: listViewDelegate
Connections {
target: view.StackView // <---- DOESN'T WORK
onDeactivating: console.log('delegate ' + index + ': needs to do some housekeeping now')
}
}
}
}
I have a view that is instantiated by a StackView in main.qml. The StackView attaches a signal StackView.onDeactivating to the view. Is there any way to attach to the signal from an object other than the one the signal is attached to? I need to do some cleanup in listViewDelegate when the view is popped.
I could have view emit its own signal, and have the delegate respond to that signal. But what I'm wondering is if there is a way to connect to the attached signal: StackView.onDeactivating from a different object (listViewDelegate).
Yes and no. The Qt documentation partially addresses this: A Note About Accessing Attached Properties and Signal Handlers
It is not possible to directly access the property from a child. Attached properties need to be explicitly read by the class providing them. For your example, the parent class (StackView) simply searches for all attached properties it does provide in the child item (FirstView) as soon as it gets added, and handles all found properties/signals etc. by connecting them internally to whatever logic provides them.
However, nothing prevents you from gettings the attached property from the parent item, as long as you refer to it by id:
sampleProp: view.StackView.someProperty
The thing is: This indirect access only works this way for properties and nor for signals, as you can't simply refer to the attached object via view.StackView - so sadly, you are stuck with forwarding the signal to the child elements indirectly by creating a second signal in the root item and emitting it when the attached signal gets emitted.
I have an application which needs the screen to be switched between multiple available screens. I am checking if this is possible with loader in qml.
The issue i am facing is connecting signals from loaded item.
I use an application example in qt documentation and found CreateConnection in application qml cannot have if condition.
I also tried to make it signal slot connection in a function and call in on source change of loader, but that too did not work.
Application.qml
import QtQuick 2.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
// here i tried using if (myLoader.item == "qrc:MyItemOne.qml") , but can't use if
onChangeToSecond: {
myLoader.source = "MyItemTwo.qml"
}
onChangeToFirst: {
myLoader.source = "MyItemOne.qml"
}
}
}
MyItemOne.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToSecond()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToSecond()
}
}
MyItemTwo.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToFirst()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToFirst()
}
}
Someone knows any way to use loader for this, or loader should not be used for this?
Your code works fine if I use MyItemOne.qml as the initial value for the myLoader.source (Qt5.6.0). However, it will print out a warning:
QML Connections: Cannot assign to non-existent property "onChangeToFirst"
which happens because MyItemOne does not define the changeToFirst signal. The ignoreUnknownSignals property of Connections element can be used to suppress the warning, or both screens should define the same set of signals.
Loader can be used if it does not matter that the previous view is always fully unloaded when switching.
In my application I'm displaying a list of audio files and the user can drag an external file to add it to the list. I want to be able to refuse the drag if no file in the list is supported by my application.
The issue is that when I call drag.accepted = false; in onEntered of my DropArea then it becomes completely unresponsive to any other event.
Here is some sample code showing the issue. If you drag an MP3 in the window you see that it works. Then if you drag any other file it won't work, as expected. But then dragging an MP3 file back will not work either.
import QtQuick 2.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
DropArea {
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
var validFile = false;
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
break;
}
}
if(!validFile) {
console.log("No valid files, refusing drag event");
drag.accepted = false;
return false;
}
}
onExited: {
console.log("[Droparea] entered");
}
onDropped: {
console.log("[Droparea] dropped");
}
// Only MP3s
function validateFileExtension(filePath) {
var extension = filePath.split('.').pop();
var valid = false;
if(extension == "mp3") {
valid = true;
}
return valid;
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
Is there a bug in the DropArea or did I misunderstood something? I know I can filter the files in the onDropped but then you loose the visual feedback you get on OSX when dragging file on an area that does not accept them.
It has been a known bug for a long time. A patch has been submitted and after been stalled for several months is now merged into 5.6 branch.
Anyone who wants to use this functionality MUST upgrade to Qt 5.6 or MANULLY integrate the available patch into his/her Qt version.
QQuickDropAreaPrivate, contained in DropArea, updates the containsDrag flag to true when a dragEnterEvent occurs, emitting the entered signal. It updates containsDrag to false when adragLeaveEvent occurs, emitting an exited signal. However, when the drag event is not accepted dragLeaveEvent is never called, leaving the private object in a incosistent state. Each subsequent dragEnterEvent is discarded since containsDrag is still true, i.e. the previous drag event is still considered active and the entered is no more emitted.
Since the issue is related to an interaction between private APIs and usage of the public APIs, the problem does not affect filtering using keys. Unfortunately, this approach does not seem to fit for the presented use case.
A quite partial workaround is to use a MouseArea along with the DropArea. The latter disables itself when a rejection occurs while the former enables back the DropArea for the next drop. This workaround covers the common case in which a wrong item is dropped inside the DropArea, which is the most common and intuitive for an end user. Releasing the wrong item outside the DropArea invalidate the mechanism (until the next drop).
Here's the code:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: !drop.enabled
onContainsMouseChanged: drop.enabled = true
}
DropArea {
id: drop
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
for(var i = 0; i < drag.urls.length; i++)
if(validateFileExtension(drag.urls[i]))
return
console.log("No valid files, refusing drag event")
drag.accept()
drop.enabled = false
}
onExited: console.log("[Droparea] exited")
onDropped: console.log("[Droparea] dropped")
// Only MP3s
function validateFileExtension(filePath) {
return filePath.split('.').pop() == "mp3"
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
you never put accepteed = true
just add drag.accepted = true after you set the valid as valid
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
drag.accepted = true;
break;
}
}