Refactoring QML and keeping components in scope - qt

I have a larger QML project that I'm trying to refactor so that some related components are in their own file. In my simplified example, what is part of ThingManager.qml was previously simply included in the main.qml file. Prior to refactoring, the scope of ids, a, b, and c were visible to page.qml since they were defined in the main file. As I tried to abstract these into a ThingManager component to reduce clutter in my main.qml, page.qml was no longer able to reference those with the familar scoping error:
page.qml:9: ReferenceError: a is not defined
Given that I am loading pages similar to page.qml and want to execute methods within these components stored in another file, what is the recommended way to refactor something like this example such that page.qml can access methods in these components without them being defined in main.qml? Or is that simply not possible to do?
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQml 2.2
ApplicationWindow {
id: window
ThingManager { }
Loader {
id: page_loader
}
Component.onCompleted: {
page_loader.setSource("page.qml")
}
}
ThingManager.qml:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
Item {
Column {
Button {
id: a
text: "A"
}
Button {
id: b
text: "B"
}
Button {
id: c
text: "C"
}
}
}
page.qml:
import QtQuick 2.7
import QtQuick.Controls 2.1
Page {
id: page
Component.onCompleted: {
console.log("Execute A's method")
a.toggle()
}
}

You should add an id to ThingManager and define a as an alias property in ThingManager.qml as follows:
main.qml:
...
ThingManager {id: manager }
...
ThingManager.qml:
Item {
property alias a: a // makes 'a' available outside this file
property alias b: b
property alias c: c
...
}
page.qml:
...
manager.a.toggle ()
...

Related

Call to a python function in the previous Page in the PageStack

I have 2 QML Silica Pages, Main.qml and Icon.qml.
Main.qml has this distribution:
import QtQuick 2.0
import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3
Page {
SilicaFlickable {
id: mainList
Button {
text: "Stack the next page"
onClicked: { // Stack de Icon Page
pageStack.push(Qt.resolvedUrl("Icon.qml"))
}
}
function reload(){ }
Python {
id: py
Component.onCompleted: { mainList.reload() }
}
}
}
And I want to call the function reload() since Icon:
import QtQuick 2.0
import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3
Page {
id: iconEditor
Button {
onClicked: {
pageStack.pop()
pageStack.completeAnimation()
pageStack.currentPage.mainList.reload() // Fail
}
}
}
The Main.qml Page is stacked in the principal QML file like this:
import QtQuick 2.0
import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3
ApplicationWindow {
id: root
Python {
Component.onCompleted: {
pageStack.push(Qt.resolvedUrl("Main.qml"))
}
}
}
How can I call the function on the previous page of the stack?
The "id" have as scope only the .qml where it was defined and cannot (or should be used outside of that scope). If you want to expose objects or variables then you must do it by creating a property in the toplevel of the file.
In this case a solution is to use an alias property:
import QtQuick 2.0
import Sailfish.Silica 1.0
import io.thp.pyotherside 1.3
Page {
property alias mainList: mainList
SilicaFlickable {
id: mainList
// ...

QML progress bar is NOT showing up on UI

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 get NavigationKey.up value in qml and c++?

Im beginner in qml. I have set KeyNavigation.up (in item up) to an id of another item (down).
Why i could't retrieve KeyNavigation.up like this in qml ?!
var x = down.KeyNavigation.up
UPDATE:
This is an example. why i couldn't get a.KeyNavigation.up ?
import QtQuick 2.3
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
TextField {
id: up
KeyNavigation.down: down
}
TextField {
id: down
KeyNavigation.up: up
}
function fun(){
var x = up.KeyNavigation.down
}
}
Your code actually works.
I don't see where you're calling fun(), but if you for example add the line onActiveFocusItemChanged: fun() after visible: true you will see the x variable is OK.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
visible: true
width: 500; height: 500
onActiveFocusItemChanged: fun()
function fun(){
var x = up.KeyNavigation.down
print("x = up.KeyNavigation.down: " + x.text)
var y = x.KeyNavigation.up
print("y = x.KeyNavigation.up: " + y.text)
}
TextField {
id: up
y: 50
KeyNavigation.down: down
}
TextField {
id: down
y: 100
KeyNavigation.up: up
}
}
In qml, items have properties. And you should declare properties. Without your whole code i cannot really give you a more detailed answer. Just have a look at this
In QML, ids must begin with a lowercase letter. If you're using Qt Creator, you'd see this problem as soon as you typed it. When you run the program, you'd see this error:
IDs cannot start with an uppercase letter
So, either you typed your code in manually for this question (bad idea) and you mistyped the ids, or you didn't run it, which would be strange.
Using a lowercase id works:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
Component.onCompleted: print(a.KeyNavigation.up)
Item {
id: A
KeyNavigation.up: b
}
Item {
id: b
}
}
Output:
qml: QQuickItem(0x596dcc9180)

ReferenceError while trying to call function of Item within Tab

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.

Can't access QML item by id inside SplitView

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 :)

Resources