I'm encountering this error:
Unable to assign QList<QUrl> to QString
when trying to directly assign the result of drop.urls (obtained from DropArea's onDropped handler) to a Label's text property in Python.
Based on this doc, I tried Qt.resolvedUrl (to convert the type to a string) as shown in the following code. However, it results in an empty text label. The urls I'm working with start with "file:///".
What am I doing wrong?
import QtQuick.Window 2.2
import QtQuick 2.2
import QtQuick.Controls 2.14
Window {
id: root
visible: true
width: 640
height: 480
title: "Drop Test"
property var attachments: "empty"
DropArea {
id: dropArea;
anchors.fill: parent
onEntered: {
root.color = "gray";
console.log("You entered drop area")
drag.accept (Qt.LinkAction);
}
onDropped: {
console.log("You dropped " + drop.urls)
attachments = Qt.resolvedUrl(drop.urls)
}
}
Label {
id: mLableId
text: attachments
}
}
Assigning a URL to a string seems like such an obvious question, but if it has already been asked in the context of Python and Qt Quick, I have not find any such existing questions after searching since yesterday.
urls is a list of url so you will have to iterate and concatenate:
onDropped: {
console.log("You dropped " + drop.urls)
var str = ""
for(var i in drop.urls){
var url = drop.urls[i]
str += Qt.resolvedUrl(url)
}
attachments = str
}
Related
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
}
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.
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)
I want to write a QML app that adds the latest opened files from FileDialog to the main menu. I'm currently following this documentation example but the problem is that I can't understand how to pass the file name of an opened file.
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar : MenuBar
{
Menu
{
id: recentFilesMenu
Instantiator
{
model: recentFilesMenu
MenuItem
{
text: model.fileName // I neeed to pass name of opned file here
}
onObjectAdded: recentFilesMenu.insertItem(index, object)
}
title: "File"
MenuItem
{
text: "Open"
onTriggered: fileDialog.visible = true
}
MenuItem
{
text: "Exit"
}
}
}
FileDialog
{
id: fileDialog
title: "Oooopen"
onAccepted:
{
// Here is problem
recentFilesMenu.objectName = fileDialog.fileUrls
}
}
}
According to the documentation, Instantiator accepts the most common types of models - both C++ and QML ones. In the documentation example such an information is missing, probably to not force the usage of a specific one. An actual implementation can relay on ListModel. In this case the model would expose a fileName role used as the actual menu item.
Following this approach the result would be something like the following code. Mind that the urls are prepended with information which can be easily removed (see for instance this answer).
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar : MenuBar {
Menu {
id: recentFilesMenu
title: "File"
MenuItem {
text: "Open"
onTriggered: fileDialog.visible = true
}
MenuSeparator { }
Instantiator {
model: ListModel { id: files }
MenuItem { text: fileName }
onObjectAdded: recentFilesMenu.insertItem(index, object)
onObjectRemoved: recentFilesMenu.removeItem(object)
}
MenuSeparator { visible: files.count > 0 }
MenuItem { text: "Exit" }
}
}
FileDialog {
id: fileDialog
title: "Open"
onAccepted: {
for(var i = 0; i < fileDialog.fileUrls.length; ++i)
files.append({fileName: fileDialog.fileUrls[i]})
}
}
}
There is a widgets version of this kind of feature:
http://doc.qt.io/qt-5/qtwidgets-mainwindows-recentfiles-example.html
But the descriptive help is non-existent. Looking through the code here:
http://doc.qt.io/qt-5/qtwidgets-mainwindows-recentfiles-mainwindow-cpp.html
You will see that it stores a QStringList of a list of recent files in QSettings, and loads everything into an array of QActions.
Follow through the mainWindow.cpp for all the references to
enum { MaxRecentFiles = 5 };
QAction *recentFileActs[MaxRecentFiles];
And you should have some good ideas about how to do something similar in QML.
Hope that helps.
You probably have a finite number of recent files that you want to display. That being said, you can implement x number of MenuItems and set the text to QStringList[i] implemented as a Q_PROPERTY in a C++ class. Then, you can manipulate the QStringList elements(size, order) on your C++ class.
I have made a qml file named fileDialog.qml and which use element FileDialog{} available from qt5
http://qt-project.org/doc/qt-5.1/qtquickdialogs/qml-qtquick-dialogs1-filedialog.html.
Whenever i need the location of resource i want to use fileDialog.qml as component and set all the properties like title, filter etc. These are working fine but when i tried to use id.fileUrl then no response. details are given below.
The file fileDialog.qml is
import QtQuick 2.1
import QtQuick.Dialogs 1.0
FileDialog {
id: fileDialog
objectName: "fileBrowser"
title: "Add New Layer"
visible: false
property alias selectedFilename: fileDialog.fileUrls
onAccepted: {
console.log("You chose: " + fileDialog.fileUrls)//-------- (1)
}
onRejected: {
console.log("Canceled")
}
//Component.onCompleted: visible = true
}
Now using this as component when Browse(an item in QML to be used like button) button is clicked then i am performing following steps.
onClicked: {
//Default Values fileDialog.{selectExisting = true, selectFolder = false}
fileDialog.title = "Add New Image"
//fileDialog1.selectMultiple = true
fileDialog.nameFilters = ["Image File (*.png *.jpg *.bmp)"]
//fileDialog.fileUrls
//string path
fileDialog.visible = true
console.log(" Image chosen: " + fileDialog.fileUrl + " in image")//--- (2)
}
The line (1) is working fine but but (2) is not working. The output of (2) line in console is just Image chosen: in image.
I don't understand what am I doing wrong here, because when I am setting other(like title, filer) property of component fileDialog its working but not for the fileUrl or fileUrls.
Please somebody suggest how to get the fileUrl when using it as component.
Thanks,
I think what you're trying to do is creating a special FileDialog component that fits your needs.
First, let's call this component "MyFileFialog" saved in MyFileDialog.qml.
// MyFileDialog.qml
import QtQuick 2.0
import QtQuick.Dialogs 1.0
Item {
id: root
property alias title: qmlFileDialog.title
property alias fileUrl: qmlFileDialog.fileUrl
property alias fileUrls: qmlFileDialog.fileUrls
signal accepted()
signal rejected()
function open() { qmlFileDialog.open() }
function close() { qmlFileDialog.close() }
FileDialog {
id: qmlFileDialog
modality: Qt.WindowModal
nameFilters: ["Image File (*.png *.jpg *.bmp)"]
onAccepted: root.accepted()
onRejected: root.rejected()
}
}
There are now a bunch of connections between MyFileDialog and FileFialog:
modality and nameFilters are fixed
title, fileUrl, fileUrls are passed between both components
when the FileDialog is accepted or rejected, the MyFileDialog will be as well
when you open() or close() your MyFileDialog, the inner FleDialog will open or close
Now that you have your very own MyFileDialog, you can use it:
Button {
text: "open"
MyFileDialog {
id: saveFileDialog
title: qsTr("Save to ...")
onRejected: {
console.log("Canceled")
}
onAccepted: {
console.log("File selected: " + fileUrl)
}
}
onClicked: {
saveFileDialog.open()
}
}
Just a try. From documentation, you can see that fileUrl property is only set if you make a single file selection. So you are right to expect it to be setted by your FileDialog
The problem is that you try to display fileUrl before the FileDialog is closed I think.
Showing a modal dialog probably don't block your function execution.
As you do in your base component fileDialog.qml, you can put a handler on onAccepted. When your handler will be called, fileUrl property will be available.
edit:
Browse {
id: browser
signal fileChosen
FileDialog {
id: fileDialog
//configure your fileDialog here
//...
//emit parent signal when done
onAccepted: browser.fileChosen();
}
onClicked: {
fileDialog.open();
}
onFileChosen: {
//fileUrl should be available here
console.log(fileDialog.fileUrl);
}
}
I got a solution after reading the answer of #jbh whose answer helps in accessing fileUrl outside FileDialog{} element in same qml file but that's not an answer to my question.
Element FileDialog{} have an signal accepted so we use this signal to connect to a method and then access the fileUrl or Urls. This how fileBrowser.qml look like.
import QtQuick 2.1
import QtQuick.Dialogs 1.0
FileDialog {
id: fileDialog
objectName: "fileBrowser"
title: "Add New Layer"
visible: false
//property alias selectedFilename: fileDialog.fileUrls
// signal fileChosen
// onAccepted: {
// console.log("You chose: " + fileDialog.fileUrls)
// fileChosen();
// }
onRejected: {
console.log("Canceled")
}
//Component.onCompleted: visible = true
}
onAccepted slot above is commented and we use accepted signal to access fileUrl.
This is how a qml file will look when using fileBrowser.qml as component whenever file dialog is required.
Item{
id: popup
Rectangle{
id: browse button
// properties setting for construction a button such as width, color, mouse area, states, etc..
// the method where we can use the URLS
function dialogAccepted(){
fileDialog.accepted.disconnect(dialogAccepted)
filePath.text = Qt.resolvedUrl( fileDialog.fileUrl ).toString()// to set the text in text field
console.log("You Chose in elev: " + fileDialog.fileUrl)
//browseButtonClicked(checkBox.checked)
}
onClicked: {
//Default Values fileDialog.{selectExisting = true, selectFolder = false}
fileDialog.title = "Add New Image"
//fileDialog1.selectMultiple = true
fileDialog.nameFilters = ["Image File (*.png *.jpg *.bmp)"]
fileDialog.visible = true
fileDialog.accepted.connect(dialogAccepted)
}
}
}
well, this worked for me but i am still facing problem with how to resolve Url when multiple file is selected and how to send it to c++ file so that it is accepted.