ReferenceError: volumeControl is not defined - qt

I have a situation where VolumeControl.qml should receive all the keys received by pages derived from Page1 (like Page2) and pages derived from Page2 (like Page3).
When I tried to forward the keys from all the pages to the VolumeControl (through id volumeControl), I am able to forward only from Page1, but getting reference error in Page2 and Page3 when trying to acess volume control id.
Why I am getting reference error in Page2 and Page3? How to resolve this to forward the keys from all individual pages to volume control, before the keys are consumed (event.accepted) by the pages?
Also Why I am not getting reference error in Page1?
VolumeControl.qml
import QtQuick 2.4
FocusScope
{
id: volumeControl
focus: true
Keys.onPressed:
{
console.error("VOLUME CTRL Key received" + event.key);
}
}
Page1.qml
import QtQuick 2.4
Item
{
id: p1
Keys.forwardTo: [volumeCtrl]
VolumeControl
{
id: volumeCtrl
visible: true
z: 60
}
}
Page2.qml
import QtQuick 2.4
Page1
{
id: p2
Keys.forwardTo: [volumeCtrl]
Keys.onDigit0Pressed: handleDigit("0")
Keys.onDigit1Pressed: handleDigit("1")
Keys.onDigit2Pressed: handleDigit("2")
Keys.onDigit3Pressed: handleDigit("3")
Keys.onDigit4Pressed: handleDigit("4")
Keys.onDigit5Pressed: handleDigit("5")
Keys.onDigit6Pressed: handleDigit("6")
Keys.onDigit7Pressed: handleDigit("7")
Keys.onDigit8Pressed: handleDigit("8")
Keys.onDigit9Pressed: handleDigit("9")
function handleDigit(digit)
{
}
}
Page3.qml
import QtQuick 2.4
Page2
{
id: p3
Rectangle
{
width: 360
height: 360
focus:true
Keys.forwardTo: [volumeCtrl]
Keys.onPressed:
{
console.error("page3 Key received" + event.key);
}
}
}

If I have correctly understood your question, you should add alias property for Page1 item. You getting reference error in Page2 and Page3 because volumeCtrl property or reference to it doesn't exist in these items directly.
Add to your root item in Page1.qml:
property alias volumeCtrlAlias1: volumeCtrl
Now Page1 items will have a reference property to your VolumeControl item.
Then in your Page2.qml change this:
Keys.forwardTo: [volumeCtrl]
to:
Keys.forwardTo: [volumeCtrlAlias1]
And finally in your Page3.qml change the same string
to
Keys.forwardTo: [p3.volumeCtrlAlias1]
Information about aliases from Qt Documentation here.

Related

Property bindings getting lost in Qml when an Item accesses same value from different items in different files

There are a bunch of Qml Properties questions on StackOverflow, all of them are related to items in the same file, and that's easily fixable by using a Connections or a Binding directly. But what happens if an Qml object loses the connection to the outside because of a missing binding?
I created a "Spinner Slider", that's a Spinner and a Slider that shares the same value:
SpinnerSlider.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.3
RowLayout {
id: root
property alias value: sliderControl.value
property alias from: sliderControl.from
property alias to: sliderControl.to
Slider {
id: sliderControl
Layout.fillWidth: true
stepSize: 1
wheelEnabled: true
}
SpinBox {
id: spinBox
from: root.from
to: root.to
value: root.value
stepSize: sliderControl.stepSize
editable: true
onValueChanged: root.value = value
}
}
And I want to use in the following way, Two sliders, one above the other.
Modifying the top one should change the value on the bottom one, changing the value on the botton one should do nothing.
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
Column {
anchors.fill: parent
SpinnerSlider {
id: outerSlider
from: 0
to: 500
value: 50
}
SpinnerSlider {
id: innerSlider
from: 0
to: 500
// Does Not Work
// value: outerSlider.value
// Works
Connections {
target: outerSlider
onValueChanged: innerSlider.value = outerSlider.value
}
// Works
Binding on value {
value: outerSlider.value
}
}
}
}
As per the comments in the code, if I use the "usual" way of connecting things on Qml, value: something.value, the code will not work as soon as I change the values on the SpinBox because the onValueChange will trigger and I will remove the Property Binding by effectively setting a constant to it.
What I really want is to be able to use the value: outerSlider.value way
What I tried:
Connections, Qt.bindings, Bindings on.. in the SpinnerSlider.qml, but nothing seemed to work.
Any hints?
I managed to fix the code by using Binding on multiple times. This way I don't break the outer binding.
Binding on value {
when: sliderControl.pressed
value: sliderControl.value
}
Binding on value {
when: spinBox.up.pressed
value: spinBox.value
}
Binding on value {
when: spinBox.down.pressed
value: spinBox.value
}
Binding on value {
when: spinBox.focus
value: spinBox.value
}

