I have:
MyItem.qml
import QtQuick 2.0
Item {
property string info1: "info"
property int info2: 1
}
How to add an item to an array qml during initialization?
It's work:
import QtQuick 2.0
Item {
property var arr: [{ info1: "test", info2: 1}, { info1: "info" }]
}
But, it's not work:
import QtQuick 2.0
Item {
property var arr: [MyItem { info1: "test", info2: 1}, MyItem { info1: "info" }]
}
If you use a newer QML version you can use the new list<Item>-type for properties. There you can easily add Items in the syntax you want - just like when you were using a property alias someProperty: someItem.somePropertyList (e.g. children) for example.
include QtQuick 2.10 // I think 2.9 is sufficient
ApplicationWindow {
...
property list<Item> myItemList: [
Item { Component.onCreated: console.log(parent) }, // parent will be null! Set it explicitly if needed.
Item {},
Item {},
...
]
}
http://doc.qt.io/qt-5/qml-list.html
Small side note from the documentation:
Note: The list type is not recommended as a type for custom properties. The var type should be used instead for this purpose as lists stored by the var type can be manipulated with greater flexibility from within QML.
IMHO, you can ignore that as long as you don't need the "greater flexibility"
In older versions this is not possible but you could hack your way around, if you have a type "ArrayObject.qml"
import QtQuick 2.0
QtObject {
id: root
default property QtObject store
readonly property var arr: (arr === undefined ? [] : arr) // This will give you a binding loop, but ensures that after filling the array, it is not reset to [].
onStoreChanged: {
if (store) {
console.log(store)
arr[arr.length] = store
console.log(arr, arr.length)
}
store = null
}
}
You can then use it:
ArrayObject {
id: tst
Item {
}
Item {
}
Item {
}
}
Of course, there might be other workarounds.
E.g:
Item {
property var myArray: [
itemId1, itemId2, itemId3, itemId4
]
Item { id: itemId1 }
...
}
You can try to use Dynamic QML Object Creation:
import QtQuick 2.0
Item {
property var arr: {
component = Qt.createComponent("MyItem.qml");
item1 = component.createObject(null, {"info1": "test", "info2": 1});
item2 = component.createObject(null, {"info1": "info"});
return [item1, item2];
}
}
See Qt.createComponent and createObject for more information.
Related
I have data in a dictionary which is [String: String]. What I want to provide is an interface to the user to edit the values in the dictionary, while the keys remain fixed. I can see how to display the values, but putting them into a TextField is what I want, and haven't been able to find how to do.
Here is the code:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.sorted(by: <), id: \.key) { key, value in
HStack {
Text(key)
TextField("", text: $entries[key])
}
}
}
}
}
This doesn't compile, with no fewer than three errors on the TextField line:
Cannot convert value of type 'Slice<Binding<[String : String]>>' to expected argument type 'Binding'
Cannot convert value of type 'String' to expected argument type 'Range<Binding<[String : String]>.Index>'
Referencing subscript 'subscript(_:)' on 'Binding' requires that '[String : String]' conform to 'MutableCollection'
So obviously I am doing things incorrectly, but I am lost trying to find what the correct way would be, and haven't been able to find an answer in an internet search. Can anyone point me in the right direction?
you could try this simple approach:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.keys.sorted(by: <), id: \.self) { key in
HStack {
Text(key)
TextField("", text: Binding(
get: { entries[key]! },
set: { entries[key] = $0 }
))
}
}
}
}
}
struct ContentView: View {
#State var entries: [String: String] = ["key1":"val1", "key2":"val2", "key3":"val3", "key4":"val4"]
var body: some View {
dictionaryEditor(entries: $entries)
Button(action: { print("----> entries: \(entries)") }) {
Text("print entries")
}
}
}
The problem is that entries[key] returns an optional String value while the text parameter of TextField expects a Binding of non optional String.
You can create an optional binding extension and then you can use it safely:
extension Binding where Value == String? {
var optionalBind: Binding<String> {
.init(
get: {
wrappedValue ?? ""
}, set: {
wrappedValue = $0
}
)
}
}
Then you can just add the optionalBind to your code:
struct dictionaryEditor: View {
#Binding var entries: [String: String]
var body: some View {
List {
ForEach(entries.sorted(by: <), id: \.key) { key, value in
HStack {
Text(key)
TextField("", text: $entries[key].optionalBind) // <--
}
}
}
}
}
I have a ListView, where the currently displayed modelData changes as a button cycles through several department options. If one of these departments has no data, my delegate continues showing the previous list data until it reaches a new section of modelData with data.
What I want to do, is when the model is 'empty' (being undefined, which may happen when the key it's looking for is yet to be created in my Firebase Database, or no items are currently visible), text/image is shown instead; i.e, "Move along now, nothing to see here".
My model is drawn from JSON, an example is below. and my calendarUserItems is the root node of multiple children within my Firebase Database, the aim of my AppButton.groupCycle was too add a further direction to each child node, filtering the data by this to view and edit within the page.
A sample of my code is:
Page {
id: adminPage
property var departments: [1,2,3,4]
property int currGroupIndex: 0
AppButton {
id: groupCycle
text: "Viewing: " + departments[currGroupIndex]
onClicked: {
if (currGroupIndex == departments.length - 1)
currGroupIndex = 0;
else
currGroupIndex++;
}
}
ListView {
model: Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]])
delegate: modelData.visible ? currentGroupList : emptyHol
Component {
id: emptyHol
AppText {
text: "nothing to see here move along now!"
}
}
Component {
id: currentGroupList
SimpleRow {
id: container
readonly property var calendarUserItem: dataModel.calendarUserItems[departments[currGroupIndex]][modelData] || {}
visible: container.calendarUserItem.status === "pending" ? true : false
// only pending items visible
// remaining code for simple row
}
}
}
}
an example of JSON within my dataModel.calendarUserItems is:
"groupName": [
{ "department1":
{ "1555111624727" : {
"creationDate" : 1555111624727,
"date" : "2019-03-15T12:00:00.000",
"name" : "Edward Lawrence",
"status": "pending"
},
//several of these entries within department1
},
},
{ "department2":
{ "1555111624727" : {
"creationDate" : 1555111624456,
"date" : "2019-05-1T12:00:00.000",
"name" : "Katie P",
"status": 1
},
//several of these entries within department2
},
}
//departments 3 & 4 as the same
]
If departments 2 and 3 have modelData, yet 1 and 4 do not, I want the text to display instead, and the ListView emptied, instead of showing the previous modelData.
I have tried playing with the image/text visibility but the issue lays more with clearing the modelData and I'm unsure where to begin?
Any help is greatly appreciated!
I have achieved the display by using the following as my delegate:
delegate: {
if (!(departments[currGroupIndex] in dataModel.calendarUserItems) ) {
return emptyHol;
}
var subgroups = Object.keys(dataModel.calendarUserItems[departments[currGroupIndex]]);
for (var i in subgroups) {
var subgroup = dataModel.calendarUserItems[departments[currGroupIndex]][subgroups[i]];
modelArr.push(subgroup);
}
var modelObect = modelArr.find( function(obj) { return obj.status === "pending"; } );
if (modelObect === undefined) {
return emptyHol;
}
return currentGroupList;
}
Then when my AppButton.groupCycle is pressed, I have added modelArr = [] to clear the array on each press, this works as intended.
Thanks!
I am trying to implement a keyClick with a shift modifier but it doesn't work. Below is a basic setup of what I am trying to do. The first test_case1 can perform what I am doing but I'd like the second test_case2 to work as well but with using the Qt.ShiftModifier.
../MyTextBox.qml
Page {
id: page1
objectName: "page1"
TextField {
id: lastNameField
objectName: "lastNameField"
text: qsTr("")
}
}
tst_page.qml
import "../"
Item {
width: 800
height: 600
MyTextBox {
id: page1
}
TestCase {
id: "txtBox"
when: windowShown
function test_case1 () {
//var qmlObj = findChild(page1, "lastNameField")
var qmlObj = page1.lastNameField
// Bring to focus
mouseClick(qmlObj, Qt.LeftButton, Qt.NoModifier)
// Keypress
keyPress("Y")
keyPress("e")
keyPress("s")
tryCompare(qmlObj, "text", "Yes") // pass
}
function test_case2 () {
var qmlObj = page1.lastNameField
// Bring to focus
mouseClick(qmlObj, Qt.LeftButton, Qt.NoModifier)
// Keypress
keyClick(QT.Key_Y, Qt.ShiftModifier)
keyClick(QT.Key_E)
keyClick(QT.Key_S)
tryCompare(qmlObj, "text", "Yes") // fail
}
}
}
test output
PASS : test_case1()
FAIL! : test_case2()
Actual (): yes
Expected (): Yes
Edit: Added a simple project to github for testing.
This seems like a basic flow, but I am unable to find examples.
I have this custom component that loads a list of items from a backend service.
I tried writing this async code below, but I get 'Unexpected token' error in the browser at let.
import {customElement, bindable, inject} from 'aurelia-framework';
import {ItemsService} from 'Services/ItemsService';
#customElement('itemslist')
export class ItemsList {
static inject() { return [Element, ItemsService]; }
constructor(element, itemsService) {
this.element = element;
this.itemsService = itemsService;
}
async attached() {
let this.items = await this.itemsService.getItemList();
}
}
How should I do async work to load items and set it on my View-Model items property?
Thanks
let keyword is used to declare local variables, you can't use it before this. Just remove let. Declare your items property in constructor or with ES7 syntax.
Babel's async/await transformer must be enabled- Change this:
config.js
"babelOptions": {
"optional": [
"es7.decorators",
"es7.classProperties"
]
},
To this:
"babelOptions": {
"optional": [
"es7.decorators",
"es7.classProperties",
"es7.asyncFunctions"
]
},
Or this:
"babelOptions": {
"stage": 0
"optional": ["runtime"]
},
If I have a simple Binding object of the form:
Rectangle {
height: 400
width: 500
property var someObj: null
Binding on color {
when: someObj
value: someObj.color
}
}
Then I would expect that when someObj is not null, someObj's color property is bound to this object's color property. What I actually get is a runtime error message:
TypeError: Cannot read property 'color' of null
Any reason why this doesn't work?
Doing the almost equivalent JavaScript expression:
color: {
if ( someObj != null ) {
return someObj.color;
} else {
return "black";
}
}
Works as expected.
As mentioned in the comment by BaCaRoZzo, the minimal example given in the question does not work because it gives a ReferenceError: someObj is not defined. However, after fixing this with an id for the Rectangle, then the example actually works despite the TypeError:
Rectangle {
id: rect
height: 400
width: 500
property var someObj: null
Binding on color {
when: rect.someObj
value: rect.someObj.color
}
}
This correctly sets the color as expected when rect.someObj is set and contains a color property.
The reason for the TypeError is that the expression rect.someObj.color is evaluated already when the Binding is created (see QTBUG-22005).
So to prevent the TypeError, one can simply check for rect.someObj to be set in the value expression of the Binding:
Binding on color {
when: rect.someObj
value: rect.someObj ? rect.someObj.color : undefined
}
I would do it in the following way:
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
height: 400
width: 500
property var someObj
color: someObj ? someObj.color : "black"
Button {
id: buttonTest
text: "test"
onClicked: parent.someObj = test
}
Button {
id: buttonTest2
anchors.left: buttonTest.right
text: "test2"
onClicked: parent.someObj = test2
}
QtObject {
id: test
property color color: "red"
}
QtObject {
id: test2
property color color: "blue"
}
}
If someObj is undefined the color of the rectangle is black, if someObj is defined, the Value of the color property is chosen.
Edit: I've seen to late, that that's only what mlvljr suggested in the comments, sorry.
The QML syntax defines that curly braces on the right-hand-side of a property value initialization assignment denote a binding assignment. This can be confusing when initializing a var property, as empty curly braces in JavaScript can denote either an expression block or an empty object declaration. If you wish to initialize a var property to an empty object value, you should wrap the curly braces in parentheses.
For example:
Item {
property var first: {} // nothing = undefined
property var second: {{}} // empty expression block = undefined
property var third: ({}) // empty object
}
In the previous example, the first property is bound to an empty expression, whose result is undefined. The second property is bound to an expression which contains a single, empty expression block ("{}"), which similarly has an undefined result. The third property is bound to an expression which is evaluated as an empty object declaration, and thus the property will be initialized with that empty object value.
Similarly, a colon in JavaScript can be either an object property value assignment, or a code label. Thus, initializing a var property with an object declaration can also require parentheses:
Item {
property var first: { example: 'true' } // example is interpreted as a label
property var second: ({ example: 'true' }) // example is interpreted as a property
property var third: { 'example': 'true' } // example is interpreted as a property
Component.onCompleted: {
console.log(first.example) // prints 'undefined', as "first" was assigned a string
console.log(second.example) // prints 'true'
console.log(third.example) // prints 'true'
}
}
So the code should be as follow:
Rectangle {
height: 400
width: 500
property var someObj: ({color: ''})
Binding on color {
when: someObj.color
value: someObj.color
}
}