There is a Q_PROPERTY in my registered C++ class:
// C++ Class
Q_PROPERTY(bool inProgress READ inProgress WRITE setInProgress NOTIFY inProgressChanged)
... based on which I intend to show a QML popup:
Popup {
id: popup
visible: cppClass.inProgress // Bind visibility to C++ Q_PROPERTY
}
But the pop doesn't show up. If change visible to true the popup is always shown of course.
Tried so far
Tried to use signal/slot connections to open/show the popup, but doesn't work:
Popup {
id: popup
visible: false
Connections {
target: cppClass
onInProgressChanged: {
if (cppClass.inProgress) {
console.log("open ...") // This text is logged correctly
popup.visible = Qt.binding(function(){return true}) // popup is NOT shown
popup.open() // popup is NOT opened
} else {
console.log("close ...")
popup.visible = Qt.binding(function(){return false})
popup.close()
}
}
}
}
What am I missing?
The code worked by triggering C++ signals further away from CPU-blocking operation:
Popup {
visible: cppClass.inProgress
}
QCoreApplication::processEvents();
cppClass->setInProgress(true);
QCoreApplication::processEvents();
// Dot not trigger signal immediately before heavy duty logic
//
// Do some less heavy statements
// before CPU-blocking operation
// to make sure QML UI engine receives signals
// and has time to show the GUI changes
// CPU-blocking operation
heavyDutyLogic.run();
QCoreApplication::processEvents();
cppClass->setInProgress(false);
QCoreApplication::processEvents();
Related
I need to perform some actions in let's say main.qml according to button press in button.qml. Button inside of the button QML is inside of some custom object. Let's give it a name customObject. So customObject in button.qml looks like this:
customObject {
id:trialObject
signal backPagePressed()
Button {
id:button1
MultitouchArea {
onPressed:
{
trialObject.backPagePressed()
}
Now when I press the button, it emits backPagePressed(). My question is: How can I make a slot for this signal in main QML? I'm familiar to signal and slot mechanism in C++, but that does not work for QML. I made something like this in main.qml:
Loader
{
id:pageLoader
onBackPagePressed:
{
pageLoader.source =""
}
}
That part needs to delete the Loader's source so that it will go back to page before. However, I'm not sure about onBackPagePressed: ... How can I connect my signal backPagePressed, to the related part in my loader?
You should use a Connections object (documented here) together with the item property of the Loader:
Loader {
id: pageLoader
source: "CustomObject.qml"
}
Connections {
target: pageLoader.item
//Qt < 5.15
onBackPagePressed: pageLoader.source = ""
//Qt >= 5.15
function onBackPagePressed()
{
pageLoader.source = ""
}
}
I hava a QML button, and I want to prevent the button to be clicked before my function call is finished.
I tried the code below and it's not working. Button does not toggle enabled/disabled within the clicked signal. I guess the binding does not take effect immediately.
import QtQuick
import QtQuick.Control
Button {
onClicked: {
console.log('clicked')
this.enabled = false
do_something()
this.enabled = true
}
}
I also tried pressed signal
Button {
onPressed: {
console.log('pressed')
this.enabled = false
}
onClicked: {
console.log('clicked')
do_something()
this.enabled = true
}
}
But the onClicked signal is not triggered at all.
If the enabled property binding doesn't work, you can always prevent your function from getting called yourself:
Button {
onClicked: {
if (this.enabled) { // Don't allow multiple clicks
console.log('clicked')
this.enabled = false
do_something()
this.enabled = true
}
}
}
UPDATE:
In response to your own answer to your question, I'd suggest an alternative to a timer is Qt.callLater(). It will essentially post an event on the event queue rather than call the function directly. That allows just enough time for other processing to occur.
Button {
id: btn
onClicked: {
console.log('clicked')
this.enabled = false
Qt.callLater(callback);
}
function callback() {
do_something()
btn.enabled = true
}
}
But I'm still wondering if you ever tried my original answer, because I think it's much easier, and it should work.
You can enclose the button onClicked properties within if statement like
if (this.enabled)
{
// Do something
}
This if statement would not allow multiple clicks on your button. You can also set other properties within this if statement like what should happen if the button is clicked or unclicked.
Finally I find the answer myself. User setTimeout like html/javascript for a delay execution. Since QML do not have setTimeout, it needs to add a Timer.
import QtQuick
import QtQuick.Control
Timer { id: timer }
Button {
id: btn
onClicked: {
console.log('clicked')
this.enabled = false
timer.interval = 100 // a short delay, enabled status would change properly.
timer.triggered.connect(callback)
timer.start()
}
function callback() {
do_something()
timer.triggered.disconnect(callback) // don't forget to remove the callback
btn.enabled = true
}
}
I am trying to implement a custom button which has press() and release() functions that i call when an expected key event is received. In these functions pressed() and released() signals are called. released() works perfectly but when pressed() is called an error is shown:
TypeError: Property 'pressed' of object CustomButton_QMLTYPE_3(0x78214d8) is not a function
My theory is that the QML can't differentiate the Button's bool pressed property and the pressed() signal. Is this a bug or am i doing something wrong? Here's what i have done:
This is the custom button qml file:
import QtQuick 2.10
import QtQuick.Controls 2.12
Button {
id: control
function press() {
down = true;
pressed();
}
function release() {
down = false;
released()
}
}
In the example below when the F3 key is pressed or released i call the button functions and i expect them to arrive to the Connections i made.
CustomButton {
id: customButton
width: parent.width
height: parent.height
Connections {
target: customButton
onPressed: {
console.log("Custom button pressed!\n");
}
onReleased: {
console.log("Custom button released!\n")
}
}
}
focus: true
Keys.onPressed: {
if(event.key === Qt.Key_F3 && !event.isAutoRepeat) {
console.log("F3 Key pressed!")
customButton.press()
}
}
Keys.onReleased: {
if(event.key === Qt.Key_F3 && !event.isAutoRepeat) {
console.log("F3 Key released!")
customButton.release()
}
}
Like i said release works but the press is problematic. I see these lines in the console:
qml: F3 Key pressed!
qml: press function called
file:///D:/Projects/QmlExamples/qml/fxMenu/button/CustomButton.qml:10: TypeError: Property 'pressed' of object CustomButton_QMLTYPE_3(0x78214d8) is not a function
qml: F3 Key released!
qml: release function called
qml: Custom button released!
If you want manually emit pressed signal, declare it in your Button object:
Button {
id: control
signal pressed;
signal released;
function press() {
down = true;
pressed();
}
function release() {
down = false;
released()
}
onPressedChanged: {
if (down) {
release();
} else {
press();
}
}
}
As you mentioned, there is also a boolean property named "pressed", so it looks like QML engine does not recognize your intentions.
I use QML to build GUI in my app + simple logics.
At some step I open dialog and after closing it I want to get back a result value.
This is sample code:
Button {
id: myButton
onClicked: {
var component = Qt.createComponent("Dialog.qml");
if (component.status === Component.Ready) {
var dialog = component.createObject(parent);
dialog.show();
dialog.onClosing: {} // that not works
}
}
}
Dialog.qml:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
Window {
id: dialogWindow
width: 800
height: 600
flags: Qt.Dialog
Button {
id: closeButton
onClicked: {
dialogWindow.close();
}
}
}
But I have no idea how can I get some return value after the dialog was closed.
My advice would be to not bother with Window and use Qt Quick Dialogs.
If you want to use Window, you have to define your own return value in the form of some Yes/No buttons in the window, for example. When one of these is clicked, set a yes property to true/false. Then, within your Button:
Connections {
target: dialogWindow
onVisibleChanged: // some action depending on value of "yes" property
}
Note that I used onVisibleChanged instead of onClosing, as the latter is only emitted upon the user closing the window:
This signal is emitted when the user tries to close the window.
If we follow the documentation for the CloseEvent argument, we see that it's explained in slightly more detail:
Notification that a window is about to be closed by the windowing system (e.g. the user clicked the titlebar close button).
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;
}
}