Load all QML's from folder - qt

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

Related

QML variable scope issues

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();

Qt QML ComboBox override wheel events

Is there any way to override ComboBox MouseArea to ignore wheel event instead of changing current index? ComboBox itself has no option to change wheel focus behaviour. So far I've tried to override onWheel from CB MouseArea with code like this:
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
console.log(combobox_ctrl.children[i])
console.log(combobox_ctrl.children[i].hasOwnProperty('onWheel'))
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')) {
console.log(combobox_ctrl.children[i]['onWheel'])
combobox_ctrl.children[i]['onWheel'] = function() { console.log("CB on wheel!") }
//combobox_ctrl.children[i]onWheel = function() { console.log("CB on wheel!")
//combobox_ctrl.children[i].destroy()
}
}
}
}
But I get
TypeError: Cannot assign to read-only property "wheel"
Did anyone was able to disable wheel events on ComboBox in Qml?
// EDIT
for example in Slider control I was able to remove wheel event handling like this:
Slider {
Component.onCompleted: {
for (var i = 0; i < slider.children.length; ++i) {
console.log(slider.children[i])
if (slider.children[i].hasOwnProperty("onVerticalWheelMoved") && slider.children[i].hasOwnProperty("onHorizontalWheelMoved")) {
console.log("Found wheel area!")
slider.children[i].destroy()
}
}
}
}
But in slider WheelArea is not responsible for handling "click" events.
You can place MouseArea over ComboBox and steel wheel event.
ComboBox {
anchors.centerIn: parent
model: [ "Banana", "Apple", "Coconut" ]
MouseArea {
anchors.fill: parent
onWheel: {
// do nothing
}
onPressed: {
// propogate to ComboBox
mouse.accepted = false;
}
onReleased: {
// propogate to ComboBox
mouse.accepted = false;
}
}
}
It's not currently possible, as ComboBox is not derived from MouseArea, but FocusScope, which has no support for these kinds of events.
A similar problem was mentioned in a suggestion recently:
Disable mouse wheel scroll event on QtQuick.Controls
If you're after a hacky way of doing it, it seems like the only option you have left is to apply a patch to ComboBox.qml that removes the onWheel handler:
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 4e29dfe..3413cac 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
## -407,13 +407,6 ## Control {
popup.toggleShow()
overridePressed = false
}
- onWheel: {
- if (wheel.angleDelta.y > 0) {
- __selectPrevItem();
- } else if (wheel.angleDelta.y < 0){
- __selectNextItem();
- }
- }
}
Another alternative that doesn't involve modifying Qt code would be to add an intermediate MouseArea above ComboBox's, and then somehow only forward specific events through to ComboBox's MouseArea. Or, create a custom C++ item that does the equivalent. You may have more control that way.
Ok. After hacking around I've managed to come with solution that is acceptable for me but may introduce some regressions in some situations. pressed and hovered properties are no longer usable
import QtQuick.Controls.Private 1.0
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel') && combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
if (!Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
if (Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
}
}
This way we can mimic mouse area that was originaly inside the ComboBox. Popup is shown as it was (at least I didn't see any regresion in it yet). However two properties are inaccesible right now
I created a separate file called NonScrollingComboBox.qml with the following code following this post: https://stackoverflow.com/a/33080217/969016
Now I can just use NonScrollingComboBox as a component instead of ComboBox on places where I don't want the mouse scroll to change the value
import QtQuick 2.0
import QtQuick.Controls 1.4
ComboBox {
id: combobox_ctrl
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')
&& combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
combobox_ctrl.__popup.toggleShow()
}
}
}
usage:
NonScrollingComboBox {
anchors.verticalCenter: parent.verticalCenter
model: ["item one", "item 2"]
}
This seems to apply only to Qt Quick Controls 1 ComboBox. On Qt Quick Controls 2 ComboBox the wheel mouse event is not enabled by default and can be enabled manually by setting to true the property wheelEnabled (documented in the base class Control). Also the combobox won't keep a "focus" on mouse events so you can freely use the wheel on other mouse areas by just entering them.

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 drag a copy of an Item through Drag and DropArea mechanism in QML?

