QML MouseArea, trigger and propagate events - qt

I have a QML component with buttons, ... in it. I put a MouseArea which covers this entire component because I need to execute an action wherever I click on the component. However, I also want to execute the action behind the MouseArea.
For example, if I click on a button in the component, I want to execute the MouseArea action and then the button action.
Item {
Button{
anchors: ...
onClicked: console.info("Button clicked")
}
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: console.info("MouseArea clicked")
}
}
If propagateComposedEvents: true, then MouseArea onClicked is not executed, but Button onClicked is. If false, MouseArea is executed but not Button onClicked.
I want to have both MouseArea (first) and Button (second) signal onClicked to be executed.

You just need to set the accepted property of the mouse to false.
Item {
Button{
anchors: ...
onClicked: console.info("Button clicked")
}
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.info("MouseArea clicked");
mouse.accepted =false;
}
}
From MouseArea documentation:
If propagateComposedEvents is set to true, then composed events will
be automatically propagated to other MouseAreas in the same location
in the scene. Each event is propagated to the next enabled MouseArea
beneath it in the stacking order, propagating down this visual
hierarchy until a MouseArea accepts the event. Unlike pressed events,
composed events will not be automatically accepted if no handler is
present.
If you want to exclusively catch mouse click on one of them, just move the MouseArea under Button regarding its z level. Or, just move its definition before the Button.
Item {
MouseArea{
anchors.fill: parent
onClicked: console.info("MouseArea clicked")
}
Button{
anchors: ...
onClicked: console.info("Button clicked")
}
}
OR:
Item {
Button{
anchors: ...
onClicked: console.info("Button clicked")
z: 1
}
MouseArea{
anchors.fill: parent
onClicked: console.info("MouseArea clicked")
z: 0
}
}

Related

MouseArea does not pass click to CheckBox

Take a look at this QML snipped:
import QtQuick 2.4
import QtQuick.Controls 2.4
Rectangle {
color: "blue"
width: 50
height: 50
CheckBox {
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
}
}
}
I want to add MouseArea over CheckBox so I can handle doubleclick. However no matter how and what I do CheckBox stops working (clicking it won't show checked mark) as soon as there is MouseArea over it.
What's wrong here?
You can programmatically toggle Qt Quick 2 CheckBox with AbstractButton.toggle(). Also, MouseArea propagateComposedEvents property works only with other MouseAreas and not with Qt Quick Controls QML types.
I don't know your use case so I add few possibilities below.
Signal connect() method
Easiest way to achieve toggling through MouseArea is to create signal chain by connecting MouseArea clicked to CheckBox clicked.
Rectangle {
anchors.centerIn: parent
color: "blue"
width: 50
height: 50
CheckBox {
id: checkBox
onClicked: toggle()
MouseArea {
id: mouseArea
anchors.fill: parent
}
Component.onCompleted: mouseArea.clicked.connect(clicked)
}
}
Note that double click always starts with a single click. If you want to catch double clicks with MouseArea you can e.g. use a Timer for preventing propagating clicks to CheckBox.
Rectangle {
anchors.centerIn: parent
color: "blue"
width: 50
height: 50
CheckBox {
id: checkBox
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
if (timer.running) {
return
}
checkBox.toggle()
timer.start()
}
Timer {
id: timer
interval: 250
repeat: false
}
}
}
}
If you want to support CheckBox's pressed visualization and/or if you want to use bigger MouseArea than the size of the CheckBox you can take a look into this answer of the question Can't click button below a MouseArea.

qt qml. Can a MouseArea see events, but pass them all to parent without affecting them?

