How to propagateComposedEvents to Button from upper MouseArea? - qt

Demo:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow{
width: 500
height: 500
visible: true
Button {
text: "button"
onClicked: {
console.log("Button onClicked")
}
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("MouseArea onClicked")
mouse.accepted = false
}
}
}
I expected below when click the button
qml: MouseArea onClicked
qml: Button onClicked
but only got
qml: MouseArea onClicked
How to make the event pass to Button? and why the current demo cannot do that?

it seems a temp solution is:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow{
width: 500
height: 500
visible: true
Button {
text: "button"
onClicked: {
console.log("Button onClicked")
}
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log("MouseArea onClicked")
mouse.accepted = false
}
onPressed: {
console.log("MouseArea onPressed")
mouse.accepted = false
}
}
}
result:
qml: MouseArea onPressed
qml: Button onClicked
it is not what I want but can solve my current problem.

If this is a specific case which you are trying, one solution which you can do is to call the button clicked signal from onClicked in mouse area
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow{
width: 500
height: 500
visible: true
Button {
id:btn
text: "button"
onClicked: {
console.log("Button onClicked")
}
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("MouseArea onClicked")
btn.clicked()
}
}
}

Why the current demo cannot do that?
The point is that the order of the components in the file matters, and this order affects how they overlap each other. For example, in this case of manual positioning, we see how one component overlaps another, what is reflected in their display relative to each other. One component will overlap another component if it is located below in the code.
So in your case, it is not surprising that you get the following output:
qml: MouseArea onClicked
since the MouseArea is located after the Button, and mouse events do not reach the Button. If you arrange them in the reverse order (first the area, and then the button), then the output will be as follows:
qml: Button onClicked
How to make the event pass to Button?
If a click occurs, then it must refer to some one specific component. This means that it will not be possible to click on one component, but to process this click already in several. Therefore, in your case, you can first click on the MouseArea, and then provoke a click from the code on the Button, or vice versa.
Thus, if you want to get exactly the output that you indicated in the question, the already proposed answer will suit you. However, in this case, we lose the ability to use the standard functionality of the Button component, such as "change color when pressed", and more. So I can suggest this solution:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
id: root
width: 500
height: 500
visible: true
MouseArea {
id: ma
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
console.log("MouseArea onClicked")
}
Button {
text: "button"
onClicked: {
console.log("Button onClicked")
ma.clicked(ma.mouseX, ma.mouseY)
}
}
}
}
The output will look like this:
qml: Button onClicked
qml: MouseArea onPressed
However, in this case, pressing will work in two places, and at the same time the above-described functionality of the Button is preserved. You can also place the Button under the MouseArea - this will work too.

Related

QML: Accept TextField onClicked outside

Imagine the following:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TextField {
anchors.centerIn: parent
onEditingFinished: console.log("input is: " + text)
}
}
How do I achive that the input of the TextField is accepted (onEditingFinished emitted), on clicking anywhere outside of the TextField (not pressing enter, tab,.. just a mouse click)?
I might set a MouseArea around it with onClicked: forceActiveFocus() to force onEditingFinished, but how do I achive this within a large application with many layers/views? This does not seem to be the right solution.
You can take this approach:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
if (root.activeFocusItem != null) {
root.activeFocusItem.focus = false;
}
}
}
TextField {
anchors.centerIn: parent
onEditingFinished: console.log("input is: " + text)
}
}
This places a single MouseArea filling your Window as a backstop to anything that might appear above it. Either a mouse click will be accepted by a control sitting above the mouse area or will pass through to it and clear focus on anything that might have it.

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

Why does my SwipeView page not change when I change the Tab in the TabBar in QML?