Hello,
I'm using Qt 5.2.1 for Android on windows
I've gone through the different drag and drop examples provided with Qt, but non of them address my problem, although I found This Example copy Text via drag and drop, but I want to copy a whole Item.
Note: I don't want external copy, just inside the application.
Thank you
Am a noop, this is just an idea,
Create a new Object ( same as your dragged Item )
Copy the Dragged item properties into the newly created object
Destroy the source
lets say you have and Item:
// DraggedItem.qml
import QtQuick 2.0
Item {
id:draggedItem
property string itemText
property string imageSource
Image {
id: img
source: imageSource
}
Text {
id: txt
text: qsTr("text")
}
}
Then you can have your drop area like this:
import QtQuick 2.0
Item {
id:dropItem
DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
if (drop.source.parent !== draggedItem) {
// Create new Item
var newItem = createNewItem();
// Copy the source item properties into the new one
newItem.width = parent.width;
newItem.height = drop.source.height;
newItem.itemText = drop.source.itemText
newItem.imageSource = drop.source.imageSource
// destroy the source
drop.source.destroy();
}
}
}
function createNewItem() {
var component = Qt.createComponent("DraggedItem.qml");
if (component.status === Component.Ready)
{
var newItem = component.createObject(parent, {"x":0,
"y":0});
if (newItem === null)
console.log("Error creating new Item");
}
return newItem
}
}
This code is not tested. There should be a better way to do this

Reject external files dragged in a DropArea without breaking the DropArea

In my application I'm displaying a list of audio files and the user can drag an external file to add it to the list. I want to be able to refuse the drag if no file in the list is supported by my application.
The issue is that when I call drag.accepted = false; in onEntered of my DropArea then it becomes completely unresponsive to any other event.
Here is some sample code showing the issue. If you drag an MP3 in the window you see that it works. Then if you drag any other file it won't work, as expected. But then dragging an MP3 file back will not work either.
import QtQuick 2.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
DropArea {
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
var validFile = false;
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
break;
}
}
if(!validFile) {
console.log("No valid files, refusing drag event");
drag.accepted = false;
return false;
}
}
onExited: {
console.log("[Droparea] entered");
}
onDropped: {
console.log("[Droparea] dropped");
}
// Only MP3s
function validateFileExtension(filePath) {
var extension = filePath.split('.').pop();
var valid = false;
if(extension == "mp3") {
valid = true;
}
return valid;
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
Is there a bug in the DropArea or did I misunderstood something? I know I can filter the files in the onDropped but then you loose the visual feedback you get on OSX when dragging file on an area that does not accept them.
It has been a known bug for a long time. A patch has been submitted and after been stalled for several months is now merged into 5.6 branch.
Anyone who wants to use this functionality MUST upgrade to Qt 5.6 or MANULLY integrate the available patch into his/her Qt version.
QQuickDropAreaPrivate, contained in DropArea, updates the containsDrag flag to true when a dragEnterEvent occurs, emitting the entered signal. It updates containsDrag to false when adragLeaveEvent occurs, emitting an exited signal. However, when the drag event is not accepted dragLeaveEvent is never called, leaving the private object in a incosistent state. Each subsequent dragEnterEvent is discarded since containsDrag is still true, i.e. the previous drag event is still considered active and the entered is no more emitted.
Since the issue is related to an interaction between private APIs and usage of the public APIs, the problem does not affect filtering using keys. Unfortunately, this approach does not seem to fit for the presented use case.
A quite partial workaround is to use a MouseArea along with the DropArea. The latter disables itself when a rejection occurs while the former enables back the DropArea for the next drop. This workaround covers the common case in which a wrong item is dropped inside the DropArea, which is the most common and intuitive for an end user. Releasing the wrong item outside the DropArea invalidate the mechanism (until the next drop).
Here's the code:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: !drop.enabled
onContainsMouseChanged: drop.enabled = true
}
DropArea {
id: drop
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
for(var i = 0; i < drag.urls.length; i++)
if(validateFileExtension(drag.urls[i]))
return
console.log("No valid files, refusing drag event")
drag.accept()
drop.enabled = false
}
onExited: console.log("[Droparea] exited")
onDropped: console.log("[Droparea] dropped")
// Only MP3s
function validateFileExtension(filePath) {
return filePath.split('.').pop() == "mp3"
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
you never put accepteed = true
just add drag.accepted = true after you set the valid as valid
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
drag.accepted = true;
break;
}
}

Resources