Handle key events Ctrl+Tab and Ctrl+Shift+Tab - qt

I want to handle the two key events Ctrl+Tab and Ctrl+Shift+Tab in order to switch between tabs in my application ("forward" and "backward" respectively). However, this doesn't seem to work as expected.
This is my current code (minimal example):
import QtQuick 1.1
Item {
width: 100
height: 100
focus: true
Keys.onPressed: {
if(event.modifiers & Qt.ControlModifier) {
if(event.key === Qt.Key_Tab) {
if(event.modifiers & Qt.ShiftModifier)
console.log('backward')
else
console.log('forward')
}
}
}
}
I ran this piece of code with qmlviewer (Qt version 4.8.2)
Output when pressing Ctrl+Tab:
forward
forward
Output when pressing Ctrl+Shift+Tab:
none
So I see two errors: The former key sequence is handled twice while the other one not at all.
EDIT: The reason that the other one doesn't get handled at all is solved, see comments.
Why does this happen and how can I solve this?
Note: I already use Qt Components for Desktop in my application, so it's OK if you know a solution requiring this module.

You have to accept the event, otherwise the event is propagated to the parents until it is accepted. The following code worked for me.
Item {
width: 100
height: 100
focus: true
Keys.onPressed: {
if(event.modifiers && Qt.ControlModifier) {
if(event.key === Qt.Key_Tab) {
console.log('forward')
event.accepted = true;
}
else if(event.key === Qt.Key_Backtab) {
console.log('backward')
event.accepted = true;
}
}
}
}
Edit: This behavior lets parents handle events that the child could not, for things like hotkeys.
Hope this helps!

Related

QML how force update / redraw

In my application I have a process which is started by clicking on a button. The button already has a property to indicate when it is busy, this changes the colours to reflect the busy state. When the process is finished the busy property is set back to false.
The problem is that although the steps are:
button.busy = true
... Do something ...
button.busy = false
In reality the button does not change to reflect the busy state until the process is almost complete, it then changes back to non-busy.
Is there anyway I can insert something after setting the busy state to true and before doing something to get the GUI to update and reflect the state?
My button QML:
Button {
id: root
property bool busy: false
property bool showDropShadow: true
property color bottomColour: MerlinStyle.greenButtonBottom
property color iconColour: "white"
property color topColour: MerlinStyle.greenButtonTop
property string icon: ""
opacity: (pressed || !enabled) ? 0.5 : 1.0
onBusyChanged: {
//Set the colours according to busy state
if ( root.busy == true ) {
root.bottomColour = MerlinStyle.indicatorOrange;
root.topColour = MerlinStyle.indicatorOrange;
} else {
root.bottomColour = MerlinStyle.greenButtonBottom;
root.topColour = MerlinStyle.greenButtonTop;
}
}
background: Item {
RadiusRectangle {
id: rect
anchors.fill: parent
radius: MerlinStyle.rectRadius
topLeftPointed: true
gradient: Gradient {
GradientStop { position: 0.0; color: root.topColour }
GradientStop { position: 1.0; color: root.bottomColour }
}
}
DropShadow {
visible: showDropShadow && !pressed && enabled
anchors.fill: rect
horizontalOffset: 1
verticalOffset: 2
color: "#80000000"
source: rect
}
}
contentItem: Item {
ColoredImage {
anchors.centerIn: parent
height: parent.height * 0.85
width: parent.width * 0.85
source: icon
color: root.iconColour
}
}
}
I've tried to trigger an update using:
idOfButton.update
This always results in :
Button_QMLTYPE_28 : Update called for a item without content
The update function takes no parameters.
When you call that function, it simply blocks the GUI thread, and those events which have been put to the event queue will wait until the program returns to the event loop again. That's why you cannot see that the button is updated based on the property changes.
This happens because of bad design. As per Qt documentation:
use asynchronous, event-driven programming wherever possible
use worker threads to do significant processing
never manually spin the event loop
never spend more than a couple of milliseconds per frame within blocking functions
You should not call a blocking function from within the GUI thread. You need to run that function from another thread or if you have a purpose to do that, you can call your function with a Timer which is a dirty hack.
Timer{
id: dummyTimer
interval:1
repeat: false
running: false
onTriggered: {
control.someLazyBlockingFunction();
idOfButton.busy = false;
}
}
Button{
id: anotherButton
...
onClicked:{
idOfButton.busy = true;
dummyTimer.running= true;
}
}
Late answer, but might help someone.
In my case, my application is running on python (PyQt5 + QML), the event comes from QML code, and is handled in a slot (nothing new here).
The thing is, as stated in the qt documentation, you cannot block the main thread, so, the best way I found to handle it was spinning a new daemon thread to run the event code without hanging the frontend.
#pyqtslot(name="doMyButtonCallback")
def do_mybuttoncallback():
def mybuttonwork():
time.sleep(3)
print("i did not hang your code!"
t=threading.Thread(target=mybuttonwork)
t.daemon=True
t.start()

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
}

