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 = ""
}
}
Related
There is a Q_PROPERTY in my registered C++ class:
// C++ Class
Q_PROPERTY(bool inProgress READ inProgress WRITE setInProgress NOTIFY inProgressChanged)
... based on which I intend to show a QML popup:
Popup {
id: popup
visible: cppClass.inProgress // Bind visibility to C++ Q_PROPERTY
}
But the pop doesn't show up. If change visible to true the popup is always shown of course.
Tried so far
Tried to use signal/slot connections to open/show the popup, but doesn't work:
Popup {
id: popup
visible: false
Connections {
target: cppClass
onInProgressChanged: {
if (cppClass.inProgress) {
console.log("open ...") // This text is logged correctly
popup.visible = Qt.binding(function(){return true}) // popup is NOT shown
popup.open() // popup is NOT opened
} else {
console.log("close ...")
popup.visible = Qt.binding(function(){return false})
popup.close()
}
}
}
}
What am I missing?
The code worked by triggering C++ signals further away from CPU-blocking operation:
Popup {
visible: cppClass.inProgress
}
QCoreApplication::processEvents();
cppClass->setInProgress(true);
QCoreApplication::processEvents();
// Dot not trigger signal immediately before heavy duty logic
//
// Do some less heavy statements
// before CPU-blocking operation
// to make sure QML UI engine receives signals
// and has time to show the GUI changes
// CPU-blocking operation
heavyDutyLogic.run();
QCoreApplication::processEvents();
cppClass->setInProgress(false);
QCoreApplication::processEvents();
I enriched a TextField (displaying a float) so that it can be changed by mouse wheel, all while being still editable by hand.
I found the quirk with forceActiveFocus and onClicked here (I wish I could just let all events pass down the widget stack like in Qt) and use onWheel to change the value (please let me know if this is not the best way to do this):
TextField{
text: cxxObject.floatAttribute.toString()
onEditingFinished: { cxxObject.floatAttribute=parseFloat(text); }
MouseArea{
anchors.fill: parent
propagateComposedEvent: true
onClicked: { parent.forceActiveFocus(); }
onWheel: { parent.text=parseFloat(parent.text)-.5*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
I would like to re-use this component instead of TextField in several places (without copy&paste), so I tried to declare the component like this:
Component{
id: wheeledFloatTextField
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
TextField{
text: parent.initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: parent.editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
and re-use:
Loader{
sourceComponent: wheeledFloatTextField
initValue: cxxObject.floatAttribute;
onEditingFinished: { cxxObject.floatAttribute=value; }
}
I am however getting (at the line where Component is used):
Component objects cannot declare new properties.
What is wrong? I was some posts (like How do you assign a QML Item to a component property in QML and then use that object inside the component? and https://developer.blackberry.com/native/documentation/dev/custom_components/index.html) from which I am gathering I might need to wrap the inside of Component (which is as-if it were a separate .qml file and does not define a scope) in something like Item or Container but I am not sure what to do. Any hint?
I would like to keep the definition inline first, later move to a separate file.
If you have the component declared in a separate file, you can (should) omit the top-level Component. For maximum reusability of components, it is reccomended to declare them in a separate file.
A Component can not have any properties declared. It is basically stopping the object creation in a prototypical state. That is useful if you want to configure the object, for later creation, for example lazy initialization (delegates).
If you have a property of type Component and you use the myProp: SomeType {...} syntax, it will automatically just create a component from that.
I think the best solution is to put your TextField in a seperate file, and add the properties to the root-node so it is customizable.
File1 (e.g. "CustomTextField.qml")
TextField{
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
You can then reuse the Component in all known ways like in a Loader:
Loader {
sourceComponent: CustomTextField { // Property type is component, so it automatically creates a Component instead of the full-blown object, until it is loaded.
initValue: 12
dWheel: 42
}
...
}
or without Loader
CustomTextField {
...
}
Of course you can keep it inline, but even then, you have to add the properties to the root-element inside the Component.
Component {
id: componentId // only thing you can set besides one Object in a Component
TextField{
id: componentRoot // You can't reference this id from outside the Component!!!
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
This has the down-side that you will always need a separate object to instantiate the Component, like a Loader which adds overhead and complicates communication in the file between the objects, since to address it, you will need to use: loaderId.item.property which might be expensive in lookup, you need to ensure that item is defined e.t.c.
I want to create an signal so I have an qml file with name TestCreateSignal.qml
with content like this:
import QtQuick 2.0
Rectangle
{
color: "red"
width: 344
height: 233
signal sendMessage
MouseArea
{
onClicked:
{
sendMessage();
}
}
}
and i want to use this signal in another qml file with name TestUseSignal.qml
import QtQuick 2.0
Rectangle
{
TestCreateSignal
{
sendMessage: //Error is at this line
{
console.log("message sendded");
}
}
}
but i got error like this when i want to user that
qrc:/TestUseSignal.qml:5 Cannot assign to non-existent property "sendMessage"
According to the docs:
An object can be notified through a signal handler whenever a
particular signal is emitted. A signal handler is declared with the
syntax on<Signal> where <Signal> is the name of the signal, with the
first letter capitalized. The signal handler must be declared within
the definition of the object that emits the signal, and the handler
should contain the block of JavaScript code to be executed when the
signal handler is invoked.
In your case:
TestCreateSignal
{
onSendMessage:
{
console.log("message sendded");
}
}
When I define a Component in QML I can define a alias property to expose properties of QtObjects that are otherwise hidden by the Components root.
Especially interesting is the default property alias to build powerful reusable and adaptable Components. But there is no generic way to have alter the nameresolution, which is bad for properties and signals.
Example: MyComp1.qml
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
}
}
main.qml
MyComp {
id: myObj
signal sig1()
onSig1: console.log('here')
Button {
id: button
onClicked: {
parent.sig()
parent.sig1()
}
}
}
Here button is now a child of childItem, but both signals: sig() and sig1() are defined in rootItem, which leads to an error. To the user of of my nicely designed Component it is not transparent. He would need to look in the code of the Component to realize, why the buttons parent is not myObj/rootItem but in fact of childItem. This might lead to bugs.
Therefore I am looking for a way to prevent it.
For signal sig() this would be possible by the modifying the MyComp and manually forward the signal from childItem to rootItem:
Item {
id: rootItem
default property alias content: childItem.data
signal sig()
Item {
id: childItem
signal sig()
onSig: rootItem.sig()
}
}
But is there a way to shortcut this, so that the user does not need to us myObj.sig1() but might use parent.sig1().
Same goes for properties, that are added uppon instantiation:
MyComp {
signal sig1()
property int num: 5
Button {
text: parent.num // <--- Error
}
}
It would be nice, if the properties and signals defined uppon instantiation would be available as parent.propertyName to the Objects added by a default-property aswell, I think.
Is there a way (to maybe hack this)?
There is no way to do this.
What you can do is stop assuming the outer scope in QML code is always the parent.
You could either access it directly by id:
MyComp {
id: myComp
signal sig1()
property int num: 5
Button {
text: myComp.num
onClicked: myComp.sig1()
}
}
Or if the object you want to access is the root item, you can access its properties implicitely:
MyComp {
signal sig1()
property int num: 5
Button {
text: num
onClicked: sig1()
}
}
One good practice (albeit maybe too extreme for most case) that can be deduced from this situation, is to only use parent when you actually want to access the parent Item in the visual hierarchy, for things like anchors or width and height.
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.