An inner MouseArea gets the mouse events first. I would like to "see" these events, so as to set various properties, but not affect them. I would like the mouse events to propagate to any parent MouseArea.
consider this code. I would like clicking on the blue square to see "blue pressed" and "blue released" as well as passing to "parent pressed" and "parent released".
If i accept the event, the parent does not get it. if i don't accept pressed, then i do not see released.
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow
{
visible: true
width: 800
height: 1024
Rectangle
{
anchors.fill: parent
color: "yellow"
MouseArea
{
// i want these to happen even when mouse events are in the
// blue square
anchors.fill: parent
onPressed: console.log("parent pressed");
onReleased: console.log("parent released");
}
Rectangle
{
x: 100
y: 100
width: 100
height: 100
color: "blue"
// i would like to "see" events, but not affect them
// i want all mouse events to pass to parent, as if i am not here.
// however, not accepting "pressed" means i don't see "released"
MouseArea
{
anchors.fill: parent
onPressed:
{
console.log("blue pressed");
mouse.accepted = false
}
onReleased:
{
console.log("blue released");
mouse.accepted = false
}
}
}
}
}
ideas welcome. thanks,
If propagateComposedEvents is set to true, then composed events will be automatically propagated to other MouseAreas in the same location in the scene. Each event is propagated to the next enabled MouseArea beneath it in the stacking order, propagating down this visual hierarchy until a MouseArea accepts the event. Once the event has traveled down the hierarchy there no way for it to come up the hierarchy until another mouse event occur. So, when you are setting mouse.accepted = false in the blue rectangle the mouse event goes to the yellow rectangle and it receives both pressed and released signals but the upper rectangle will no longer receive any events untill another mouse event occurs. So, the answer is NO.
If you want to handle mouse events on different levels such as if you want one MouseArea to handle clicked signals and the other to handle pressAndHold, or if you want one MouseArea to handle clicked most of the time, but pass it through when certain conditions are met following example would help
import QtQuick 2.0
Rectangle {
color: "yellow"
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: console.log("clicked yellow")
}
Rectangle {
color: "blue"
width: 50; height: 50
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("clicked blue")
mouse.accepted = false
}
}
}
}

in Qt How to enable ListView and its item all receive MouseArea events?

I'm using Qt 5.6
I want ListView and its items all receive MouseArea onEntered, onClicked signals.
I tried the examples and changed:
ListView {
anchors.fill: parent
model: searchModel
delegate: Component {
Row {
spacing: 5
Marker { height: parent.height }
Column {
Text { text: title; font.bold: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: console.log("eeee");
}
}
Text { text: place.location.address.text }
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: console.log("entered");
}
}
Only ListView can accept onEntered signal, there is no response from its items.
How to enable items receive MouseArea events ?
To propagate clicked events, you should set propagateComposedEvent to true to the outermost MouseArea.
Guess if the same applies to the entered event.

How to make double MouseArea take effect?

Here is my QML code :
Rectangle
{
.....
Rectangle
{
....height and width is smaller than parent
MouseArea
{
id: mouseArea2
anchors.fill: parent
hoverEnabled: true
onEntered:
{
console.log("enter 2")
}
}
}
MouseArea
{
id: mouseArea1
anchors.fill: parent
hoverEnabled: true
onEntered:
{
console.log("enter 1")
}
}
}
Only mouseArea1 takes effect. If I remove mouseArea1 then mouseArea2 takes effect. So I think the mouse event must be handled by mouseArea1 and let it couldn't be passed to mouseArea2.
I search the document to find out which attr can prevent such behavior but nothing found. So how to let the mouseArea1 and mouseArea2 take effect at the same time?
For "composed" mouse events -- clicked, doubleClicked and pressAndHold -- you can achieve this using the propagateComposedEvents property. But that won't work here because hover events are not composed events.
So what you need to do instead is to change the order in which the MouseAreas are evaluated.
One simple trick is to swap the order of the two MouseAreas in the QML source itself. By placing the smaller one after the larger one, the smaller one takes precedence:
Rectangle{
//.....
MouseArea{
id: mouseArea1
anchors.fill: parent
hoverEnabled: true
onEntered:{
console.log("enter 1")
}
}
Rectangle{
//....height and width is smaller than parent
MouseArea{
id: mouseArea2
anchors.fill: parent
hoverEnabled: true
onEntered:{
console.log("enter 2")
}
}
}
}
A second method that achieves the same thing is to add a z index to the topmost MouseArea that's greater than the lower one. By default every element has a z index of 0, so just adding z: 1 to the smaller MouseArea will do the trick:
Rectangle{
//.....
Rectangle{
//....height and width is smaller than parent
MouseArea{
z: 1 // <-----------------
id: mouseArea2
anchors.fill: parent
hoverEnabled: true
onEntered:{
console.log("enter 2")
}
}
}
MouseArea{
id: mouseArea1
anchors.fill: parent
hoverEnabled: true
onEntered:{
console.log("enter 1")
}
}
}
I have found the solution in the documentation. Take for instance the following QML code:
import QtQuick 2.0
Rectangle {
color: "yellow"
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: console.log("clicked yellow")
}
Rectangle {
color: "blue"
width: 50; height: 50
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("clicked blue")
mouse.accepted = false
}
}
}
}
Here the yellow Rectangle contains a blue Rectangle. The latter is the top-most item in the hierarchy of the visual stacking order; it will visually rendered above the former.
Since the blue Rectangle sets propagateComposedEvents to true, and also sets MouseEvent::accepted to false for all received clicked events, any clicked events it receives are propagated to the MouseArea of the yellow rectangle beneath it.
Clicking on the blue Rectangle will cause the onClicked handler of its child MouseArea to be invoked; the event will then be propagated to the MouseArea of the yellow Rectangle, causing its own onClicked handler to be invoked.

