Loading a Rectangle upon mouse hover in QML - qt

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.)

Related

QML give focus to a Component

I have this code:
//main.qml
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
objectName: "Window"
onActiveFocusItemChanged: console.log("***** ACTIVE FOCUS:", activeFocusItem, "*****")
StackView {
anchors.fill: parent
initialItem: "qrc:/LoaderPage.qml"
objectName: "StackView"
onCurrentItemChanged: currentItem.forceActiveFocus()
}
}
//LoaderPage.qml
Item {
objectName: "ItemLoaderPage"
// Keys.forwardTo: loader
Loader {
id: loader
anchors.fill: parent
objectName: "Loader"
focus: true
sourceComponent: rect1
}
Component {
id: rect1
Rectangle {
Keys.onReleased: {
if(event.key === Qt.Key_Escape || event.key === Qt.Key_Back)
{
console.log("Esc or back pressed from", objectName)
event.accepted = true
}
}
objectName: "Rectangle"
focus: true
color: "blue"
}
}
}
I am trying to give the focus to the Rectangle in rect1 Component and catch key events, but with this code, the focus is always given to ItemLoaderPage and I am not able to catch key events. How can I solve that?
I find that maintaining keyboard focus is a big weakness in Qt. The docs make it all sound so straightforward, but in practice I am always ending up in situations where I can't even tell where the focus went.
I usually resort to manually calling forceActiveFocus() rather than depending on Qt to do the right thing automatically. It's a fragile solution, but it's at least one that I feel I have control over.
Loader {
sourceComponent: rect1
onLoaded: {
item.forceActiveFocus();
}
}
The Loader and the rectangle has requested the focus by your attribute:
focus: true
You should try to set the focus only once if I get the idea of the focus-attribute right.

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.

QML: Mouse wheel event propagation in ListView

Have strange situation with ListView scrolling on mouse wheel. Have Items structure similar to this:
MainAppWindow {
// Some zoomable map item
Map {
anchors.fill: parent
}
PopupMenu { // Simple Rectangle item
anchors.top: parent.top
width: 200
height: parent.height / 2
z: parent.z + 1
ListView {
anchors.fill: parent
clip: true
...
delegate: Item {
...
MouseArea {
anchors.fill: parent
onClick: {
someHandler()
}
}
}
}
}
}
ListView with vertical scroll works and scrolls just fine until it stops at bounds (top or bottom - whatever) and after this mouse event starts to propagate to underlying layer and ZoomableMap starts to zoom which is not we want: should be propagated there only if PopupMenu is not visible. Adding
onWheel: wheel.accepted = true
into MouseArea inside ListView delegate could partially solve the problem - it disables wheel and allows scrolling only by dragging the content. However better allow scrolling by the wheel as well. MouseArea in PopupMenu blocks wheel and dragging in the ListView completely as well - not helps also.
So what is problem here, how to fix? Or we doing something wrong here?
Need to add another MouseArea into PopupMenu which blocks all mouse events and is disabled by default and enable it only if popup is visible (optional):
enabled: popupMenu.visible
MainAppWindow {
// Some zoomable map item
Map {
id: map
anchors.fill: parent
}
PopupMenu { // Simple Rectangle item
id: popupMenu
anchors.top: parent.top
width: 200
height: parent.height / 2
z: parent.z + 1
MouseArea {
id: mapMouseArea
anchors.fill: parent
enabled: popupMenu.visible
preventStealing:true
hoverEnabled: true
onWheel: { wheel.accepted = true; }
onPressed: { mouse.accepted = true; }
onReleased: { mouse.accepted = true; }
}
ListView {
anchors.fill: parent
clip: true
...
delegate: Item {
...
MouseArea {
anchors.fill: parent
onClick: {
someHandler()
}
}
}
}
}
}
Note: however this solution does not work if ListView (or any other control) is a Map descendant item: item dragging causes map panning. To make it work need to make it at least sibling.

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

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.

Resources