How to access containing QML StackView?

I am new to QML, I have a Component which is pushed on a StackView. I'd like from this component to access the StackView containing it.
Here is a code that works
import QtQuick 2.11
import QtQuick.Controls.2.2
ApplicationWindow {
id: window
StackView {
id: stackView
initialItem: test1
anchors.fill: parent
}
Component {
id: test1
Button {
text: "Go to test2"
onClicked: stackView.push(test2)
}
}
Component {
id: test2
Button {
text: "Back to test1"
onClicked: stackView.pop()
}
}
}
However, I'd like to avoid accessing stackView by its id
Stack.view seems to be what I'm looking for, but I have no idea how to use it. I tried all of the following (replacing Buttons' onClicked) :
Stack.view.push(test2)
view.push(test2)
test1.Stack.view.push(test2)
test1.view.push(test2)
None of these work.
Am I misunderstanding something ? How am I supposed to use Stack.view ?
Edit : this question looks really close to mine : Access QML StackView from a control
I could indeed use a property to keep a reference to my StackView, but I would still like to avoid that if possible.
The accepted answer says that root.StackView.view.pop() is the correct method. I assume root is the Page's id from this post, so I tried test1.StackView.view.push(test2), but this still doesn't work. (Also tried with root, but it's not better)
Be aware that this Q/A is about the QtQuick.Controls 2.x
I also think it is good style to first use the attached property, rather than doubling this functionality by adding own, custom properties.
The problem is, that you are using the attached property in the wrong place - it is attached to the Item that is pushed onto the StackView and not to any of its children. So to use the attached property, you need to specify an identifier of this Item first.
In the example, that has been linked, this is root. But it is not the id of the Component-object. It has to be the root-node of the content of the Component-object.
You should have something like this:
Component {
id: myComponent
Item { // The Page. To this the attached property will be attached.
id: myComponentRoot // Use this to identify the root node of the pushed item.
Item { // Some more layers ...
...
Button { // Here you now want to access it perhaps?
onClicked: myComponentRoot.StackView.view.pop() // Reference the root node of the component to access it's attached properties.
}
}
}
}
A simple way of convenient using StackView.view is by assigning it to a property. In the following, I deliberately did not give the parent StackView an id. I can create a property in the sub-pages to grant access to the StackView as follows:
property StackView stackView: StackView.view
Here's a fully working example:
import QtQuick
import QtQuick.Controls
Page {
StackView {
anchors.fill: parent
initialItem: "Test1.qml"
}
}
//Test1.qml
import QtQuick
import QtQuick.Controls
Page {
property StackView stackView: StackView.view
Button {
anchors.centerIn: parent
text: "Go to test2"
onClicked: stackView.push("Test2.qml")
}
}
//Test2.qml
import QtQuick
import QtQuick.Controls
Page {
property StackView stackView: StackView.view
Button {
anchors.centerIn: parent
text: "Back to test1"
onClicked: stackView.pop()
}
}
You can Try it Online!

If Loader's sourceComponent Item references its parent, and I set "loader.active = false", I get an error

My code:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
Item {
id: loaderParent
Loader {
id: loader
active: true
sourceComponent: Item {
parent: loaderParent
x: parent.x
}
}
}
Item {
focus: true
Keys.onPressed: {
loader.active = false;
}
}
}
When I press any key, I get this error:
qrc:/main.qml:16: TypeError: Cannot read property of null
Thought I suspect this error is harmless, I'd like an explanation or any idea for a fix/workaround?
Reported here.
I found a workaround: instead of fetching parent.x, fetch loaderParent.x. Still want to know why the problem happens.
The Loader appears to set the item parent to null on destruction. QML objects are not deleted immediately, instead they use deleteLater() which leaves the object alive for another event loop cycle.
This leads to a reevaluation of the binding expression, which is no longer possible since the parent is now null. I've had a more severe encounter with this behavior described here.
A simple way to avoid it would be to not use the parent property which you already found, or to use a more complex binding expression such as x: loader.active ? parent.x : someFailsafeValue.
By using onParentChanged: console.log(parent) you can verify that the parent indeed changes to null when the loader is deactivated.

