How to receive keyboard events in an unfocused QtQuick Item? - qt

I want to use a press-and-hold behaviour to switch states of a gui Item.
I use a FocusScope(below) to recieve keyboard events.
FocusScope{
id:pageFocus
property var pedalKey//a key id
Keys.enabled: true
Keys.onPressed: {
if(event.key===pedalKey && !event.isAutoRepeat)
{
state="a"
}
}
Keys.onReleased: {
if(event.key===pedalKey && !event.isAutoRepeat)
{
state="b"
}
}
}
It works, but when FocusScope loses the focus.
The most terrible thing is that I don't know which Item got the focus.
Is there any way to enable the Item to receive keyboard events without focus?

You can forward key events to other objects (even multiple objects). Here is the example from Qt's documentation:
Item {
ListView {
id: list1
// ...
}
ListView {
id: list2
// ...
}
Keys.forwardTo: [list1, list2]
focus: true
}

It works, but when FocusScope lost the focus.
Yes, key events are only delivered to the items with activeFocus. The event will be sent to the inner-most item first, proceeding up the chain of parents until one of them accepts the events (using e.g. the handlers you're using here).
The most terrible thing is that I don't know which Item got the focus.
You can use the Window.activeFocusItem attached property to see where the focus is currently.
Is there any way to enable the Item to receive keyboard events without focus?
Not easily or directly. You could use event filtering to intercept events before they get to the window, but I would consider that absolutely an option of last resort. Shortcuts are another possibility, depending on what presses you are trying to intercept.

Related

How to find subsequent (all) SLOT from a subclass

I need to block specific buttons on an MMI.
I implemented a button blocking function in a subclass of QPushButton.
For this, I used the clicked() signal and blocked the button with blockSignals(true).
This means that with each button clicked on my MMI, 2 SLOTS are always called.
But when calling the blocking of a specific button, I get the first SLOT (clicked()) of my subclass, in which I block the button, then I then arrive in the original SLOT linked to this button, which is still called despite the blocking (the first time only).
How can I in my QPushButton subclass know the subsequent SLOTs linked to this button and avoid them (delete them)?
void QbtnStandardButton::slotButtonClicked(void)
{
if (modeProtection)
{
// Special mode to protect/unprotect the button
if (isProtected())
{
// Reset the protection
this->blockSignals(false);
}
else
{
// Set the protection: button will be unclickable
this->blockSignals(true);
}
modeProtection = false;
}
if (isProtected())
{
QMessageBox *pMsgBox = new QMessageBox(QMessageBox::Information,
"Protection",
"This button is protected!",
QMessageBox::Ok);
pMsgBox->exec();
pMsgBox->deleteLater();
// Here: remove subsequent SLOT of this button ?
}
}
I think it's very difficult if not impossible to find SLOTS linked to a button.
I worked around the problem by using an eventFilter() instead of a SIGNAL() in my base class.
In this case, I can filter the "clicked()" event before it is reissued.

QML Keyboard Shortcuts are interfering with Key OnPressed Events

I'm having an issue with my QML program. In the top-level main.qml file, I have some keyboard Shortcuts like so:
Shortcut {
sequence: "Up"
onActivated: {
// yadda yadda
}
}
In another file, I have several Keys.onPressed calls like this:
Keys.onPressed: {
if (event.key == Qt.Key_Up) {
// yadda yadda
}
}
Apparently, the Shortcuts are interfering with the OnPressed calls. When I comment out the Shortcuts, the OnPressed's work fine. But when they're both active, it seems the Shortcut intercepts the keyboard press and prevents the OnPressed from activating.
I know that with mouse events, Qt has the "accepted" variable. If you want an event to continue propagating down the stack, you can just set "accepted = false" in the OnActivated function in order to accomplish this. However, I am not seeing any equivalent "accepted" variable in the Shortcuts API. Is there some other way I can ensure that the event is propagated correctly?
In order to act like a shortcut, Shortcut must have a higher priority than key handlers in active focus items. Otherwise it would be no different to a normal key handler. However, sometimes there is a need to override a shortcut, like you do.
In Qt 5.8 and earlier, you can disable a Shortcut to prevent it processing shortcut events under certain conditions. For example:
Shortcut {
enabled: !someItem.activeFocus
}
In Qt 5.9, a better mechanism has been introduced for this. Active focus items with key handlers can now override shortcuts by accepting shortcut override events using Keys.shortcutOverride. For example:
Item {
focus: true
Keys.onShortcutOverride: {
if (event.key == Qt.Key_Up)
event.accepted = true
}
Keys.onUpPressed: ...
}

Prevent Controls from stealing keyboard focus

In QML only a single object can have keyboard focus (per window). In my application, I need the option of having multiple objects with keyboard focus, thus I use a custom event dispatcher in combination with a custom multiple selection implementation.
The problem is however that every time any of the stock Control elements are clicked, they automatically steal the focus, breaking the custom event dispatcher.
In addition to that, it still needs to be possible to explicitly set another focus item, in the case of overlay popups and such.
I'm not sure how it fits in with your custom event stuff, but this answer might also help others who have found your question but are simply looking to prevent a control from getting focus.
You can prevent controls from getting focus with the focusPolicy enum:
Button {
focusPolicy: Qt.NoFocus
// Other options:
// focusPolicy: Qt.TabFocus - The control accepts focus by tabbing.
// focusPolicy: Qt.ClickFocus - The control accepts focus by clicking.
// focusPolicy: Qt.StrongFocus - The control accepts focus by both tabbing and clicking.
// focusPolicy: Qt.WheelFocus - The control accepts focus by tabbing, clicking, and using the mouse wheel.
}
I ended up with this interface, applied to all focus-able items:
Item {
onFocusChanged: if (keepFocus) focus = true
property bool keepFocus: false
property Item prevFocus: null
function getFocus() {
if (prevFocus) {
prevFocus.keepFocus = false
keepFocus = true
focus = true
}
}
function restoreFocus() {
if (prevFocus) {
keepFocus = false
prevFocus.keepFocus = true
prevFocus.focus = true
}
}
}
Since only overlay dialogs are supposed to take focus from the event dispatcher, the dialog base type automatically handles the acquiring and restoring of focus on dialog show and hide respectively.
So from "one item may have focus" I move to a "one item may have explicit focus", causing the focus to be re-enabled for that item whenever a Control element might steal it.

QML Calendar : Programmatically call onClicked handler

My QML application is displaying a Calendar element.
When the selected date is changed (clicked), I need to update an other element (an image).
I have something like this:
Calendar {
id: calCalendar
onClicked: {
// update other QML element
}
}
It works fine when the user click with the mouse on the calendar: the other element (the image) is correctly updated.
My problem is initialization : when my app is started, the calendar displays the current date by default, and I'd like to programmatically call the onClicked handler, to make sure the image is up to date.
I don't know how to do that.
If you want to do something when a QML component is done initializing you can use the Component.onCompleted : slot.
Calendar {
id: calCalendar
onClicked: {
// update other QML element
}
Component.onCompleted: {
// Do stuff for initialization.
// you could do this here : calCalendar.Clicked()
// if you want to use the same code for initialization and for user input handling later on.
}
}
The point is the following : onXXX : { declares a slot to handle the singal XXX. Here the signal is Clicked. You can trigger the signal programmatically as you say, just by invoking it like a function. You'll need to know a valid overload for the arguments (if any).

How to know when the selected item has been changed in a QML QListView?

I'm using QtQuick 2.0 and and a QML ListView to display some items, and I need to know when the user chooses a different item. Emitting a signal when the user clicks a mouse area in the delegate works, i.e.
MouseArea{
onClicked: {
controller.itemChanged(model.item);
someList.currentIndex = index;
}
}
but only if the user uses the mouse to choose the item, but it doesn't work if the user uses the arrow keys.
I've been looking through the docs to find what signal is emitted when the currentIndex is changed, but I can't seem to find any. I'm looking for something similar to QListWidget::itemSelectionChanged() but it seems QML ListView doesn't have that.
You just need onCurrentItemChanged:{} in your ListView.
I ended up having to re-implement keyboard behaviour and exposing the model data from the delegate so I could fire the signal when a key is pressed.
ListView {
id: myList
focus: true
orientation: "Horizontal" //This is a horizontal list
signal itemChanged(var item)
interactive: false //Disable interactive so we can re-implement key behaviour
Keys.onPressed: {
if (event.key == Qt.Key_Left){
myList.decrementCurrentIndex(); //Change the current list selection
itemChanged(myList.currentItem.selectedItem.data); //Fire signal notifying that the selectedItem has changed
}
else if (event.key == Qt.Key_Right){
myList.incrementCurrentIndex(); //Change the current list selection
itemChanged(myList.currentItem.selectedItem.data); //Fire signal notifying that the selectedItem has changed
}
}
delegate: Component {
Rectangle {
id: myItem
property variant selectedItem: model //expose the model object
MouseArea {
anchors.fill: parent
onClicked: {
myList.currentIndex = index; //Change the current selected item to the clicked item
itemChanged(model.data); //Fire signal notifying that the selectedItem has changed
}
}
}
}
}
With this solution you have to manually change the item in QML whenever the user clicks an item or presses a key. I'm not sure this'd be an optimal solution with GridView but it works fine with ListView.
See this question. There are two approaches you can take
Connect to another component's event
Handle the event within that component
The signal handler is named on<SignalName> with the first letter of the signal in uppercase.

Resources