QML - MouseArea - propagating onPositionChanged - qt

Is it possible to propagate a MouseArea's positionChanged event to an underlying one?
I've tried to set the mouse.accepted to false for any existing signal handler of the top-most MouseArea as well as setting the propagateComposedEvents to true. Neither of those worked (although I'm not surprised with the propagateComposedEvents not working since the documentation says it only relays events like clicked, doubleClicked and pressAndHold).

Depending on your structure you can always manually propagate the event by having your onPositionChanged handler call underlyingMouseArea.positionChanged(mouse) This should manually emit the signal in the underlying MouseArea. My only concern is that you might not be able to pass a MouseEvent object this way(never tried with anything other than a string). However, you can always perform this manual emit in C++ which definetly would not suffer from any type conversion issues.

Unless you need to handle position change events with multiple mouse areas simultaneously you could try reparent your top mouse area:
import QtQuick 2.2
import QtQuick.Layouts 1.1
Rectangle {
id: __root
color: "lightgreen"
width: 360
height: 360
Rectangle {
id: rect2
width: 100; height: 100
color: "cyan"
MouseArea {
parent: __root // set 'logical' parent
anchors.fill: rect2 // set 'visual' ancestor
hoverEnabled: true
onPositionChanged: {
console.log('mouse area 2 onPositionChanged');
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log('mouse area 1 onPositionChanged');
}
}
}
There is an unresolved bugreport.

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.

ContainsMouse gives incorrect value on parent change

In QML, the MouseArea's containsMouse property is supposed to return true when the mouse is currently inside the mouse area. Unfortunately, this is not always the case. In the following code, the red square turns blue when the MouseArea within it contains the mouse (ContainsMouse is true). However, if you hit the control key while the square is blue, when the square is reparented to the Window's contentItem, the containsMouse property is not updated (as indicated by the text in the middle of the square). The square will still be blue even though it doesn't contain the mouse anymore. Is there anyway to tell the MouseArea to refresh it's containsMouse property?
Here is the code:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 800
height: 500
visible: true
Rectangle {
id: square
width: 200
height: 200
focus: true
color: my_mouse_area.containsMouse ? "blue" : "red"
MouseArea {
id: my_mouse_area
anchors.fill: parent
hoverEnabled: true
onClicked: {
my_mouse_area.x = 200
}
}
Text {
anchors.centerIn: parent
text: my_mouse_area.containsMouse + ""
font.pixelSize: 20
}
Keys.onPressed: {
if(event.key === Qt.Key_Control){
second_window.show()
square.parent = second_window.contentItem
}
}
}
Window {
id: second_window
width: 400
height: 400
visible: false
}
}
I don't like my first solution, so I have made another, more sophisticated one, but this is not a pure QML solution. The trick is that on parent change you should call a C++ method where you send a mouse move event back to the mouse area, so it will re-evaluate the hovered aka containsMouse boolean. It is a nicer solution, but still a bit of a workaround.
Make sure you have a simple QObject derived class like MyObject with the following Q_INVOKABLE method:
class MyObject : public QObject
{
Q_OBJECT
//
// constuctor and whatnot
//
Q_INVOKABLE void sendMouseMoveEventTo(QObject* item)
{
QEvent* e = new QEvent(QEvent::MouseMove);
QCoreApplication::sendEvent(item, e);
}
};
Make an instance of it in main.cpp, and set as context property, so you can reach it from QML:
MyObject myObject;
engine.rootContext()->setContextProperty("myObject", &myObject);
And finally in the QML Rectangle add this:
onParentChanged: {
myObject.sendMouseMoveEventTo(my_mouse_area)
}
The solution I came up with uses Timer, but with zero interval, thus zero flickering. You can try setting the interval to higher value, to see what is going on. The trick is to set the rectangle visibility dependent of the timer running using "visible: !tmr.running", and start the timer immediately after the parent change of the rectangle.
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 800
height: 500
visible: true
Rectangle {
id: square
width: 200
height: 200
focus: true
color: my_mouse_area.containsMouse ? "blue" : "red"
visible: !tmr.running
Timer {
id: tmr
interval: 0
}
MouseArea {
id: my_mouse_area
anchors.fill: parent
hoverEnabled: true
onClicked: {
my_mouse_area.x = 200
}
}
Text {
anchors.centerIn: parent
text: my_mouse_area.containsMouse + ""
font.pixelSize: 20
}
Keys.onPressed: {
if(event.key === Qt.Key_Control){
second_window.show()
square.parent = second_window.contentItem
tmr.start()
}
}
}
Window {
id: second_window
width: 400
height: 400
visible: false
}
}

