I've began learning QML and I'm getting the following error:
ReferenceError: chatTextArea is not defined
I have a global function that does something on an item within the same QML file, by id.
For some reason I can't access via the ID of my TextArea, or any item inside of the SplitView. But I am able to manipulate the properties of TabView and each Tab.
My broken code:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
Rectangle {
id: lobby
function appendChatMsg(msg) {
chatTextArea.append(msg) //causes: ReferenceError: chatTextArea is not defined
}
TabView {
id: frame
Tab { //I CAN access this item via ID.
id: controlPage
SplitView {
anchors.fill: parent
TableView {
Layout.fillWidth: true
}
GridLayout {
columns: 1
TextArea { //This item I CANNOT access via ID.
id: chatTextArea
Layout.fillHeight: true
Layout.fillWidth: true
}
TextField {
placeholderText: "Type something..."
Layout.fillWidth: true
}
}
}
}
}
}
Any idea why chatTextArea is out of scope of my function? Thanks in advance.
Change the starting portion of your code to smth like this:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
Rectangle {
id: lobby
function appendChatMsg(msg) {
controlPage.chatArea.append(msg) //causes: ReferenceError: chatTextArea is not defined
}
TabView {
id: frame
Tab { //I CAN access this item via ID.
id: controlPage
property Item chatArea: item.chatArea
SplitView {
property Item chatArea: chatTextArea
Reason this works, is that Tab turns out to behave like a Loader (per the docs), loading whichever Component you give it; thus, the SplitView in your code is a Component specification, and that component is instantiated by the Tab in a separate QML context (parented to that of the document root item). Which is why everything inside that context can see things up the scope chain (like the appendMessage() function), but not the reverse :)
Related
If a ListView contains user-defined properties, these properties can be referenced in a binding for model but they can not for anything inside the delegate. Why is this?
The docs seem to say that a Component should be able see properties in enclosing scopes where it was declared.
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
visible:true
ListView {
orientation: ListView.Vertical; height: 300; width: 100
property var myCount: 3
property var myMessage: "Hello"
Component {
id: myComp
Text {text: myMessage} // ReferenceError: myMessage is not defined
}
model: myCount // this works
delegate: myComp
}
}
(In my real application, the ListView is a component (.qml file) and the invoker needs to pass in information needed to configure the delegate; not
literal text like in this example, but information for a nested ListView.)
Thanks for any help...
The variables in QML have a scope, in your case when using myMessage without reference these indicating that the variable belongs to the Text item.
# ...
Component {
id: myComp
Text {text: myMessage}
}
# ...
So the solution is to use the ListView id as a reference:
# ...
ListView {
id: lv
orientation: ListView.Vertical; height: 300; width: 100
property var myCount: 3
property var myMessage: "Hello"
Component {
id: myComp
Text {text: lv.myMessage}
}
model: myCount // this works
delegate: myComp
}
# ...
I have this QML progress bar:
import QtQuick.Controls 2.0 as QQC20
Item {
QQC20.ProgressBar {
id: progressbar_id
visible: false // even if "true", the progress bar does NOT show up on UI
from: editorScene.progressbarMin
to: editorScene.progressbarMax
value: editorScene.progressbarVal
onValueChanged: {
console.log("Progressbar value changed: ", progressbar_id.value)
}
onVisibleChanged: {
console.log("Progressbar visibility chanaged: ", progressbar_id.visible)
}
}
}
I can confirm that the progress bar value and visibility are changed by the methods onValueChanged and onVisibleChanged.
However, the problem is that the progress bar does NOT show up on the UI! How can I actually show the progress bar on the UI? Can anybody give me a hint?
Right now, all you're doing is creating a QML type which you can use as part of your API. To actually see it, you need to create an instance of it under a ApplicationWindow or Window (or anything else equivalent, e.g. Canvas or Felgo's GameWindow).
There are two ways you can accomplish this. You can
Directly add your item as a child of a window.
Put your item in a separate file, and create an instance of that file under a window.
Lé Code
Method 1: Directly Adding as Child
Directly insert your codeblock as a child of an ApplicationWindow.
// Main.qml
import QtQuick 2.0 // for `Item`
import QtQuick.Window 2.0 // for `ApplicationWindow`
import QtQuick.Controls 2.0 // as QQC20 // no need to label a namespace unless disambiguation is necessary
ApplicationWindow {
width: 480 // set the dimensions of the application window
height: 320
// here's your item
Item {
anchors.centerIn: parent // place in centre of window
ProgressBar {
id: progressbar_id
anchors.horizontalCenter: parent.horizontalCenter // horizontally align the progress bar
from: 0 // don't know what editorScene is
to: 100 // so I'm using raw values
value: 5
onValueChanged: {
console.log("Progressbar value changed: ", progressbar_id.value)
}
onVisibleChanged: {
// side note: I'm not getting any output from this handler
console.log("Progressbar visibility chanaged: ", progressbar_id.visible)
}
}
}
// provide user-interaction for changing progress bar's value
MouseArea {
anchors.fill: parent // clicking anywhere on the background
onClicked: progressbar_id.value += 5; // increments the progress bar
// and triggers onValueChanged
}
}
Method 2: Using a Separate File
Save your item into a new qml file.
// MyProgressBar.qml
import QtQuick 2.0 // for `Item`
import QtQuick.Controls 2.0 // for `ProgressBar`
// here is your item, it has grown up to be in a file of its own 🚼
Item {
property alias value: progressbar_id.value // for user-interaction
ProgressBar {
id: progressbar_id
anchors.horizontalCenter: parent.horizontalCenter // centre horizontally
from: 0
to: 100
value: 5
onValueChanged: {
console.log("Progressbar value changed: ", progressbar_id.value)
}
onVisibleChanged: {
console.log("Progressbar visibility chanaged: ", progressbar_id.visible)
}
}
}
Note that you still need the import statements.
Then call it from a window in Main.qml. We'll use an ApplicationWindow here.
// Main.qml
import QtQuick 2.0
import QtQuick.Window 2.0 // for `ApplicationWindow`
// import "relative/path/to/progressbar" // use this if MyProgressBar.qml is not in the same folder as Main.qml
ApplicationWindow {
width: 480
height: 320
MyProgressBar {
id: progressbar_id
}
MouseArea {
anchors.fill: parent
onClicked: progressbar_id.value += 5;
}
}
If your qml files aren't in the same directory, make sure you add an import "relative/path" at the top of the Main.qml file among the other import statements.
For example, if
Your Qml project is in /Users/Lorem/Project,
The full path to your Main.qml is /Users/Lorem/Project/qml/Main.qml, and
The full path to your MyProgressBar.qml is /Users/Lorem/Project/qml/myControls/MyProgressBar.qml...
Then use import "myControls" in Main.qml to import the items from the myControls subdirectory. Remember, you only need to import the directory, not the file itself.
Result
This is what the result resembles when I run it from a macOS.
At startup.
After 3 clicks on the background.
There is also console/debug output after each click:
Progressbar value changed: 10
Progressbar value changed: 15
Progressbar value changed: 20
How to make some reusable QML object, which can inject another object?
I've ever tried to use Component & Loader , but seems not what I want. (It still encapsulate the whole QML type and lacks of elasticity, hard to reuse)
Usage example:
Card.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
default property var innerObject
property string titleText: "[Hello Untitled Title]"
id: root
color: "#fff"
ColumnLayout {
anchors.fill: parent
Rectangle {
id: header
height: 10
width: parent.width
color: "#666"
RowLayout {
Text { text: titleText; color: "#fff" }
}
}
// How to inject innerObject in here ?
}
}
main.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Card {
titleText: "Image Information"
ColumnLayout { /* .......*/ } // innerObject
}
Card {
titleText: "Image Viewer"
Rectangle { /* .......*/ } // innerObject
}
The answer I linked works like this:
Main.qml
Card {
titleText: "Image Viewer"
innerObject: Rectangle {
Component.onCompleted: {
console.log(parent.objectName)
}
}
}
Card.qml
Rectangle {
property string titleText: "[Hello Untitled Title]"
default property alias innerObject : innercolumn.children
id: root
color: "#fff"
ColumnLayout {
id: innercolumn
objectName: "column"
anchors.fill: parent
Rectangle {
id: header
height: 10
width: parent.width
color: "#666"
RowLayout {
Text { text: titleText; color: "#fff" }
}
}
}
}
I also want to suggest a solution based on default property and reparenting:
The Item which can embed another Item:
MyItem.qml
import QtQuick 2.7
import QtQuick.Layouts 1.2
Rectangle {
id: root
default property Item contentItem: null
border {
width: 1
color: "#999"
}
ColumnLayout {
anchors.fill: parent
Rectangle {
Layout.fillWidth: true
height: 30
color: "lightgreen"
}
Item {
id: container
Layout.fillWidth: true
Layout.fillHeight: true
}
}
onContentItemChanged: {
if(root.contentItem !== null)
root.contentItem.parent = container;
}
}
Can be used as below:
main.qml
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
visible: true
width: 600
height: 600
MyItem{
width: 400
height: 400
anchors.centerIn: parent
Text {
text: "Hello!"
anchors.centerIn: parent
}
}
}
But I still agree with #ddriver that Loader is the best solution for this case
It is not mandatory that you use a Loader with a component. You can just go:
Loader {
source: "Something.qml"
}
When the source is something that can be loaded synchronously, you can directly use the loader's item for stuff like bindings, without worrying about whether or not it is created. If you load over network, you have to delay the bindings until the item is completed and use either a Binding element or Qt.binding() to do it respectively in a declarative or imperative manner.
In your case, a loader would be appropriate, and the property for the inner dynamic object outta be a Component. This way you can populate it either with an inline component, or with Qt.createComponent() from existing source.
property Component innerObject
...
innerObject: Component { stuff }
...
innerObject: Qt.CreateComponent(source)
Of course, there are even more advanced ways to do it, for example, the "generic QML model object" I have outlined here. It allows to quickly and easily create arbitrary data structure trees both declaratively and imperatively, and since the object is also a model, you can directly use listviews or positioner elements with repeaters to layout the gui without actually writing the UI code each and every time.
Also, from your main.qml code example - you cannot have more than one root element in a qml file.
Edit: The default property approach actually works if the element is moved to its own qml file, so also basically you could just:
default property alias innerObject: innerColumn.children
where innerColumn is the id of your ColumnLayout. Also, innerObject could be whatever legal name, since as a default property, it will not actually be used.
There is also the option to not use a default property, which is useful when the root item still needs to have its own children, but still have the ability to redirect declarative objects to be children of a sub-object:
property alias content: innerColumn.children
// and then
content: [ Obj1{}, Obj2{}, Obj3{} ] // will become children of innerColumn
I tried to call functions of qml file from another qml file user component id but i am facing some issues. could some one help me out of this.
here is my code.
Browser.qml:
import QtQuick 2.0
Item {
function callme(message) {
console.log(message)
}
}
main.qml:
import QtQuick 2.3
import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.0
ApplicationWindow {
visible: true
width: 640
height: 100
TabView {
id: tabView
width: 640
height: 50
Tab {
width: 100
title: "Sample1.html"
onVisibleChanged: {
browser1.callme("hi")
}
Browser {
id: browser1
}
}
Tab {
width: 100
title: "Sample2.html"
onVisibleChanged: {
browser2.callme("bye")
}
Browser {
id: browser2
}
}
}
}
Error reported:
ReferenceError: browser1 is not defined
If you want access to items inside Tab control, you have to use its item property. I have changed your signal handler and it works:
...
onVisibleChanged: {
item.callme("hi")
}
Browser{
id: browser1
}
...
Tab control inherits from Loader component. It takes its children as delegate and they are only created with the tab is activated. Most of the behavior is the same then the Loader component.
Experimentation for the record
What happend if we define two or more components inside a Tab? Loader component only accepts a delegate and the component created is accessed by item property. Tab component maps children property to delegate and you can define more than one, but I realized that only the last child is created.
When I try to access a son of a Tab element, if it is not active QML throws an error saying is undefined.
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.0
ApplicationWindow {
TabView {
Tab {
id: mytab1
}
Tab {
id: myTab2
Rectangle {
//(...)
}
}
}
Connections {
target: eventManager
onData: {
var scene = myTab2.children[0];
console.log(scene);
}
}
}
So, if myTab2 is active, I can see in the console QQuickItem_QML_15(0x27f1e2e0). If myTab2 is not active, then qml throws TypeError: Cannot read property 'children' of undefined.
Why is undefined if the tab is not active and how can I fix it?
Thanks!
From the Qt documentation website, I've found a solution.
Tabs are lazily loaded; only tabs that have been made current (for
example, by clicking on them) will have valid content. You can force
loading of tabs by setting the active property to true:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.0
ApplicationWindow {
TabView {
Tab {
id: mytab1
active: true
}
Tab {
id: myTab2
active: true
Rectangle {
//(...)
}
}
}
Connections {
target: eventManager
onData: {
var scene = myTab2.children[0];
console.log(scene);
}
}
}
So, I've added the property active: true in both tabs, and It works fine!
A TabView doesn't create its content items until a tab is activated.
Your example begins at tab 1, at this point the rectangle in tab 2 doesn't exist, so you get undefined. If you activate tab 2 the rectangle will be created, and then if you go back to tab 1 you will not get undefined.
A Tab inherits a Loader, and comes with an active property. I suppose there is an optimization that exists back in the Loader component to delay loading until the element becomes visible. If you set active: true in your Tab it will be loaded before the tab is activated. Note that this will not make the tab view open with the second tab active.
Tab {
id: t2
active: true
Rectangle {
}
}