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.
Related
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.
In MouseArea.onEntered, can I detect if the cause for the event firing is only that the MouseArea moved and came to be under the cursor, rather than the other way round?
I thought of doing something like this: (pseudocode)
MouseArea {
// ...
property bool positionDirty = false
Connections {
target: window
onAfterRendering: {
positionDirty = false;
}
}
onMouseAreaPosChanged: {
positionDirty = true;
}
onEntered: {
if(positionDirty) {
positionDirty = false;
return;
}
// handle event here
}
}
But this makes the assumption that entered will be fired after mouseAreaPosChanged, and before window.afterRendering. And I'm not confident in that assumption.
Also, it doesn't work when an ancestor of the MouseArea moves, or when the MouseArea is positioned/sized via anchoring.
Assumptions:
This only affects the edge case, that both, the cursor and the MouseArea are moving.
My Assumption here is, that the movement of the cursor is handled before the movement of the MouseArea. I don't have any definite proof for this. Only my test with the solution below, suggests that.
Solution
The first challenge is to detect movement of the MouseArea. It might be that it moves, without its own x and y-values changing, e.g. if its parent is moving.
To solve this, I'll introduce two properties globalX and globalX. Then I use the trick from this answer on how to track a gobal position of an object.
Now I'll have two signals to handle: globalXChanged and globalYChanged.
According to my assumption, they are fired, after the mouseXChanged and mouseYChanged. I will use a flag isEntered to make sure, I only handle one of them, by setting it to true, if the first of them is fired.
I will use the cursor position on a globalMouseArea to determine, whether the cursor is within bounds of the MouseArea. This requires, the cursor is not in some other MouseArea at that time, or at least I know of it
-> With this I already succeeded in detecting the entrance.
The second challenge is to detect the exit. Here we have 4 cases to distinguish:
Cursor enters and leaves the MouseArea because of it's movement.
Cursor enters and leaves the MouseArea because of the movement of the MouseArea
Cursor enters because the MouseArea moves, and leaves, because the cursor moves
Cursor enters because it moves, and leaves as the MouseArea moves away.
The first would be easy to handle. After it enters we handle entered and when it leaves we handle exited. But after the fix, mentioned by Mitch we can't rely on this anymore.
So we will not set hoverEnabled: true and map the position of the cursor to the targetMouseArea whenever either the cursor moves, or the targetMouseArea moves, and act accordingly.
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: root
visible: true
width: 400; height: 450
MouseArea {
id: globalMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: ani.restart()
}
Rectangle {
x: 300
y: 300
width: 50
height: 50
color: 'green'
}
Rectangle {
id: rect
width: 50
height: 50
color: 'red'
Text {
text: targetMouseArea.isEntered.toString()
}
MouseArea {
id: targetMouseArea
anchors.fill: parent
signal enteredBySelfMovement
signal enteredByMouseMovement
onEnteredByMouseMovement: console.log('Cause: Mouse')
onEnteredBySelfMovement: console.log('Cause: Self')
property point globalPos: {
var c = Qt.point(0, 0)
var itm = this
for (; itm.parent !== null; itm = itm.parent) {
c.x += itm.x
c.y += itm.y
}
return c
}
property bool isEntered: false
function checkCollision(sig) {
if ((globalPos.y < globalMouseArea.mouseY)
&& (globalPos.y + height > globalMouseArea.mouseY)
&& (globalPos.x < globalMouseArea.mouseX)
&& (globalPos.x + width > globalMouseArea.mouseX)) {
if (!isEntered) {
isEntered = true
sig()
}
}
else if (isEntered && !containsMouse) {
console.log(isEntered = false)
}
}
onGlobalPosChanged: {
checkCollision(enteredBySelfMovement)
}
Connections {
target: globalMouseArea
onPositionChanged: {
targetMouseArea.checkCollision(targetMouseArea.enteredByMouseMovement)
}
}
}
}
NumberAnimation {
id: ani
target: rect
properties: 'x,y'
from: 0
to: 300
running: true
duration: 10000
}
}
Problems left: When we clicked within the targetMouseArea, as long as a button is pressed, we won't detect the leaving.
You can check whether mouseX, mouseY were changed since last event.
property int previousMouseX = mouseX; // or use other values to init
property int previousMouseY = mouseY; // e.g., 0, x, parent.x,
// or set it from extern
onEntered() {
if (mouseX != previousMouseX || mouseY != previousMouseY) {
// TODO do something
previousMouseX = mouseX;
previousMouseY = mouseY;
}
}
In case mouseX, mouseY are relative to the mouse area 0,0 you can use mapFromItem(null, 0, 0) to get the absolute values.
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.