So, I'm trying to create a traditional look for my QML application which simply consists of a TabBar and a SwipeView. There's also another component which essentially boils down to a button click which should result in a new tab in the TabBar and a new page in the SwipeView. This works, except that when I click on the previous tab (there's a static tab and page when the app starts up), the page in the view doesn't change.
Here's my QML file -
import QtQuick 2.12
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.12
import QtQuick.Controls 2.13
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
Component {
id: tabButton
TabButton {
width: implicitWidth
}
}
Component {
id: resultListView
Rectangle {
anchors.fill: parent
color: "green"
}
}
SwipeView {
id: tabBarLayout
Layout.fillHeight: true
Layout.fillWidth: true
interactive: false
currentIndex: 0
LogArea {
id: logArea
}
}
TabBar {
id: tabBar
currentIndex: tabBarLayout.currentIndex
Layout.fillWidth: true
font.capitalization: Font.MixedCase
TabButton {
text: qsTr("log")
width: implicitWidth
}
Connections {
target: qmlBridge
onLoadResultTab: {
tabBar.addItem(tabButton.createObject(tabBar, {text: qsTr("search - " + term)}))
tabBarLayout.addItem(resultListView.createObject())
tabBarLayout.incrementCurrentIndex()
console.log("Added new item...")
}
}
}
}
The onLoadResultTab signal is invoked when the button is clicked. As you can see, I'm adding a new TabButton and incrementing the current index and then creating another component which is just a simple Rectangle for now. Now, when I click on the "log" button, the SwipeView doesn't change its page to the one corresponding to the log tab. Could anyone point out what's the issue here?

How should I start QML files?

I have 4 QML files: MainMenu.qml, AppArea.qml, Result.qml and main.qml.
When my app starts, I want to see first page as MainMenu.qml fullscreen. There is a button (on MainMenu.qml) to start AppArea.qml. When I click the the button, I want to start AppArea.qml as fullscreen new window.
There is a button (on AppArea.qml), when I click that button, I want to show Result.qml but I want to see Result.qml on AppArea.qml, I mean when Result.qml come outs, AppArea.qml will not disappear but Result.qml will appear on AppArea.qml.
There is a button on Result.qml. When I click the button, the Repeater in AppArea.qml will regenerate, because maybe model of Repeater changing like 1, 2, 3, 4.... There is a button on AppArea.qml, when I click the button, I want to open MainMenu.qml as a fullscreen new window like AppArea.qml.
Actually you can think basic: My app is a game like this:
How way should I choose for these jobs?
In addition to the mentioned post, in your case you are using the component from qml file, so you need to load the component first, your main.qml can be like this:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
id: mainWindow
title: "Main window"
visible: true
flags: Qt.Dialog
modality: Qt.ApplicationModal
Loader{
id: mainMenuLoader
}
Component.onCompleted: {
mainMenuLoader.source="mainMenu.qml"
var mainMenu = mainMenuLoader.item.createObject(mainWindow);
mainWindow.hide()
}
}
and your mainMenu.qml can look like this:
import QtQuick 2.9
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
Component {
id: mainMenu
Window {
id:mmenu
title: "Main Menu"
width: 600
height: 600
visible: true
flags: Qt.Dialog
modality: Qt.ApplicationModal
Loader{
id: appAreaLoader
}
Text {
text: "This is mainMenu"
}
Button{
id: loadAppArea
anchors.centerIn: parent
text: "Start Game"
onClicked: {
appAreaLoader.source="appArea.qml"
var appArea = appAreaLoader.item.createObject(mainMenu);
hide()
}
}
}
}
you will need to do the same for successive windows ...etc.
While for result, you need to use a MouseArea:
appArea.qml:
Component {
id: appMenu
Window {
id:appMenuWindow
title: "App Menu"
width: 600
height: 600
visible: true
flags: Qt.Dialog
modality: Qt.ApplicationModal
Loader{
id:anotherLoader
visible: true
anchors.left: appMenuText.left
anchors.top: appMenuText.bottom
width: parent.width/3
height: parent.height/3
}
Text {
id: appMenuText
text: "This is App Area"
anchors.centerIn: parent
}
Button{
id: loadResult
text: "Show Result"
onClicked: {
anotherLoader.source = "result.qml"
anotherLoader.visible=true
}
}
Button{
anchors.right: parent.right
id: loadMainMenu
text: "Open main Menu"
onClicked: {
hide()
//mmenu.show()
anotherLoader.setSource("main.qml")
}
}
}
}
result.qml:
Rectangle{
color: "green"
Text {
anchors.centerIn: parent
id: resultxt
text: qsTr("This is result, Click to close")
}
MouseArea {
anchors.fill: parent
onClicked: { anotherLoader.visible = false
}
}
}

Resources