QT QML Accessing Attached Property From Another QML Object - qt

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.

Related

How to open new page from page which is open from stackview in Qt

I have StackView in main.qml.I pushed menu.qml file from main.qml using stackview. I'm trying to access stackview in menu.qml file to open new item. Is there a way with which we can push component/items with properties using stackview? My components are basically.qml files for different views
ApplicationWindow {
id: settingsWindow
StackView {
id: stack
initialItem: view
Component {
id: view
MouseArea {
onClicked: stack.push(view)
}
}
}
Button{
id: button1
onClicked: {
stack.pop(StackView.Immediate)
stack.push (Qt.resolvedUrl("menu.qml"))
}
}
}
menu.qml
Item {
Button{
id: button1
onclicked : { stack.push (Qt.resolvedUrl("new.qml")) }
}
}
Assuming you mean you want to access the StackView object from withing pages you pushed on it.
StackView has an attached property, which lets you obtain a reference to the view that owns the page.
Long story short, in Menu.qml you can do:
Item {
id: root
Button {
id: button1
onClicked: { root.StackView.view.push(Qt.resolvedUrl("new.qml")) }
}
}
https://doc.qt.io/qt-5/qml-qtquick-controls2-stackview.html#view-attached-prop
Finally ended up creating each page in StackView as a component property and then pushing each of them using a signal. Added the signal to every page and connected it to main page where stackview existed . This answer helped
https://stackoverflow.com/a/45354861/11288640

QML - How to get children in an item?

I had a problem with the item in QML. I wanna get children of an item but it seems working in the first element.
The detail code is below:
I have a gridview with a list custom component AAA_Styles
GridView{
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator{}
cellWidth: 200
cellHeight: 300
model: ListModel{}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles{
id: deviceControl
objectName: "deviceControl"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
name: Names
subname: Subnames
}
}
My custom AAA_RTS is a QML component have some functions such as:
- function_a()
- function_b()
I added 2 items into model using
grdViewDeviceControl.model.append()
I ensure the model have data that I added because It appeared in my design and the count of gridview is 2 elements
console.log(grdViewDeviceControl.count) //the result is 2
After that, I tried to get each element to access functions that they are available using a method in signal onRelease of a button:
onReleased: {
console.log("number of item: " + grdViewDeviceControl.count)
var myRTS = grdViewDeviceControl.contentItem.children[0]
console.log(myRTS)
console.log(myRTS.children[0])
myRTS = grdViewDeviceControl.contentItem.children[1]
console.log(myRTS)
console.log(myRTS.children[1])
}
The result on console:
qml: number of item: 2
qml: QQuickItem(0x9828f60)
qml: AAA_Styles_QMLTYPE_0_QML_112(0x9829070, "deviceControl")
qml: QQuickItem(0x5554140)
qml: undefined
With the first element grdViewDeviceControl.contentItem.children[0], I access function_a or function_b successful but when I using the second the error appeared
TypeError: Cannot call method 'function_a' of undefined
So can anyone tell me why I wrong and how to fix it?
Many thanks for any help!
Do not try to access directly to the child items. Use delegate IDs, signals and slots instead:
Give a "delegate ID" to all your delegates through the model of your GridView.
In your GridView, add signals that will be used to broadcast to all the delegates the following things:
The "delegate ID" of the delegate that you want it to execute the function.
The arguments for the AAA_Styles function.
In your delegate, add one slot per AAA_Styles function. Each slot will execute the AAA_Styles function only if the broadcasted delegate ID is the delegate's: if (broadcastedID === delegateID) { function_ab() }.
When you want to execute function_a() or function_b() in a delegate, broadcast the delegate ID and the function arguments through the corresponding GridView signal (in onReleased, for example).
The following piece of code sums up what I have just described to you. If it does not work put the delegate in a separated QML file. This should work for good:
// Your grid
GridView {
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator {}
cellWidth: 200
cellHeight: 300
model: ListModel {
/*
Example of list element:
ListElement { m_uuid: "{element-uuid}" }
*/
}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles {
id: deviceControl
objectName: "deviceControl"
anchors.centerIn: parent
name: Names
subname: Subnames
}
// The delegate ID
property string delegate_id: m_uuid
// Broadcast receivers
function delfunc_a(uuid, argA0) {
if (uuid === this.delegate_id) {
deviceControl.function_a(argA0)
}
}
function delfunc_b(uuid, argB0, argB1) {
if (uuid === this.delegate_id) {
deviceControl.function_b(argB0, argB1)
}
}
// Connecting broadcasters to receivers
Component.onCompleted: {
grdViewDeviceControl.exec_a.connect(this.delfunc_a)
grdViewDeviceControl.exec_b.connect(this.delfunc_b)
}
Component.onDestruction: {
grdViewDeviceControl.exec_a.disconnect(this.delfunc_a)
grdViewDeviceControl.exec_b.disconnect(this.delfunc_b)
}
}
// Your broadcasters
signal exec_a(string uuid, int argA0)
signal exec_b(string uuid, bool argB0, string argB1)
}
// Somewhere else in your code:
onReleased: {
/*
* Only the delegate whose ID is "{a-given-uuid}"
* will execute deviceControl.function_a(3):
*/
grdViewDeviceControl.exec_a("{a-given-uuid}", 3)
/*
* Only the delegate whose ID is "{another-given-uuid}"
* will execute deviceControl.function_b(true, "U got style!"):
*/
grdViewDeviceControl.exec_b("{another-given-uuid}", true, "U got style!")
}

Re-using Component of TextField responding to wheel events, with signal handling

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.

Qt QML Focus an Item (TextField) when showing a StackView Page

I want to enable TextField focus when QML file is loaded. But, it is not working. After loading TestUi.qml file I put some button and its onClick() method I did _recipientView.focus = true_, it works fine. The problem is that default focus is not enabled when view is loaded first time.
TestUi.qml
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.0
Page {
function init() {
recipientView.focus = true;
}
TextField {
id: recipientView
Layout.fillWidth: true
font.pixelSize: 18
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhEmailCharactersOnly
focus: true
placeholderText: "Email"
}
}
main.qml
onComposeBtnClicked: {
rootStackView.push(test)
test.init()
}
TestUi {
id: test
visible: false
}
Edit
The Page component already acts as a FocusScope, so only forcing the active focus is necessary. Thanks for the comments.
StackView {
id: stackView
initialItem: firstPage
// Ensures the focus changes to your page whenever
// you show a different page
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
TextField {
// Explicitly set the focus where needed
focus: true
}
}
Original answer
This is simply because you are pushing TestUi into your stack with rootStackView.push(test). When you do so, the focus is reset. This is typically handled with a QFocusScope, which role is to remember the focused Item, and give the focus back to it when the QFocusScope regains focus.
In your case, adding a QFocusScope to your base page would enable restoring the focus correctly when the page is shown:
StackView {
id: stackView
initialItem: firstPage
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
onFocusChanged: {
scope.focus = true
}
FocusScope {
id: scope
TextField {
focus: true
// ...
}
}
}
You can then use your page handler onVisibleChanged if you want to reset to focus when the user comes back to it (after a pop for instance), instead of memorizing it where the focus is. But in that case the FocusScope might be overkill.
For information, you can also use the StackView property initialItem in order to set the first page.
It is also kind of unrelated, but prefer importing the most recent version of the QtQuick components available. QtQuick version will be 2.12 for Qt 5.12. A bit less trivial for QtQtcuik.Controls version, but they are getting in line with that versioning scheme.

Qt - How to connect signal from loader item, if loader source changes dynamically

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.

Resources