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
}
}
Related
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();
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.
How can I intercept the signal of the red 'x' close button of a dialog in qml?
Dialog
{
id : dialog1
visible : false
title : "dialog1"
onRejected:
{
console.log("Red button x clicked signal") // Not working
}
Button
{
id: exitButton
text : "Exit"
onClicked :
{
console.log("exit button clicked") // this works
dialog1.visible = false
}
}
I've tried all signals of qml dialog, and none seem to work for the x red button.
Here I want the "X" button to behave exactly like it being rejected. You could call a different signal if you wanted, but I personally kept it to be the same as the rejected signal.
signal yesButtonClicked()
signal noButtonClicked()
signal rejectedButtonClicked()
signal acceptedButtonClicked()
property bool xButton: true
Dialog{
id: dialogId
title: dialogTitle
onYes: {
xButton = false
yesButtonClicked()
}
onNo: {
xButton = false
noButtonClicked()
}
onRejected: {
xButton = false
rejectedButtonClicked()
}
onAccepted: {
xButton = false
acceptedButtonClicked()
}
onVisibilityChanged: {
if (!this.visible && xButton){
rejectedButtonClicked()
}
if (this.visible){
xButton = true
}
}
I'm using Qt 5.9.1 on Ubuntu and trying to detect the "x" / close button being clicked in a QML MessageDialog window. The documentation does indeed indicate that onRejected is the event that should fire - but it does not! So I use onVisibleChanged and check if it is NOT visible. That works.
Item {
signal dialogClosed()
property string dialogTitle: ""
property int dialogIcon: 0
property string dialogText: ""
property bool dialogVisible: false
MessageDialog {
id: messageDialog
title : dialogTitle
icon : dialogIcon
text : dialogText
visible : dialogVisible
modality: Qt.ApplicationModal
onAccepted: dialogClosed()
// onRejected: dialogClosed() // Doesn't work in when "x" is clicked as documented!
onVisibleChanged: { if( !this.visible ) dialogClosed(); }
}
}
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.
I have a simple MouseArea where I'm trying to detect drag actions. Everything seemes to work fine, but wasHeld property somehow allways going false. Am I using it wrong way, or it's just broken?
MouseArea {
id: mousearea
property bool dragging: false
// ...
onPressed: {
// ...
dragging = true;
// ...
}
onReleased: {
if (mouse.wasHeld) { // allways false
dragging = false;
status.text = "was held - stop dragging";
} else {
points.add(mouseX, mouseY); canvas.drawPoint(mouseX,mouseY);
status.text = "wasn`t held - add point";
}
}
onPositionChanged: {
if (dragging) {
// ...
canvas.repaint();
}
}
}
Is there another simple way to recognize whether mouse was held or not?
UPD: solved by timing onPressed signal.