how to implement search function in Qt Quick folderListmodel ?? - qt

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
//...
}

Related

Dynamically bind values from qml to repeater created object

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
}
}
}
}

QML prevent ListView delegate to update all the time

I'm relatively new to QML/QtQuick and still learning. I have a little performane issue with a very small private project. I just tryed to implement a filter function to my ListView, because >15.000 objects are a lot to search manually. I just want to update the ListView when I finished the editing of my search field or pressing "return". But instead it's refreshing every time I insert or delete a character from this textfield which needs sometimes a few seconds.
Anyone have an idea how to prevent the list to be refreshed permanently or reducing theese performance issues?
Thanks a lot
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.XmlListModel 2.12
import Anime_initialiser 1.0
import "."
Page {
TextField{
id: searchField
width: parent.width
z: 1
/*onEditingFinished: {
XL.animeListModel.reload()
}*/
}
ListView {
z: 0
ScrollBar.vertical: ScrollBar { active: true }
id: listView
width: parent.width
height: parent.height
model: XL.animeListModel
y: searchField.height
Anime_initialiser {
id: initialiser
onShowAnimeDetails: {
xmlDataString = xmlString
swipeView.currentIndex = swipeView.currentIndex+1
}
}
delegate: ItemDelegate {
visible: {
if (searchField.length > 0)
return (main_title.toLowerCase().match(searchField.text.toLowerCase()) || de_title.toLowerCase().match(searchField.text.toLowerCase())) ? true : false
else
return true
}
height: visible ? Button.height : 0
width: parent ? parent.width : 0
Button {
anchors.fill: parent
onClicked: {
anime_id = aid
initialiser.buttonClicked(anime_id)
}
Text {
width: parent.width
height: parent.height
font.pointSize: 100
minimumPointSize: 12
fontSizeMode: Text.Fit
text: aid + ": " + main_title + (de_title ? "\nDE: " + de_title : "")
}
}
}
}
}
Rather than toggling the visible flag of all of your delegates, you should use a QSortFilterProxyModel. The idea is that the proxy model would use your XL.animeListModel as a source model, and then you can give the proxy a regular expression telling it which ones to filter out. Depending on how you want it to filter, you could just call setFilterRole() to tell it which property to compare against your regex, or you could do a custom filter by overriding the filterAcceptsRow() function.
EDIT:
If you don't want to use a proxy, you can still prevent the constant updates by not binding on the visible property directly to the search field. You were on the right track with your onEditingFinished code. You could create a separate text string that just holds the completed search text.
property string searchText: ""
Then update that string when you are done typing your search text.
onEditingFinished: {
searchText = searchField.text.toLowerCase();
}
And finally, bind your visible property to this new string.
visible: {
if (searchText.length > 0)
return (main_title.toLowerCase().match(searchText) || de_title.toLowerCase().match(searchText)) ? true : false
else
return true
}

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.

how to regenerate an UI stochastically in QML using a Button on the interface?

I am working on the following QML framework for music apps user-interface prototyping:
https://github.com/tiagmoraismorgado/TMM_QML_UI_UX_FRAMEWORK_WIP in the file instiationTest, i need to add a type Button1_1 and then use it to regenerate the whole gui based on combostochasticselector.
i know i have to instantiate the randomPicking() function, from the instantiationtest file, on top of the mouseArea Pressed statement of Button1_1, but i am not being able to do so.
if anyone could provide me any insight i would be pretty thankful
here is the code of the main file
(...)
Window {
id: root
width: Screen.width
height: Screen.height
visible: true
visibility: "FullScreen"
title: qsTr("instantiationTest")
color: "black"
CombosStochasticSelector {}
}
on the combostochasticselector, you can see the following:
Item {
property var model: model1
anchors.fill: parent
id: randomMIDIkeyboardSelector;
property var random: 0;
function randomSelection(min, max) {(...)}
function createMidiKeyboard(itemToBeInstantiated) {
var component = Qt.createComponent(itemToBeInstantiated)
if (component.status === Component.Ready) {
var midiKeyboard = component.createObject(randomMIDIkeyboardSelector, {model: model});
}
else if (component.status === Component.Error) {
}
}
function randomPicking() {
random = parseInt(randomSelection(1, 13));
if(random == 1) {createMidiKeyboard("./../_Combos/Combo1.qml");}
(...)
return random;
}
Component.onCompleted: {
randomPicking();
}
}
on the Button1_1 you can see the following:
import QtQuick 2.6
QMLOpenGLToggleButtonPrimitive {}
It instantiates a press released kind of button made out of Rectangle QML Primitives
kind regards
Tiago

Resources