QML MenuBar hovering items with mouse pressed - qt

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.

Related

Keyboard events for Keys.onPressed not handled over some conditions

I'm trying to get the button event from a USB camera on an application running under linux (custom built using Yocto Project) on an embedded system. Currently I'm using Qt 5.6.3. My problem is that the code I show right below works like a charm while I run the code through SSH from my pc (both via Qt Creator and a simple shell), but if I run the same program directly on the system without using SSH nothing happens when i click the button on the camera (nor any other key from a keyboard really).
Following some examples online I used Keys.onPressed and then filter the event to get the desired behaviour. To get it globally I put the event handler inside an Item directly in my ApplicationWindow.
ApplicationWindow {
Item {
focus: true
Keys.onPressed:{
console.log(event.key)
playSound.play() //I play a sound to be sure a button is clicked
if(camera.recording && event.key === 16777466) {
//do stuff for the right key here
}
}
}
//Other elements here
}
I think it has something to do with the X server running on my system. But everything is default and I really don't know what to look for in my system to get a hint of what's not working. Any advice il really appreciated.
Maybe a problem related with Focus. What about forcing the focus after onCompleted event?
ApplicationWindow {
Item {
id: myItem
focus: true
Keys.onPressed:{
console.log(event.key)
playSound.play() //I play a sound to be sure a button is clicked
if(camera.recording && event.key === 16777466) {
//do stuff for the right key here
}
}
Component.onCompleted: {
myItem.forceActiveFocus()
}
}
Component.onCompleted: {
myItem.forceActiveFocus()
}
//Other elements here
}
can you prove to set width and height of item?
After some hours of searching it turned out it was really a problem with the X server's "active window", the solution was very simple though, I just had to add requestActivate(); on my main view just like this:
ApplicationWindow {
Item {
focus: true
Keys.onPressed:{
console.log(event.key)
playSound.play() //I play a sound to be sure a button is clicked
if(camera.recording && event.key === 16777466) {
//do stuff for the right key here
}
}
}
StackView{
id: rootView
width: 1280
height: 800
Component.onCompleted: {
requestActivate(); //THIS SOLVED THE PROBLEM
push(mainarea);
}
//Other elements here
}

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.

Catching mouse events from QML

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.

Qt 5 QML app with lots of Windows or complex UIs

In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loaders to get around this, but as you can see it is not a very nice solution.
Edit 1 - Why is Loader suboptimal?
Ok, to show you why Loader is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask to be loaded and have all its declarative relationships activated when componentLoaded is set to true, and then have the relationships disconnected and unload the component when componentLoaded is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView.
Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):
// Message dialog box component.
Component {
id: messageBoxFactory
MessageDialog {
}
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
if (typeof modal === 'undefined')
modal = true;
// mainWindow is the parent. We can also specify initial property values.
var messageDialog = messageBoxFactory.createObject(mainWindow, {
text: text,
title: title,
visible: true,
modality: modal ? Qt.ApplicationModal : Qt.NonModal
} );
messageDialog.accepted.connect(messageDialog.destroy);
messageDialog.rejected.connect(messageDialog.destroy);
}
I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.
And do you think your app can take more than even 512 MB ram? I doubt it.
You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.
If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.
Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk
I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

Ubuntu SDK: How do I implement a “onClick” for an image in QML?

I have a background image for my page, and I want to implement a sort of page refresh when the background is clicked. However, I didn't find any actions for the image element in QT quick.
What's the right way to implement this?
You need to use MouseArea to handle click events.
Image {
source: "myimage.png"
MouseArea {
anchors.fill: parent
onClicked: {
// do what you want here
}
}
}

Resources