QML variable scope issues - qt

I am trying to use a property of the top-most component in the QML file inside a javascript function within some deeply nested structure, but it I'll get an error stating the property/variable name is undefined.
The confusing part is, that access to that very variable works in the line before.
Here's a part of the QML:
Page {
id: page
property var modelParam
property var albumNameParam
SilicaGridView {
id: grid
header: PageHeader { title: albumNameParam }
cellWidth: width / 3
cellHeight: width / 3
anchors.fill: parent
model: page.modelParam
delegate: GridItem {
id: gridItem
menu: ContextMenu {
MenuItem {
text: qsTr("Delete" )
onClicked: {
var dialog = pageStack.push(...)
dialog.accepted.connect(function() {
var res = modelParam.deleteSelected(); // works
modelParam.clearSelection(); // gives error
})
}
}
One solution I have found to this is:
onClicked: {
var theModel = modelParam
var dialog = pageStack.push(...)
dialog.accepted.connect(function() {
var res = theModel.deleteSelected(); // works
theModel.clearSelection(); // works
})
}
In this case, probably, the variable gets captured in the JS closure and is thus available inside the callback function.
However I don't understand how the first example can work partially? What is the proper approach in this case?

Try:
page.modelParam.clearSelection();

Related

Load all QML's from folder

Is it possible to load all QML's from working directory subfolder to SwipeView? Amount of those QML's is unknown and will be changing in time (user may add new and delete old ones), so I think that I also need to reload (refresh) SwipeView when certain Button is clicked. All QML's are different, because they are being created depending on the information which user provides, in Python back-end (just the template is the same).
I've managed to make something like this:
SwipeView {
id: job_swipe
width: jobwindow.width/2
height: jobwindow.height/2
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
orientation: Qt.Vertical
Component.onCompleted:
function load_jobs() {
jobsignals.jobs_to_qmls_slot()
var i
var fn
var fc = (jobsignals.filecount)
for (i = 0; i<fc; i++) {
fn = "jobs/job" + i + ".qml";
job_swipe.addPage(job_swipe.createPage(fn))
}
//job_swipe.currentIndex = (fc-1)
}
function addPage(page) {
addItem(page)
page.visible = true
}
function createPage(jobfile){
var component = Qt.createComponent(jobfile);
var page = component.createObject(job_swipe);
return page
}
function removePage(page) {
removeItem(page)
page.visible = false
}
}
But removing pages does not working as I want - it removes visually pages, but objects are still there, so when I want to add new page it firstly creates those which I've removed
I would use a FolderListModel to generate a list of .qml files in a folder. Then you can load those with Loaders.
SwipeView {
Repeater {
model: FolderListModel {
id: folderModel
folder: // Whatever folder you want to search
nameFilters: ["*.qml"]
}
Loader {
source: fileUrl
}
}
}
EDIT:
To refresh the data, I unfortunately don't see a built-in method in FolderListModel to do that. But you should be able to reset the folder value (in a kind of ugly way), like this:
Button {
text: "Refresh"
onClicked: {
var currentFolder = folderModel.folder
folderModel.folder = "" // First clear the folder
folderModel.folder = currentFolder; // Then set the folder again
}
}

QML: public variable

I have two QML files.
In First.qml I can make visible Second.qml. In Second.qml I have selectedParts variable.
I want to set selectedParts to value 1 always, when I make Second.qml visible. That works only when I load
Second.qml for first time. If I make Second.qml invisible and then visible, selectedParts value is 2. Is there anyway
to make selectedParts variable public and set its value always when I click on myImage?
First.qml
Item {
Image {
id: myImage
MouseArea{
anchors.fill: parent
onClicked: {
second.visible = true
...
}
}
}
}
Second.qml
Item {
property int selectedParts: 1
Image {
id: myImage2
MouseArea{
anchors.fill: parent
onClicked: {
selectedParts = 2
...
}
}
}
}
QML public variable? Look up for MessageBoard in Defining QML types from C++. We are using that approach. All you need is to create C++ MessageBoard object, put some data in there and reference it via the QML context given to every QML root object:
m_quickView.engine()->rootContext()->setContextProperty("myMsgBoard", MyQmlMsgBoard::instance());
And in QML:
Rectangle {
id: topRect
scale: myMsgBoard.scale // or anywhere in QML
// ....
}
Of course that "message board" C++ object exposes to QML something like:
Q_PROPERTY(qreal scale READ scale CONSTANT);
I solved my problem by adding back button into the Second.qml file. And in this button I put statement selectedParts = 1.

Items inside TabView/Tab are not accessible

Suppose we have a QML file like the following:
Window {
Component.onCompleted: rect.color ="green"
TabView {
Tab {
Rectangle {
id: rect
color: "white"
}
}
}
}
When I run this code, for some reason, it gives me this error:
ReferenceError: rect is not defined
Somebody can say it's a scope problem but the following code works fine:
Window {
Component.onCompleted: rect.color ="green"
Item {
Item {
Rectangle {
id: rect
color: "white"
}
}
}
}
In my case I have a big form with tabs and controls inside it and I pass all the controls to several functions to validate the form, i.e. some code like this:
function onClose() {
validate(control1);
validate(control2);
// etc
}
but while accessing controls by id I get the above error.
How about just binding the rectangle to a color parameter instead of a hardcoded value?
This wil separate your Model and View code further to make it more readable elsewhere in your project as well...try:
Window {
property string myColor: "White"
Component.onCompleted: myColor = "Green"
TabView {
Tab {
Rectangle {
id: rect
color: myColor
}
}
}
}
To view the QML object tree, you need to start your project in debug mode. Then split your code window so that the "Locals and Expressions" view is showing (checkbox ticked on the right side). You will see your root item and all the other controls nested in a tree and now you can directly edit the values of their properties, and the changes will render immediately. There is a youtube video tutorial on debugging: https://youtu.be/mPXn6L2Wftc?t=19m55s
I'm not sure if the tree would give you access to the controls at runtime, but it might help you with debugging.
Ok, since Tab item cannot be accessed from outside I think it can be done in this way:
TabView {
id: tabView
Tab {
title: "tab1"
function validate() { /* validate all the controls related to tab1 only */ }
Item { id: item1 }
Item { id: item2 }
}
Tab {
title: "tab2"
function validate() { /* validate all the controls related to tab2 only */ }
Item { id: item3 }
Item { id: item4 }
}
function validateTabs() {
for(var i = 0; i < tabView.count;i ++) {
var tab = tabView.getTab(i);
if(tab && tab.active && tab.item.validate) {
if(!tab.item.validate())
return false;
}
}
return true;
}
}
The good point that if some Tab wasn't opened and so not changed it will not be validated.

How to pass “this” from a QML element to a JS Function

Similar to the this keyword in C++, I’d like to either have a QML element to pass itself into a JS function, or have it set a property on another element to itself. Is this possible?
For example:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
Or, Consider this:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
}
Then, in SomeElement.qml:
Rectangle{
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
}
In this case, the *whatGoesHere* would be the instance of SomeElement where these are being called from.
Is this possible in QML? I would think the id property would make sense, but according to the docs, you cannot query the value of the id field, and anyway the id wouldn't be available if my SomeElement was described in a separate file, and the whatGoesHere handling above appeared in that separate file rather than in a particular instance.
I have two complementary proposals :
First, for a single usage, pass the ID as it's basically a pointer to the item :
MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
Then if you need multiple items to share this behavior, that means you're using MVC so the ID will work exactly the same :
Repeater {
model: 100;
delegate: MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
}
That is the classical part.
But to todo even better if you create your own components, keep in mind to create a 'self' helper property that does the 'this' job properly :
MouseArea { // component root definition
id: base;
property var self : base; // bind self on the I
}
Then use it like this :
Repeater {
model: 100;
delegate: MyComponent {
onClicked: { callFunc (self); }
}
}
Use this as often as you want !
Instance of your SomeElement is its id property value i.e. theChild. You can use it as this. No other built-in way exists as far as I can tell. But you can try to add your own QML item hierarchy with property which will return this.
Another way is to get children of some parent and use them. So you get children, locate the child you need and use that particular instance
If you define your element in a separate file, then you can simply assign an id and use it. It will be valid just within the context of that instance:
SomeElement.qml
Rectangle{
id: thisElement
MouseArea {
anchors.fill: parent
onClicked: {
someJsFunction(thisElement);
parent.theElement = thisElement;
}
}
}

Can Repeater delegate in Qml be made to behave in a generic way to the given Items?

{New to Qml (quick 2.0 using Qt 5.1 beta) and learning}.
I wanted to know if such an idiom would be possible in Qml:
Below I have objLeftColumn which expects its children to expose a boolean m_bIsSelected and a MouseArea alias m_mouseProperty and uses them to make the collection of such children mutually exclusive, ie., only one of them can be in selected state. The followin works fine but I need to repeat it every time I want and specially if I wanted it for Row etc.
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: ABCD {
id: objABCD
m_mouseProperty.onClicked: {
if(m_bIsSelected) {
objLeftColumn.m_iCurrentButtonClicked = index
}
else {
objLeftColumn.m_iLastButtonClicked = -1
objLeftColumn.m_iCurrentButtonClicked = -1
}
}
}
}
}
Can I write a generic objLeftColumn (in a separate qml file) that could arrange the given Items in Column while aslo dealing with exclusivity of their selection?
The idea is instead of giving the component to the delegate right there an then, I'll give it later and for each instantiation of the component (depending on numeric value of model above and below) the delegate: in Repeater should behave similarly.
eg., in psedo code:
in Exclusive.qml:
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
property alias m_delegate: objLeftColumnRepeater.delegate
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
//state of item can be manipulated but want to
//add behaviour to the item eg:
/*item {
m_mouseProperty.onClicked: {
//do something
}
}*/
}
}
}
in SomeOther.qml:
Exclusive {
model: 5
delegate: ABCD
}
Exclusive {
model: 9
delegate: DEFG
}
etc..So this way Column in Exclusive is more generic and can be called with any Item assigned to its delegate and will behave similarly. Is this possible in qml
This needs a bit of trickery to be solved, I can think of two ways:
Use the JS connect() function to manually create the connections. Something like this:
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
item.m_mouseProperty.onClicked.connect(function() {
console.log("Clicked!");
});
}
}
Wrap the delegate into an Item by using a Loader, and use aConnections element for the connection. Something like this:
property Component delegate
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: Item {
Loader {
id: loader
sourceComponent: delegate
}
Connections {
target: loader.item.m_mouseProperty
onClicked: console.log("Clicked")
}
}

Resources