Qt Quick QML MouseArea autorepeat on press

Hopefully I'm not missing something obvious here.
I am writing an app and have made a zoom in button with an Image and a MouseArea. I need the button to repeat a method call after, say, every second to zoom in while holding the mouse button down. It isn't entirely obvious how to make this repeat. Right now I have:
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
onPressed: { cameraController.zoomIn(0.5); }
}
I have also tried with
onPressAndHold: { cameraController.zoomIn(0.5); }
which does basically the same, although with a small delay as expected, but I need to repeat this action every second while the mouse button is held.
To perform the task you need you must use a Timer. the timer must remain active while the containsMouse is active. you must also enable triggeredOnStart to run immediately if the timer is activated.
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
}
Timer {
id: timer
interval: 1000
repeat: true
triggeredOnStart: true
running: zoomIn.containsMouse
onTriggered: cameraController.zoomIn(0.5) //task
}
}
}

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

Drag after long press

I want to drag my custom buttons QML after a long press over them. I've implemented that behaviour, however the problem is that after enabling drag, I need to press button once again to actually start dragging. How should I implement this mechanism if I want to move buttons without releasing after long press?
Here is my button code (onReleased and onLongPressed are my own signals):
ButtonWidget.SoftButtonUI
{
id:softButtonDelegate2
x:500
y:300
labelText: "button"
iconImageSource: path
isGrayedOut: false
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
onReleased:
{
console.log("onClicked")
}
onLongPressed:
{
console.log("onLongPressed")
dragArea2.enabled = true
}
MouseArea {
id: dragArea2
enabled: false
anchors.fill: parent
drag.target: parent
onReleased: parent.Drag.drop()
onClicked: {
console.log("MouseArea onClicked")
}
onPressAndHold: {
console.log("MouseArea onPressAndHold")
}
}
}
Any idea?
Generally speaking you can connect different signals and concatenate operations as discussed in this page. You should have a look at it since it is full of nice and useful information.
However, when it comes to mouse events, an interesting approach to events concatenation is given by MouseEvents acceptation. Documentation says about MouseEvent::accepted:
Setting accepted to true prevents the mouse event from being
propagated to items below this item. Generally, if the item acts on
the mouse event then it should be accepted so that items lower in the
stacking order do not also respond to the same event.
In this case we can take the opposite approach by not accepting the event. This way the pressed event can be used to both activate the drag and actually perform it. Then the MouseEvent can be accepted (implicitly) in the release event, occurring at the end of the drag.
Here is a simple example following this approach. As the mouse is pressed and hold the drag.target is set and drag can start, whereas when the mouse is released the drag.target is removed, removing the dragging behaviour. To test it, just press and hold the mouse over the rectangle and when it changes color just drag it.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 300
height: 300
visible: true
Rectangle {
id: item
border.width: 2
x: 100
y: 100
width: 100
height: 100
state: "BASE"
states: [
State {
name: "BASE"
PropertyChanges { target: mouseArea; drag.target: undefined}
PropertyChanges { target: item; color: "steelblue"}
},
State {
name: "DRAGGABLE"
PropertyChanges { target: mouseArea; drag.target: item}
PropertyChanges { target: item; color: "darkblue"}
}
]
MouseArea {
id: mouseArea
anchors.fill: parent
drag{
// target: NOT SET HERE
minimumX: 0
minimumY: 0
maximumX: parent.parent.width - parent.width
maximumY: parent.parent.height - parent.height
smoothed: true
}
onPressAndHold: {
item.state = "DRAGGABLE"
mouse.accepted = false // mouse event is USED but NOT CONSUMED...
}
onReleased: {
item.state = "BASE" // mouse event acceptation occurs here!
}
}
}
}
This simple approach should work perfectly also with your custom signals.

Resources