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.
Related
I have a QML Controls 2 SplitView and a redefined handle, which works well, but I want detect mouse release event on the handler, so I could collapse the SplitView under a certain threshold of width. Adding a MouseArea on top of the existing handle will absorb drag events, so I'm unable to move the handlebar. Any idea how could I gather the mouse release event, or any other solution which solves this problem?
Alright, I have created an example application. As you can see in this example, my MouseArea is marked with yellow and collapses the right view programmatically when double clicked, which is nice, but I also want to drag the handlebar and upon mouse release under a certain width threshold I want to collapse the view as well. The black part of the handlebar where my MouseArea is not covering the handlebar, responds to drag, but since there is no signal I can gather from it, the width threshold already set shouldCollapse boolean property, so the view won't update. Probably I could solve this issue with a timer, but I need a more sophisticated solution.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 800
height: 400
visible: true
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
function toggleCollapse() { collapsibleRect.shouldCollapse = !collapsibleRect.shouldCollapse }
handle: Rectangle {
implicitWidth: 20
implicitHeight: 20
color: "black"
MouseArea {
anchors.centerIn: parent
width: parent.width
height: parent.height / 2
onDoubleClicked: splitView.toggleCollapse()
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
anchors.centerIn: parent
text: "Double click to collapse"
rotation: 90
}
}
}
}
Rectangle {
id: mainRect
color: "green"
SplitView.fillWidth: true
Text {
anchors.centerIn: parent
font.pixelSize: 24
text: "Main scene"
}
}
Rectangle {
id: collapsibleRect
property bool shouldCollapse: false
SplitView.preferredWidth: shouldCollapse ? 0 : 300
color: "purple"
clip: true
onWidthChanged: {
if(width < 200) shouldCollapse = true
else shouldCollapse = false
}
Text {
anchors.centerIn: parent
rotation: parent.shouldCollapse ? 90 : 0
font.pixelSize: 24
text: parent.shouldCollapse ? "SHOULD BE COLLAPSED" : "NOT COLLAPSED"
Behavior on rotation { NumberAnimation { duration: 100 } }
}
}
}
}
I had a similar problem and was able to solve it thanks to the hint of #Ponzifex that the SplitView's resizing property will be set to true as soon as the handle is clicked. Using a Timer I managed to detect whether the handle was quickly pressed twice in a row.
SplitView {
id: view
...
handle: Rectangle {
...
}
//============================================================
// double click behavior
Timer {
id: doubleClickTimer
interval: 300 // number of ms between clicks that should be considered a double click
}
property bool doubleClicked: false
// `resizing` will be set to true even if the handle is just pressed
onResizingChanged: {
if (view.resizing) {
if (!doubleClickTimer.running) {
doubleClickTimer.start();
return;
}
view.doubleClicked = true;
} else {
if (view.doubleClicked) {
// do any manual resizing in here
view.doubleClicked = false;
}
}
}
}
It is important to note, however, that it is only possible to resize the contents of a SplitView when resizing is false. That's why I need to have the doubleClicked helper property.
Add this to MouseArea:
onPressed: {
mouse.accepted = (mouse.flags & Qt.MouseEventCreatedDoubleClick);
}
propagateComposedEvents: true
cursorShape: Qt.SplitHCursor
I have a row for a listview delegate with buttons on it. On click of a button, i need a dialog to open just below that button. I tried mapToItem property and partially succeeded but this listview is scrollable and on scrolling the dialog stays in its initial position. Unsure of how to get it working. Also, new to posting questions. Kindly ignore if I am being vague and help me out.
The dialog i want to open is placed outside of this delegate. I have provided a short outline of my code.
Listview{
delegate: Row{
Button1{
}
Button2{
id: button2Id
onCheckedChanged{
var coords = button2Id.mapToItem(null,0,0)
dialogId.x = coords.x
dialogId.y= coords.y
dialogId.visible = true
}
}
}
}
//dialog rect outside of my listview
Rectangle{
id: dialogId
}
You could add the dialog to the highlight item of the list. I have modified your example a little so that I could test it. I encapsulated your Rectangle in an Item because ListView controls the size and position of the root object of the highlight. The Rectangle then just has to be anchored to the bottom of that Item.
ListView {
id: lv
width: 200
height: parent.height
model: 50
spacing: 1
currentIndex: -1
delegate: Row {
spacing: 1
height: 40
Button {
text: index
}
Button {
id: button2Id
text: ">"
onClicked: {
lv.currentIndex = index;
}
}
}
highlight: Item { // ListView controls the size/pos of this Item
z: 1
Rectangle {
id: dialogId
anchors.top: parent.bottom // Anchor to bottom of parent
width: 200
height: 100
color: "red"
}
}
}
UPDATE:
Here is a way to keep the dialog directly under the button without calculating margins. I put it in a Loader so that each item in the list doesn't always carry the whole dialog around with it. It might make a performance difference.
The ugly part of this solution is the z-ordering. Each item in the list is drawn after the one that comes sequentially before it. (I'm not actually sure if that's even guaranteed.) That means the dialog gets drawn underneath any item that comes after it in the list. I was able to get around that by changing the z value of each item in the list to be less than the item before it.
ListView {
id: lv
width: 200
height: parent.height
model: 50
spacing: 1
currentIndex: -1
delegate: Row {
z: lv.count - index // <<- z-value fix
spacing: 1
height: 40
Button {
text: index
}
Button {
id: button2Id
text: ">"
onClicked: {
lv.currentIndex = index;
}
Loader {
anchors.top: parent.bottom
asynchronous: true
sourceComponent: (index === lv.currentIndex) ? dialogComp : null
}
}
}
}
Component {
id: dialogComp
Rectangle {
id: dialogId
width: 200
height: 100
color: "red"
}
}
I have an item that consists of an image:
Item {
/* other stuff */
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
}
}
I want to overlay a (transparent) rectangle of the same size on the image when one hovers over it, so that I can do stuff like provide captions for the image etc.
How would one go about doing this?
My first try below: (I used console statements to verify that hovering on an image itself works)
import QtQuick 2.6
Item {
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
enabled: img.status == Image.Ready
anchors.fill: parent
hoverEnabled: true
onEntered: {
console.log("Entering: ")
overlay
}
onExited: {
console.log("Exiting: ")
}
}
Rectangle {
id : overlay
anchors.fill: parent
color: "transparent"
Text {
id: imgcaption
text: "Caption"
anchors.bottom: overlay.bottom; anchors.verticalCenter: overlay.verticalCenter
font.pointSize : 14
}
}
}
}
When I do this I get something like this at all times, regardless of if I'm hovering over the image or not.
I also tried to put the Rectangle inside the onEntered handler itself, but when I do this the image doesn't display at all, and neither do the console statements so I'm not sure if this is valid QML.
It should be fairly obvious that I'm new to QML and don't really know what I'm doing, so I'd appreciate it if someone could point me in the right direction.
Try toggling the visibility based on the containsMouse property, like this:
Item {
Image {
id: img
anchors.fill: parent
source: mySource
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
id: mouseArea
enabled: img.status == Image.Ready
anchors.fill: parent
hoverEnabled: true
}
Item {
id : overlay
anchors.fill: parent
visible: mouseArea.containsMouse
Text {
id: imgcaption
text: "Caption"
anchors.bottom: overlay.bottom; anchors.verticalCenter: overlay.verticalCenter
font.pointSize : 14
}
}
}
}
HoverHandler might be better. It's born to deal with hover events so you don't have to set hoverEnabled or anchors.
More importantly, it's transparent for mouse movement. This would be useful in more tricky scenarios, such as dealing with hover events for an item that already contains interactive controls (Button, Flickable, MouseArea, etc).
If you still use MouseArea in such cases, it may interrupt how these controls deal with hover events while HoverHandler won't. (MouseArea has a propagateComposedEvents property, which only controls whether clicked, doubleClicked, and pressAndHold would be propagated or not.)
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
}
}
}
}
I have code like this:
GridView {
// ... declarations ...
model: theModel
delegate: MouseArea {
id: cellMouseArea
onClicked: // open the cell
}
MouseArea {
id: gridViewMouseArea
// here process horizontal mouse press/release actions
}
}
with a MouseArea defined in each delegate and an overall MouseArea covering my GridView. In the cellMouseArea I want to perform an open item action whereas in the gridViewMouseArea I want to implement mouseX handle to open/close a sidebar. However, the two MouseAreas do not work together. How can I carry it out?
You can exploit propagateComposedEvents:
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.
You can set the property to true on the GridView MouseArea. In this way click events are propagated to the MouseAreas in the delegates whereas the outer MouseArea can implement other behaviours such as drag or hoven.
Here is an example in which outer MouseArea defines drag property to slide in/out a Rectangle ( simulating your sidebar) and thanks to the propagateComposedEvents clicks are managed by the single delegates.
import QtQuick 2.4
import QtQuick.Window 2.2
ApplicationWindow {
width: 300; height: 400
color: "white"
Component {
id: appDelegate
Item {
width: 100; height: 100
Text {
anchors.centerIn: parent
text: index
}
MouseArea {
anchors.fill: parent
onClicked: {
parent.GridView.view.currentIndex = index
console.info("Index clicked: " + index)
}
}
}
}
Component {
id: appHighlight
Rectangle { width: 80; height: 80; color: "lightsteelblue" }
}
GridView {
anchors.fill: parent
cellWidth: 100; cellHeight: 100
highlight: appHighlight
focus: true
model: 12
delegate: appDelegate
MouseArea {
anchors.fill: parent
z:1
propagateComposedEvents: true // the key property!
drag.target: dragged
drag.axis: Drag.XAxis
drag.minimumX: - parent.width
drag.maximumX: parent.width / 2
onMouseXChanged: console.info(mouseX)
}
}
Rectangle{
id: dragged
width: parent.width
height: parent.height
color: "steelblue"
x: -parent.width
}
}