ListView model function

I am just getting started in Qt, and trying to make function which operates ListView model's elements.
I have custom made button in "myButton.qml" which has states like "normal", "pressed", "selected", etc.
ListView is in "main.qml". Structure is like this:
ListView{
//...
model: nameModel
delegate: myButton {
//...
}
}
So here is my goal: this list of buttons should act like group of radiobuttons - only one can have selected state and selected state is when you press button. I think that I should have click handler and a function that calls on button click. Function should check the list of buttons and if one button was selected before function just changes its state to "Normal".
So I have no idea of how to write this func and where should I place it. I read Qt docs but still no idea.
A possible easy way to solve this problem is by exploiting ExclusiveGroup. As discussed in the documentation, support to this type can be added to any type:
It is possible to add support for ExclusiveGroup for an object or control. It should have a checked property, and either a checkedChanged, toggled(), or toggled(bool) signal. It also needs to be bound with ExclusiveGroup::bindCheckable() when its ExclusiveGroup typed property is set.
You can define an ExclusiveGroup at the ListView level and implement the required logic in the ListView delegate. By binding the delegate ExclusiveGroup property to the ExclusiveGroup of the ListView you should achieve what you want, without the need of a function that crawls the model.
Final toy example to demonstrate the usage:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: root
visible: true
width: 200
height: 500
ListView {
anchors.fill: parent
model: 10
spacing: 20
ExclusiveGroup { id: ex } // the group for all the delegate
delegate: Rectangle {
id: delegate
width: ListView.view.width
height: 30
color: checked ? "yellow" : "steelblue"
// code to have exclusive behaviour
property bool checked: false
property ExclusiveGroup exclusiveGroup: ex
onExclusiveGroupChanged: {
if (exclusiveGroup)
exclusiveGroup.bindCheckable(delegate)
}
// mouse area to trigger the property change
MouseArea {
anchors.fill: parent
onClicked: checked = true
}
}
}
}

How to make tab's item to be created programmatically?

I dynamically add tabs to TabView and pass tab's item to c++ for futher processing. The problem is that method tabview.getTab(tabview.getTab(tabview.count-1).item) returns null for the which index is >0. Here is code:
//main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
signal tabAdded(variant c)
ColumnLayout{
anchors.fill: parent
TabView{
visible: true
id:tabview
Layout.fillHeight: true
Layout.fillWidth: true
}
Button{
text: "add tab"
onClicked:{
var c = Qt.createComponent("Tab.qml");
tabview.addTab("tab", c)
// tabAdded(tabview.getTab(tabview.count-1).item)
console.log(tabview.getTab(tabview.count-1).item)
}
}
}
}
//Tab.qml
import QtQuick 2.0
import QtQuick.Controls 1.1
Item{
signal tabButtonClicked()
anchors.fill: parent
Button{
text: "tabButton"
anchors.centerIn: parent
onClicked: tabButtonClicked()
}
}
I figured out that tabview.getTab(index).item returns apropriate value if tab(index) was activated manually (by clicking with mouse on it). It seems like tab's item is created only when user firstly activate tab. So, how to make item to be created immediately after tab creation?
Every tab in TableView is represented by a Tab component that inherits from Loader component. If you want force the Tab to load its contents, just set active property to true. This property controls when the component must be loaded. So now, your button code looks like this:
Button{
text: "add tab"
onClicked:{
var c = Qt.createComponent("Tab.qml");
tabview.addTab("tab", c);
var last = tabview.count-1;
tabview.getTab(last).active = true; // Now the content will be loaded
console.log(tabview.getTab(last).item);
}
I hope this help you.

Resources