Dynamically bind values from qml to repeater created object - qt

I control the position of some elements of my scene using alias properties likes this : If I have a file Foo.qml containing
Item {
property alias myprop1: id1
property alias myprop2: id2
Node {id:id1,...}
Node {id:id2,...}
On my main, I can then call
Slider{
id:myslider
}
foo{
myprop1.x: myslider.value
}
Now if my Foo.qml contains an unknow number of properties (lets say they are all called mypropX). If I have 10 properties I want to create 10 sliders, one for each property. It is possible with a repeater and loop like mentioned in last answer here
Foo{
id:myfoo
}
Column {
Repeater {
id: myrepeater
delegate: Slider {
from:0
to:400
y: 12*index
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo){
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
The problem is now that I don't know how to bind those 10 sliders to my properties.
I tried adding to my Foo instance in main
Component.onCompleted: {
let i=0
for(var prop in myfoo){
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
//equivalent to myprop1.x: myslider.value when there was no repeater
myfoo.prop.x = Qt.binding(function() {
return myrepeater.itemAt(i).value
})
i++
}
}
}
But it return
QQmlEngine::setContextForObject(): Object already has a QQmlContext
qrc:/main.qml:145: Error: Cannot assign to non-existent property "prop"
The problem is that in the for loop, prop is a string. I am also not sure that at the moment the onCompleted is executed, the repeater has already created all the slidders.
I could use the QML type Bindings{} which takes a target (myrepeater.itemAt(i).value) and the property name as a string, but I don't know how to call the Bindings{} type from javascript

You can use the [] operator to read the properties from myfoo and as discussed I would use a Binding object inside the delegate:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
id: myfoo
property int myprop_upper_threshold
onMyprop_upper_thresholdChanged: console.log("upper_threshold", myprop_upper_threshold)
property int myprop_lower_threshold
onMyprop_lower_thresholdChanged: console.log("lower_threshold", myprop_lower_threshold)
}
ColumnLayout {
Repeater {
id: myrepeater
delegate: Slider {
id: myslider
from: 0
to: 400
Text {
text: modelData
}
Binding {
target: myfoo
property: modelData
value: myslider.value
}
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo)
{
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed")
{
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
}

Related

QML GridView hide one cell

It's possible to hide certain cell in GridView? I set delegate, but I still got empty place for this GridView element. It's possible to do this?
visible: false
width: 0
height: 0
As was said in the comment, you can indeed use a QSortFilterProxy model, but here is another solution. You could implement a pure-QML FilterProxyModel, using DelegateModel and DelegateModelGroup
import QtQuick 2.10
import QtQml.Models 2.3
DelegateModel {
property var filterAccepts: function(item) {
return true
}
onFilterAcceptsChanged: refilter()
function refilter() {
if(hidden.count>0)
hidden.setGroups(0, hidden.count, "default")
if(items.count>0)
items.setGroups(0, items.count, "default")
}
function filter() {
while (unsortedItems.count > 0) {
var item = unsortedItems.get(0)
if(filterAccepts(item.model))
item.groups = "items"
else
item.groups = "hidden"
}
}
items.includeByDefault: false
groups: [
DelegateModelGroup {
id: default
name: "default"
includeByDefault: true
onChanged: filter()
},
DelegateModelGroup {
id: hidden
name: "hidden"
}
]
}
Explanation: Every time an item is added to the model, it is added in the "default" group, which triggers the onChanged handler that will call filter().
Filter() will look for items in the default group, and move them either in the items group (which will make them visible) or to the hidden group, depending on the result of the filterAccepts function.
When filterAccept changes, the SortProxyModel will move every item to the default group to trigger a global refiltering.
You can then use your proxy model like this:
FilterProxyModel
{
id: filterProxyModel
model: <YourBaseModel>
delegate: <YourDelegate>
filterAccepts: function(item) {
// Eg: Only "small" items will be displayed
return item.size == "small"
}
}
GridView
{
anchors.fill: parent
model: filterProxyModel
cellHeight: 100
cellWidth: 100
}
Another simplified solution with QML only, based on hiding items.
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.2
Window {
id: window
title: "test"
visible: true
width: 400
height: 400
GridLayout {
id: layout
anchors.fill: parent
columns: 4
Repeater {
id: container
model: 20
Rectangle {
id: item
property int itemIndex: index
Layout.fillWidth: true
height: 60
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
Text {
anchors.centerIn: parent
text:item.itemIndex
}
MouseArea {
anchors.fill: parent
onClicked: {
item.visible = false;
layout.doIt(item.itemIndex);
}
}
}
}
function doIt(index)
{
var item = container.itemAt(index);
if(item)
item.visible = false;
for(var i = index - 1;i >= 0;i --)
{
var prev_item = container.itemAt(i);
if(prev_item.visible) {
prev_item.Layout.columnSpan ++;
break;
}
}
}
}
}

How i can save into JSON file a QML list model? [duplicate]

I am able to save settings for list items which is statically created using Component.onComponent method. But Settings for statically created list items take affect after reopening app. I would like to save settings for dynamically created list model. I am unable to save Settings for a dynamically created list item. The code below does that a list item is on and off while clicking Show/Hide action. When I reopen the app, created list item disappears. How to save list item using Setting?
import QtQuick 2.9
import Fluid.Controls 1.0
import Qt.labs.settings 1.0
import QtQuick.Controls 1.4
ApplicationWindow {
id:root
visible: true
width: 640
height: 480
property variant addlist
property int countt2: 0
Settings{
id:mysetting4
property alias ekranCosinus: root.countt2
}
function listonoff(){
if(countt2%2==1){
return true
}
else if(countt2%2==0){
return false
}
}
Connections {
target: addlist
onTriggered: listonoff()
}
addlist: favourite2
/* main.qml */
menuBar: MenuBar {
Menu {
title: "&Edit"
MenuItem { action: favourite2 }
}
}
Action {
id:favourite2
text: qsTr("Show/Hide")
onTriggered: {
countt2++
console.log(countt2)
if(listonoff()===true){
return list_model.insert(list_model.index,{ title: "First item."} )
}
else if(listonoff()===false){
return list_model.remove(list_model.index)
}
}
}
ListView {
id:contactlist
width: parent.width
height: parent.height
focus: true
interactive: true
clip: true
model: ListModel {
id:list_model
}
delegate: ListItem {
text: model.title
height:60
}
}
MouseArea {
id: mouse
anchors.fill: parent
}
}
Quite curious that you expect that saving a single integer value will somehow be able to store the content of an arbitrary data model... It doesn't work even for the static model data, it is only "restored" because it is static - it is part of the code, you are not really saving and restoring anything.
If you want to store all that data, you will have to serialize it when your app quits, and deserialize it when the app starts.
You could still use Settings, but to store a string value, that will represent the serialized data.
The easiest way to do it is to transfer the model items back and forth with a JS array, this way the JS JSON object functionality can be used to easily serialize and deserialize the data:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Window 2.3
import Qt.labs.settings 1.0
ApplicationWindow {
id: main
width: 640
height: 480
visible: true
property string datastore: ""
Component.onCompleted: {
if (datastore) {
dataModel.clear()
var datamodel = JSON.parse(datastore)
for (var i = 0; i < datamodel.length; ++i) dataModel.append(datamodel[i])
}
}
onClosing: {
var datamodel = []
for (var i = 0; i < dataModel.count; ++i) datamodel.push(dataModel.get(i))
datastore = JSON.stringify(datamodel)
}
Settings {
property alias datastore: main.datastore
}
ListView {
id: view
anchors.fill: parent
model: ListModel {
id: dataModel
ListElement { name: "test1"; value: 1 }
}
delegate: Text {
text: name + " " + value
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
var num = Math.round(Math.random() * 10)
dataModel.append({ "name": "test" + num, "value": num })
} else if (dataModel.count) {
dataModel.remove(0, 1)
}
}
}
}
The application begins with a single data model value, more data items can be added or removed by pressing the left and right mouse button respectively.
As long as the application is closed properly, the data model will be copied into an array, which will be serialized to a string, which will be stored by the Settings element. So upon relaunching the app, if the data string is present, the model is cleared to remove the initial value so it is not duplicated, the data string is deserialized back into an array, which is iterated to restore the content of the data model. Easy peasy.
Of course, you could also use the LocalStorage API as well, or even write a simple file reader and writer by exposing a C++ object to QML. All this approach needs is to be able to store and retrieve a single string.

Binding specific item of a ListModel to a Component

I am building an application with HsQML. This is my first encounter with QML, my second ever work in Qt, and first larger project with Haskell, so forgive my ignorance.
In the UI, I have a TabView. The first Tab contains a ListView which is bound to a model and displays a list of items. Double-clicking an item in the ListView opens a new tab with a component which correctly shows that item's details (my guess is by virtue of the new tab inheriting its context from the list item that was clicked).
Now, my objective is to open a tab in which to create a new item for that model. The idea is to create a blank data item (optionally adding it to the model), and "load" this into the same component type used for editing existing items. I scoured QML's documentation and could not find anything even remotely related, which makes me think the approach is completely flawed.
TabView {
id : rootTabs
Tab {
ListView {
model : AutoListModel {
source : workflowModel // this is sort of HsQML specific, data comes as a list from Haskell
}
delegate : Rectangle {
Text {
text : modelData.name
}
MouseArea {
anchors.fill : parent
// this part works because the new component inherits its modelData from the current context
// so the new tab has correct data
onDoubleClicked : {
rootTabs.addTab(modelData.name, Qt.createComponent("WorkflowView.qml"))
rootTabs.currentIndex = rootTabsCount - 1
}
}
}
}
Button {
text : "Create workflow"
// this is the part in question - how do I assign the newly appended data to comp?
onClicked : {
wModel.appendBlank()
comp = Qt.createComponent("WorkflowView.qml")
var tab = rootTabs.addTab("New workflow", comp)
comp.statusChanged.connect(tabLoaded)
}
}
}
}
WorkflowEdit.qml:
Rectangle {
TextField {
id : nameInput
text : modelData.name
Binding {
target : modelData
property : "name"
value : nameInput.text
}
}
}
I think I have what you're looking for. It was a little tricky because Tab are essentially loaders. It was a matter of creating an extra property for the Tab QML type as a place to store a model index. And since tabs are simply children of a TabView, new tabs can be parented to the TabView instead of using the addTab() method. Note that for my model I used a ListModel.
main.qml
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
TabView {
id : rootTabs
anchors.fill: parent
ListModel {
id: listModel
ListElement { car: "Toyota" }
ListElement { car: "Chevrolet" }
ListElement { car: "Honda" }
ListElement { car: "Daihatsu" }
ListElement { car: "Ford" }
ListElement { car: "Nissan" }
ListElement { car: "Hyundai" }
ListElement { car: "Acura" }
}
MyTab {
title: "Default"
Item {
ListView {
id: listView
anchors { fill: parent; bottomMargin: 240 }
model : listModel
delegate : Rectangle {
width: parent.width
height: 40
Text {
text : car
color: "black"
font.pointSize: 20
}
MouseArea {
anchors.fill : parent
onDoubleClicked : {
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": car, "modelIndex": index, "sourceComponent": workflow });
rootTabs.currentIndex = rootTabs.count - 1
}
}
}
}
Button {
anchors {fill: parent; topMargin: 240 }
text : "Create workflow"
onClicked : {
listModel.append( { "car" : "New car" } )
var myTab = Qt.createComponent("MyTab.qml")
var workflow = Qt.createComponent("Workflow.qml")
myTab.createObject(rootTabs, { "title": "New Workflow", "modelIndex": listModel.count - 1 , "sourceComponent": workflow });
}
}
}
}
}
}
MyTab.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Tab {
property int modelIndex
}
Workflow.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
Rectangle {
TextField {
id : nameInput
text : listModel.get(modelIndex).car
onTextChanged: {
// Update model using modelIndex. Observe updates in listview
listModel.set(modelIndex, { "car" : text })
}
}
}
TabView::addTab returns a Tab object, which is basically a Loader object. Loader::item is the current loaded object. So, the solution is to add an new empty model data to the tab as follows (in Button::onClicked):
var tab = ...
tab.loaded.connect(function () {tab.item.data = newModelData;}); // newModelData = wModel.appendBlank() ???
And you should add the property modelData explicitly to WorkflowEdit.qml:
Rectangle {
property var data: modelData // create property data and assign the context variable modelData to it by default
TextField {
id : nameInput
text : data === undefined ? "" : data.name
Binding {
target : data
property : "name"
value : nameInput.text
}
}
}

how to implement search function in Qt Quick folderListmodel ??

I want implement search functioning to my music player totally written in Qml
. In my case i initiated a qml Filedialog to get folder from filesystem and then i used folderListModel to list them through ListView .
I want search through the list any clue how can i achieve this ????
Please don't suggest to use c++ . and also not suggest me to use nameFilters :["*."] in foldelistmodel cause it wont work it only filter according to extension of file not the file name
Actually nameFilters does allow to filter by filename. Using a kind of hack, it is even possible to make it case insensitive.
Here is an ugly but working example:
import QtQuick 2.3
import QtQuick.Controls 1.2
import Qt.labs.folderlistmodel 2.1
Item {
width: 300
height: 300
FolderListModel
{
id: folderListModel
}
function updateFilter()
{
var text = filterField.text
var filter = "*"
for(var i = 0; i<text.length; i++)
if(!caseSensitiveCheckbox.checked)
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
else
filter+= text[i]
filter+="*"
print(filter)
folderListModel.nameFilters = [filter]
}
Row
{
spacing: 5
Text {text:"Filter"}
TextField
{
id: filterField
onTextChanged: updateFilter()
}
Text {text:"Case Sensitive"}
CheckBox
{
id: caseSensitiveCheckbox
checked: false
onCheckedChanged:updateFilter()
}
}
ListView
{
anchors.fill: parent
anchors.topMargin: 30
model:folderListModel
delegate: Text{text: model.fileName}
}
}
Use DelegateModel :
with DelegateModelGroup to sort and filter delegate items.
Assume that you have a function to filter files using filename,
function willBeShownOnView(filename){ /* ... */ }
You can extend this function by passing more roles (fileSize, fileIsDir, ...) or filter string entered by user if you need, and implement the filtering logic within this function.
Next, create a DelegateModel with a filterGroup:
DelegateModel {
id: delegateModel
model: FolderListModel{id: folderModel}
groups: [
DelegateModelGroup {
name: "filterGroup"; includeByDefault: true
}
]
filterOnGroup: "filterGroup"
delegate: MyFileDisplayComponent{/* ... */}
function applyFilter(){ /* see below */}
}
As includeByDefault: true, all items in folderModel are included in the filterGroup. And when we applyFilter, some items should be removed from this group. For example,
function applyFilter(){
var numberOfFiles = folderModel.count;
for (var i = 0; i < numberOfFiles; i++){
var fileName = folderModel.get(i, "fileName");
if (willBeShownOnView(fileName)){items.addGroups(i, 1, "filterGroup");}
else {items.removeGroups(i, 1, "filterGroup");}
}
}
After applyFilter is called, only files that passes willBeShownOnView is added to the filterGroup. And the property filterOnGroup: "filterGroup" says the delegate model contains only items within the filterGroup. Therefore, we can use a simple ListView to display the result:
ListView {
model: delegateModel
//...
}

QML XmlListModel messes sorting when delegated

I'm trying to get a Canvas to draw lines in the same order as presented in this xml-file:
<root>
<doc><nopeus>80.0</nopeus><aika>40.0</aika></doc>
<doc><nopeus>110.0</nopeus><aika>80.0</aika></doc>
<doc><nopeus>120.0</nopeus><aika>120.0</aika></doc>
<doc><nopeus>190.0</nopeus><aika>160.0</aika></doc><doc><nopeus>243.0</nopeus><aika>200.0</aika></doc><doc><nopeus>260.0</nopeus><aika>240.0</aika></doc><doc><nopeus>300.0</nopeus><aika>280.0</aika></doc><doc><nopeus>350.0</nopeus><aika>320.0</aika></doc>
</root>
QML-file with XmlListModel:
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtQuick.XmlListModel 2.0
Page {
id: page
property alias startx : coords.mX
property alias starty : coords.mY
Item {
id: coords
property int mX: 0
property int mY: 0
}
XmlListModel {
id: myxml
source: "/home/nemo/filename.xml"
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
}
ListView {
model: myxml
anchors.fill: page
delegate:
Item {
Chart {
xc: coords.mX;
yc: coords.mY;
xd: aika;
yd: nopeus;
}
}
}
}
Chart.qml:
import QtQuick 2.0
Rectangle {
id: myrect
width: 540
height: 960
color: "transparent"
property int xd: 0
property int yd: 0
property int xc: 0
property int yc: 0
Canvas {
id: mycanvas
width: myrect.width; height: myrect.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(xc,yc)
context.lineTo(xd,yd)
context.stroke()
startx = xd
starty = yd
}
}
}
The question is why is the resulting path messed up when inserted in the ListView via delegates? I have tried to sort the path items separately, via a function and another ListModel but the result is the same.
Here is a screenshot:
Delegates are created for each item in a model. Your model contains eight items (as of your input). Hence, you create eight Canvases (each one as a ListView item, i.e. at (theoretical) increasing y w.r.t. ListView origin coordinates).
Combile these problems with the (probably wrongly set) starting points...and you get a random mess! You can't see that, since the Canvases tend to overlap due to sizing/constraints set on the component.
In this case you just need one Canvas on which each myxml item is painted. Here is a (naive) adaptation of your code which correctly shows the path stored in the xml file:
// main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.XmlListModel 2.0
Window {
visible: true
width: 600
height: 600
XmlListModel {
id: myxml
source: "qrc:/filename.xml" // added to the resources
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
onStatusChanged: {
if(status === XmlListModel.Ready)
comp.mod = myxml // set the model ASA it is ready to be used
}
}
Chart {
id: comp
anchors.fill: parent
mod: myxml
}
}
// Chart.qml
import QtQuick 2.4
import QtQuick.XmlListModel 2.0
Item {
property var mod: undefined
onModChanged: {
if(mod)
mycanvas.requestPaint() // repaint only when the model is available
}
Canvas {
id: mycanvas
width: parent.width; height: parent.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(0,0)
// iterate over all the point to print them
for(var i = 0; i < mod.count; i++)
{
var point = mod.get(i)
context.lineTo(point.aika, point.nopeus)
}
context.stroke()
}
}
}
The resulting path rendered:

Resources