QML- Right Click Not Detected in MouseArea

I'm working on an image editor and specifically working on setting the current left or right button's associated color using a MouseArea (inside a Button type). The problem I'm having is that I can't seem to filter for particular buttons at all. Here's the snippet giving me trouble:
Button {
x: 60
width: 80
height: 40
text: "Blue"
anchors.verticalCenter: parent.verticalCenter
onButtonClick: {
if(mouseArea.pressedButtons & Qt.RightButton) {
console.log("Right button used");
GlobalState.setRightColorValues(0.0, 0.0, 1.0, 1.0);
} else {
console.log("Left button used");
GlobalState.setLeftColorValues(0.0, 0.0, 1.0, 1.0);
}
}
}
(If needed I can provide the entirety of Button.qml, but it's mostly from here).
I'm trying to follow the example here, but the method used to filter for right mouse clicks doesn't seem to work (anymore, anyway). What happens is the statement "defaults" to assuming a left click. I've also tried separating the two into different if-statements, but doing so causes no buttons to be filtered explicitly.
What needs to be changed in order to filter for specific mouse buttons? Or will I have to implement the sort of "switch primary color" button used in Paint/Paint.NET?
Edit 1: I've realized that there was a relevant snippet missing from Button.qml-
MouseArea{
id: buttonMouseArea;
acceptedButtons: Qt.AllButtons;
hoverEnabled: true
onEntered: parent.color = onHoverColor
onExited: parent.color = buttonColor
anchors.fill: parent;
onClicked: buttonClick();
}
This is nested inside a Rectangle, which also holds a Text field.
By default MouseArea only handles the left mouse button. You can handle other buttons by setting the acceptedButtons property. You can determine which button caused the click by using the mouse MouseEvent that is accessible in the onClicked handler.
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) { // 'mouse' is a MouseEvent argument passed into the onClicked signal handler
console.log("right button clicked!")
} else if (mouse.button === Qt.LeftButton) {
console.log("left button clicked!")
}
}
}
See acceptedButtons, and mouse MouseEvent
You can check the button this way:
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button & Qt.RightButton) {
}
}
}
You can avoid using if statements if you dedicate a MouseArea for each mouse button you're interested in:
import QtQuick
import QtQuick.Controls
Page {
Rectangle {
anchors.centerIn: parent
color: "#ffe"
border.color: "grey"
width: parent.width / 2
height: parent.height / 2
Text {
id: txt
anchors.centerIn: parent
text: "Use Mouse here"
}
MouseArea {
anchors.fill: parent
onClicked: txt.text = "left click detected";
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: txt.text = "right click detected";
}
}
}
You can Try it Online!
What I have observed is that pressedButtons works only with onPressed, not with onClicked.
I find that kind of odd, because clicked() is nothing but a press followed by a release. Hence I assumed that it will work with clicked() as well, but sadly it doesn't.
Example:
MouseArea {
id: idMouseArea
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
//onClicked: { // pressedButtons not identified with onClicked
onPressed: { // pressedButtons identified and works well with onPressed
if (idMouseArea.pressedButtons & Qt.RightButton) {
console.log("right-button pressed")
} else if (idMouseArea.pressedButtons & Qt.LeftButton) {
console.log("left-button pressed")
}
}
}
This is what I have observed with Qt 5.5.1 and QtQuick 2.5. The documentation doesn't show how to use pressedButtons property with if-else. Please correct/comment if the observation is wrong.
Update:
If you direly need to use pressedButtons with onClicked you can use following hack to do so.
MouseArea {
property int mouseButtonClicked: Qt.NoButton
acceptedButtons: Qt.RightButton | Qt.LeftButton
anchors.fill: parent
onPressed: {
if (pressedButtons & Qt.LeftButton) {
mouseButtonClicked = Qt.LeftButton
} else if (pressedButtons & Qt.RightButton) {
mouseButtonClicked = Qt.RightButton
}
}
onClicked: {
if (mouseButtonClicked === Qt.LeftButton) {
console.log("left button clicked")
} else if (mouseButtonClicked === Qt.RightButton) {
console.log("right button clicked")
}
}
}

Resources