Crash in QQuickItem destructor / changeListeners when closing application (Qt 5.6)

We have a fairly big QtQuick application, with a lot of modal dialogs. All of these modals share a consistent look and behaviour, and have leftButtons, rightButtons, a content and additional warning widgets. We use the following base class (PFDialog.qml):
Window {
property alias content: contentLayout.children
ColumnLayout {
id: contentLayout
}
}
and declare dialogs in the following way (main.qml):
Window {
visible: true
property var window: PFDialog {
content: Text { text: "Foobar" }
}
}
The problem is that when the application is closed, a segfault happens in the QQuickItem destructor. This segfault is hard to reproduce, but here is a surefire way of making it happen: with visual studio in debug mode, freed memory is filled with 0xDDDDDDD with triggers the segfault every time.
Full example application can be found here: https://github.com/wesen/testWindowCrash
The crash happens in QQuickItem::~QQuickItem:
for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
if (anchor)
anchor->clearItem(this);
}
The reason for this is that the content of our dialog (the Text item in the example above) is a QObject child of the main Window, but a visual child of the dialog window. When closing the application, the dialog window is destroyed first, and at the time the Text item is deleted, the dialog window (still registered as a changeListener) is stale.
Now my question is:
is this a QtQuick bug? Should the dialog deregister itself as a changeListener for its children when it is destroyed (I think it should)
is our property alias content: layout.children pattern correct, or is there a better way to do this? This also happens when declaring a default property alias.
For the sake of completeness, here is how we hotfix it in our application. When content changes, we reparent all the items to the layout item. A of elegance, as you will all agree.
function reparentTo(objects, newParent) {
for (var i = 0; i < objects.length; i++) {
qmlHelpers.qml_SetQObjectParent(objects[i], newParent)
}
}
onContentChanged: reparentTo(content, contentLayout)
I have had this problem lots of times, I don't think it is a bug, more like a design limitation. The more implicit behavior you get, the less control you have, leading to inappropriate orders of object destruction and access to dangling references.
There are numerous situations where this can happen "on its own" as you exceed the bounds of a trivial "by the book" qml application, but in your case it is you who's doing it.
If you want proper ownership, don't use this:
property var window: PFDialog {
content: Text { text: "Foobar" }
}
Instead use this:
property Window window: dlg // if you need to access it externally
PFDialog {
id: dlg
content: Text { text: "Foobar" }
}
Here is a good reason why:
property var item : Item {
Item {
Component.onCompleted: console.log(parent) // qml: QQuickItem(0x4ed720) - OK
}
}
// vs
property var item : Item {
property var i: Item {
Component.onCompleted: console.log(parent) // qml: null - BAD
}
}
A child is not the same as a property. Properties are still collected but they are not parented.
As for achieving the "dynamic content" thingie, I've had good results with ObjectModel:
Window {
property ObjectModel layout
ListView {
width: contentItem.childrenRect.width // expand to content size
height: contentItem.childrenRect.height
model: layout
interactive: false // don't flick
orientation: ListView.Vertical
}
}
Then:
PFDialog {
layout: ObjectModel {
Text { text: "Foobar" }
// other stuff
}
}
Lastly, for the sake of doing explicit cleanups before closing the application, on your main QML file you can implement a handler:
onClosing: {
if (!canExit) doCleanup()
close.accepted = true
}
This ensures the window will not be destroyed without doing the cleanup first.
Finally:
is our property alias content: layout.children pattern correct, or is
there a better way to do this? This also happens when declaring a
default property alias.
It wasn't last time I looked into it, but it was at least couple of years back. It would certainly be nice to have objects declared as children actually becoming children of some other object, but at the time this was not possible, and still may not be. Thus the need for the slightly more verbose solution involving the object model and the list view. If you investigate the matter and find something different, leave a comment to let me know.
I believe that you cannot declare a Window Object in a var. In my tests the SubWindow never open and sometimes broken on startup.
A Window can be declared inside an Item or inside another Window; in that case the inner Window will automatically become "transient for" the outer Window
See: http://doc.qt.io/qt-5/qml-qtquick-window-window.html
Modify your code to this:
Window {
visible: true
PFDialog {
content: Text { text: "Foobar" }
}
}

QML several items with active focus / keyboard shortcut control

