Catching mouse events from QML - qt

I want to create a QML item which disappears when the mouse moves outside of it. Here is my code:
Item {
id: disappearing_element
ListView { ... }
MouseArea {
id: collapser
anchors.fill: parent
propagateComposedEvents: true
hoverEnabled: true
onExited: {
disappearing_element.visible = false
}
}
}
It works well, but MouseArea propagates events like onClicked() onDoubleClicked() only (as said in Qt docs).
Is there a way to notify disappearing_element's childrens about mouse enter and mouse exit events (without using a Popup element)?

I think this is one of the common needs when developing QtQuick apps. One solution we currently use quite often is to add MouseArea in each of the children that need check mouse containment, and emit signals (and catch these signals in your main item) when the mouse enters or exits.
Things go a bit complicated when the children items also need such mechanism to manage their children. However, for common usage, this approach is enough for us right now.

Related

QML MenuBar hovering items with mouse pressed

Using Qt Quick Controls 2, you can create a "traditional" menu bar like this:
ApplicationWindow {
id: window
width: 320
height: 260
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("&File")
Action { text: qsTr("&New...") }
Action { text: qsTr("&Open...") }
Action { text: qsTr("&Save") }
Action { text: qsTr("Save &As...") }
MenuSeparator { }
Action { text: qsTr("&Quit") }
}
Menu {
title: qsTr("&Edit")
Action { text: qsTr("Cu&t") }
Action { text: qsTr("&Copy") }
Action { text: qsTr("&Paste") }
}
Menu {
title: qsTr("&Help")
Action { text: qsTr("&About") }
}
}
}
This works ok, but when the user presses on a menu and then drag the mouse while pressed, on the menus are not hovered. In order to hover over the menus, the mouse cannot be in a pressed state (using Qt Widgets https://doc.qt.io/qt-5/qtwidgets-mainwindows-menus-example.html this is not needed).
Is there a way to make the MenuBar, hover over items while the mouse is pressed?
When you did through this doc
https://doc.qt.io/qt-5/qml-qtquick-controls2-action.html
You then go to this doc and have high hopes when reading about "hoverEnabled"
https://doc.qt.io/qt-5/qml-qtquick-controls2-toolbutton-members.html
You really need these two signals entered() exited() from a MouseArea.
https://doc.qt.io/qt-5/qml-qtquick-mousearea.html
The short hopeful hack is to see if the documentation is "just wrong" and somewhere deep in the class structure they declared a MouseArea for a button and you really do get entered() and exited().
How else would you be able to implement hoverEnabled()? The "widget" has to know the mouse entered and did not yet exit. They may well be consuming that, but you should be able to dig through the source and find child entity that you can connect to the signal of.
There is not an easy way.
The underlying issue is that MenuItem is implemented as a Button.
When you press a button and release on another button, none of them register a click.
However, on traditional menus, if you press an item and release on another one, the item that registers the release is triggered.
The public API offered by QtQuick Controls 2 does not seem to offer a way to easily change this. So to get what you want Isee the following solutions:
Use Qt.labs.platform to build your menus, these will be native menus so they should have the correct behavior. However it still in a preview state and i have not tested them.
Reimplement MenuItem. Since it is part of Qt Quick Controls 2 it is easy to reimplement your own MenuItem, see Qt documentation. However, you will have to use MouseArea to catch user inputs and force the behavior you want.
EDIT
The 2nd solution won't work. In Qt once an Item (or a QWidget) accepts a press event, it grabs the mouse until it is released. So reimplementing MenuItem and adding MouseArea to them won't help.
Knowing that it seems that the solution would to reproduce what QMenu is doing: You need to have a single Item responsible for handling mouse events. So you should not let each MenuItem handles mouse events individually. Instead you should handle mouse events at the Menu or MenuBar level, process the events and manually change the MenuItems.
At this point I do not know if it is easier to customize Menu and MenuItem or to jus write your own Menu from scratch.

Stackview Push and Pop destroys and creates a new page

I have many pages and when a button is clicked I make transitions between these pages with a StackView, push and pop. And in these pages when corresponding buttons are clicked I make these buttons red. However, when I pop and re-opened the same page with a push, button is no longer red. So that makes me think that pop and push destroys and creates a new page which is the opposite what is written in docs:
This means that any item pushed onto a StackView will never be
destroyed by the StackView; only items that StackView creates from
Components or URLs are destroyed by the StackView.
Here is the code for stackview:
Window{
id:main
property bool isAbsOffRoad: false
StackView{
id:contentFrame
initialItem:Qt.resolvedUrl("qrc:/MainPage.qml")
Connections{
target:contentFrame.currentItem
onBackButtonPressed:{
contentFrame.pop() }
}}
And then here is how I push:
Item {
id:backgroundItem
LeftButtons{
id:buttonSettings
MultiPointTouchArea{
onPressed{
contentFrame.push("qrc:/SettingsPage.qml")}
}}
I cannot see a reason why the page doesn't preserves it's state when popped and pushed back. What might be the reason?
Another question is: I get a
QML Connections: Cannot assign to non-existent property
"onBackButtonPressed".
However, back buttons work. Why I get that error?
The documentation you quote gives you the answer.
This means that any item pushed onto a StackView will never be destroyed by the StackView; only items that StackView creates from Components ->or URLs<- are destroyed by the StackView.
If a StackView creates an item from a URL, it will have ownership of it, and therefore feel free to destroy it.
Your code shows this:
initialItem:Qt.resolvedUrl("qrc:/MainPage.qml")
So you're giving the StackView a URL to your QML. If you don't want it to do that, try doing something like this instead:
initialItem: MainPage {}
That way, the StackView will be given a fully constructed item, and it won't try to destroy it.
For your second question, I'm guessing that your MainPage.qml does not define that signal. You could create that signal in MainPage just to remove the warning, or you can try adding the ignoreUnknownSignals property to your Connections object.
UPDATE:
You can still use push and pop. You just have to provide a created instance of your item, not just the item type. You could try something like this, for example:
component SomePage: Rectangle {
signal clicked()
MouseArea {
anchors.fill: parent
onClicked: parent.clicked()
}
Component.onCompleted: {
console.log("Created: " + color);
}
Component.onDestruction: {
console.log("Destroyed: " + color);
}
}
SomePage {
id: bluePage
color: "blue"
visible: false
onClicked: contentFrame.push(redPage)
}
SomePage {
id: redPage
color: "red"
visible: false
onClicked: contentFrame.pop();
}
StackView {
id: contentFrame
anchors.fill: parent
initialItem: bluePage
}

QML Image source is not updated while mouse is pressed

I have ImageButton.qml which should change image when user holds the button.
import QtQuick 2.0
Image {
id: svg
property string idleImage
property string hoverImage
signal clicked
state: "idle"
source: state === "idle" ? idleImage : hoverImage
onSourceChanged: {
console.log("source = " + source)
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressedChanged: {
svg.state = pressed ? "hover" : "idle"
}
onClicked: svg.clicked()
}
}
But the image is not changed immediately. It's changed only when I hold the button for several seconds. When I press and release mouse button immediately I never see the hover image. onSourceChanged is executed immediately and outputs to console the right image source. This strange bug happens only when I use QQuickWidget. When I don't use widgets, but qml only everything works as expected.
I found the issue. I am using QOpenGLWidget in the MainWindow. And I called update method in paintGL.
void Workspace::paintGL() {
QOpenGLWidget::paintGL();
workspaceDrawer.draw();
update();
}
I replaced update call with a timer, running every 1000 / 60 milliseconds.
And the issue was gone. It's strange that it was reproduced only when a mouse button was pressed, otherwise everything was updated correctly.

Stackview calling slots for previous screen

I am implementing stackview in my application.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: showfooter.currentIndex
DashboardListView{
id:dashboard
}
Settings{
id:setting
}
Cart{
id:cart
}
}
StackView {
id: stackView
initialItem: Pane {
id: pane
}
}
When i am loading some other screen(like SightDescription.qml) from DashboardListView using push method and cliking somewhere on that screen its calling slots for DashboardListView. DashboardListView Screen controls are getting onclick signal. Is there any setting related to stack view that I need to do, I read stackview's documentation but did not find anything to restrict this behavior.
It seems like the Pane is usually intercepting the mouse events, so the lower Items cannot receive them.
When you push the new item on the StackView the Pane becomes visible: false and therefore does not care for input anymore. If the new Item does not handle the mouse events, they will propagate to the lower Item.
To prevent that, you have various options:
Make sure that all Items pushed on the StackView will handle mouse events, e.g. by making a Pane or a MouseArea the root item.
Place a MouseArea directly below the StackView that is only enabled when there are Items on the StackView
Some more... e.g. installing EventFilters in C++ e.t.c. but I think 1 and 2 should be suffice and be easy to implement.

Setting activeFocus by clicking anywhere within an Item

I want to set the activeFocus for a FocusScope by clicking anywhere within an Item.
Is there a way to achieve this without having a MouseArea over the entire Item? Because it would have to overlay all elements within the Item, making them unclickable.
I'm pretty new to QtQuick/QML and have troubles understanding how to properly implement FocusScopes. I've read about propagating click signals, but couldn't get it to work.
Assuming I have something like this (no FocusScopes for readability):
Rectangle
{
id: outerRectangle
width: 1000
height: 1000
// various controls in here
Rectangle
{
id: innerRectangle
anchors.centerIn: parent
width: 200
height: 200
// even more controls in here
}
}
I want the outerRectangle to get the activeFocus when I click anywhere on the outerRectangle and vice-versa for the innerRectangle. But all controls on both Rectangles still have to work properly.
How can I achieve this?
Surround your Item with FocusScope:
FocusScope {
Item {
focus: true
}
}
See Qt Doc

Resources