I'm trying to implement a keyboard shortcut control for my qml application. I know there's the possibility to do that with an Action element, but I don't want menus and toolbars which are then mandatory to use.
That's why I'm approaching this topic with keyboard events. For this, I need to have the element performing the action to be in focus. But my goal is a global shortcut control, so theoratically I'd need to have all the elements in question in focus.
I found the FocusScope type in the documentation, but I'm not sure if this is what I need.
Does it mean that the focus of nested FocusScopes 'slides' through to the last element that's not a FocusScope and acquiring focus manually with focus: true thus only this last element holding focus? Or do all the elements on the way down the slide that acquire focus have the activeFocus property set?
Is this the right approach or would I need something else?
Focus in Qt Quick is a mess in my opinion. It always confuses me and I end up hacking around it with forceActiveFocus(). I'd recommend the new Shortcut type:
Shortcut {
sequence: StandardKey.Quit
context: Qt.ApplicationShortcut
onActivated: Qt.quit()
}
With the context property, you can choose whether you want the shortcut to apply to the current window or the entire application.
The motivation for this type can be seen in the comments of patch set 5:
Shortcut aims to supersede Action. I want to kill the latter in the future because...
compare the actual user code: http://cutebin.fi/prwznhkbo
look at the amount of "action" related expressions all around BasicButton.qml
IMHO the whole concept doesn't quite fit mobile/embedded or QML
Action was a frequently requested feature. Now that they have it, the frequent questions are "how to use a different icon/text" or "how to know the source that triggered an action". Both are contradicting the sole purpose of Action, and neither "problem" would exist if they just wrote simpler QML code in the first place, as illustrated by the example snippet. :)
Evidently the most usable part of Action is the shortcut feature. Those who need shortcuts are not happy that they need to use Action, because "what's up with all this other stuff, I just want a shortcut".
Maybe there are different ways of achieving this, but the way I know is the following one.
The idea is to have an Item which controls the key events you need to handle.
I'll explain myself with an example. As you will see, if we have input widgets (i.e. TextInput) we have to implement a mechanism to return the input to our Item in order to process again the keyboard events. In this example, the Qt.Key_Escape key will be used to set the focus back.
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
id: mainwindow
title: qsTr("Hello")
width: 640
height: 480
visible: true
Item {
anchors.fill: parent
focus: true
Keys.onPressed: {
if ( (event.key === Qt.Key_Q) && (event.modifiers & Qt.ShiftModifier) ) {
rect.blue()
} else if ( (event.key === Qt.Key_W) && (event.modifiers & Qt.AltModifier) ) {
rect.red()
} else if ( (event.key === Qt.Key_E) && (event.modifiers & Qt.AltModifier) ) {
text.text = 'Key Alt+E was pressed'
}
}
Rectangle{
id: rect
width: 100
height: 100
color: "black"
function blue() {color = "blue"}
function red() {color = "red"}
}
Text {
id: text
anchors.centerIn: parent
font.pointSize: 20
}
TextInput {
id: textinput
anchors.top: text.bottom
text: "sample text"
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
console.log('Key Escape was pressed');
parent.focus = true;
}
}
}
}
}
Edit #1: #Mitch suggested to use the Shortcut QML Type. If you can use it (it's available since Qt 5.5), the code will be slightly different. Anyway, you need also to set the focus to the main app in some cases depending on the shortcut sequences implemented. For example, if we're typing text, Shift+Q doesn't have effect in this example. We need to press Escape first.
import QtQuick 2.5
import QtQuick.Controls 1.3
ApplicationWindow {
id: mainwindow
title: qsTr("Hello")
width: 640
height: 480
visible: true
Shortcut {
sequence: "Shift+Q"
onActivated: rect.blue()
context: Qt.ApplicationShortcut
}
Shortcut {
sequence: "Alt+W"
onActivated: rect.red()
context: Qt.ApplicationShortcut
}
Shortcut {
sequence: "Alt+E"
onActivated: text.text = 'Key Alt+E was pressed'
context: Qt.ApplicationShortcut
}
Item {
anchors.fill: parent
Rectangle{
id: rect
width: 100
height: 100
color: "black"
function blue() {color = "blue"}
function red() {color = "red"}
}
Text {
id: text
anchors.centerIn: parent
font.pointSize: 20
}
TextInput {
id: textinput
anchors.top: text.bottom
text: "sample text"
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
console.log('Key Escape was pressed');
parent.focus = true;
}
}
}
}
}
Much like Mitch, I found focus to be a mess in QML, much like many other aspects of it.
I ended up implementing my own "active focus / selection" scheme. Basically I keep a list of item pointers as my "active selection", I have the keyboard focus fixed at a single item acting as an event dispatcher, and it redirects keyboard events to all items in the active selection list. I still use QML's MouseArea to manage the selected items.

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.

Resources