I am creating a QML ListView
that supports multiple selection, and will be instantiating multiple of these in my application. I need to give keyboard focus to a specific list, handle key presses for that list, and draw highlighting for selected row(s) depending on the focus.
However, giving focus=true
to either a ListView delegate or the ListView itself does not cause activeFocus
to be present on the ListView, and does not cause key press signals to be triggered.
I have created a simple sample app showing my problem:
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
id:window; visible:true; width:400; height:160; color:'darkgray'
Component {
id: row
Rectangle {
id: root
property int i: index
property bool current: ListView.isCurrentItem
property bool focused: ListView.view.activeFocus
width:parent.width; height:20
color: current ? ( focused ? 'pink' : 'lightblue' ) : 'lightgray'
MouseArea {
anchors.fill: parent
onClicked: {
root.ListView.view.currentIndex = i;
root.ListView.view.focus = true;
}
}
Text { anchors.fill:parent; text:modelData }
}
}
Component {
id: myList
ListView {
delegate: row
width:window.width/2-10; height:window.height; y:5
Keys.onUpPressed: decrementCurrentIndex()
Keys.onDownPressed: incrementCurrentIndex()
}
}
Loader {
sourceComponent:myList; x:5
onLoaded: item.model = ['a','b','c','d']
}
Loader {
sourceComponent:myList; x:window.width/2
onLoaded: item.model = ['1','2','3','4','5']
}
}
Clicking on either list does not turn the selected row pink, and pressing key up/down does not adjust the selection.
How can I pass focus to a particular ListView
such that (a) only one ListView holds it at a time, and (b) I can detect when a ListView holds this focus, and (c) it allows keyboard signals to work for that ListView only while focused?
I am using Qt 5.7, in case it matters.
Things I've tried, unsuccessfully:
focus=true
on the Rectangle instead of the ListView.focused
property to watch for ListView.view.focus
instead of activeFocus
: this allows both lists to turn pink simultaneously.Reading through Keyboard Focus in Qt Quick and wrapping either the Rectangle or the ListView in a FocusScope
. (What a pain that is, forwarding all interfaces along.)
Re-reading the page in the light of day and wrapping the two Loader
in a single FocusScope
. Cursing as both ListView are apparently allowed to have simultaneous focus, violating my reading of this section of the documentation:
Within each focus scope one object may have
Item::focus
set to true. If more than one Item has thefocus
property set, the last type to set the focus will have thefocus
and the others are unset, similar to when there are no focus scopes.
Wrapping the ListView in an Item and putting the Keys handlers on that item, and attempting to focus that item.
interactive:false
on the ListView.keyNavigationEnabled:false
on the ListView. (This oddly yields "ListView.keyNavigationEnabled" is not available in QtQuick 2.7
, despite the Help stating that it was introduced in 5.7, and suggesting that QtQuick 2.7 is the proper import statement.)focus:true
on just about every object I can find, just in case.Note: I realize that a standard ListView
uses a highlight
to show selection, and keyboard navigation to adjust the currentItem
. However, because my needs require multiple concurrently-selected items, I must manage the highlight and keyboard specially.
Edit: here's a simpler test case that's not behaving as I would expect:
import QtQuick 2.7
import QtQuick.Window 2.0
Window {
id:window; visible:true; width:400; height:160; color:'darkgray'
FocusScope {
Rectangle {
width: window.width/2-6; height:window.height-8; x:4; y:4
color: focus ? 'red' : 'gray'
MouseArea { anchors.fill:parent; onClicked:parent.focus=true }
Text { text:'focused'; visible:parent.activeFocus }
Keys.onSpacePressed: console.log('space left')
}
Rectangle {
width: window.width/2-6; height:window.height-8; x:window.width/2+2; y:4
color: focus ? 'red' : 'gray'
MouseArea { anchors.fill:parent; onClicked:parent.focus=true }
Text { text:'focused'; visible:parent.activeFocus }
Keys.onSpacePressed: console.log('space right')
}
}
}
Clicking on each Rectangle makes it turn red, and only one is allowed to be red at any given time. (That's good.) However, the text "focused" does not show up, because the Rectangle does not have activeFocus
. Further, pressing space never logs a message when either is focused.
Edit 2: Whoa. If I delete the FocusScope it works as expected: the focus is still exclusive, activeFocus
is granted, and the space works correctly. Perhaps my problems above are because ListView
is a FocusScope
(according to the docs).
Edit 3: Per Mitch's comment below, setting focus:true
on the FocusScope
also allows everything to work correctly with the FocusScope. However, per my answer below (and Mitch's comment), the FocusScope is not necessary to get either of my simplified sample apps working correctly.
If you print out the active focus item in your original example
onActiveFocusItemChanged: print(activeFocusItem)
you can see that none of the loaders ever get focus; it's always the root item of the window.
If you set focus: true
on one of the loaders, then it will have focus:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: window
visible: true
width: 400
height: 160
color: 'darkgray'
onActiveFocusItemChanged: print(activeFocusItem)
Component {
id: row
Rectangle {
id: root
property int i: index
property bool current: ListView.isCurrentItem
property bool focused: ListView.view.activeFocus
width: parent.width
height: 20
color: current ? (focused ? 'pink' : 'lightblue') : 'lightgray'
MouseArea {
anchors.fill: parent
onClicked: {
root.ListView.view.currentIndex = i
root.ListView.view.focus = true
}
}
Text {
anchors.fill: parent
text: modelData
}
}
}
Component {
id: myList
ListView {
delegate: row
width: window.width / 2 - 10
height: window.height
y: 5
Keys.onUpPressed: decrementCurrentIndex()
Keys.onDownPressed: incrementCurrentIndex()
}
}
Loader {
objectName: "loader1"
sourceComponent: myList
focus: true
x: 5
onLoaded: item.model = ['a', 'b', 'c', 'd']
}
Loader {
objectName: "loader2"
sourceComponent: myList
x: window.width / 2
onLoaded: item.model = ['1', '2', '3', '4', '5']
}
}
However, now the second view doesn't have focus when you click on its delegates. If we declared them as both having focus, we lose control over which one ends up with it. In fact, even if we do declare them as both having focus, clicking on the second view's delegates doesn't give that view active focus, because the loader that it was in lost focus when the first loader got it. In other words, for it to work, you'd have to give the loader focus as well, which defeats the whole purpose of having nice little bundled up delegate components:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: window
visible: true
width: 400
height: 160
color: 'darkgray'
onActiveFocusItemChanged: print(activeFocusItem)
Component {
id: row
Rectangle {
id: root
property int i: index
property bool current: ListView.isCurrentItem
property bool focused: ListView.view.activeFocus
width: parent.width
height: 20
color: current ? (focused ? 'pink' : 'lightblue') : 'lightgray'
MouseArea {
anchors.fill: parent
onClicked: {
root.ListView.view.currentIndex = i
root.ListView.view.parent.focus = true
root.ListView.view.focus = true
}
}
Text {
anchors.fill: parent
text: modelData
}
}
}
Component {
id: myList
ListView {
delegate: row
width: window.width / 2 - 10
height: window.height
y: 5
Keys.onUpPressed: decrementCurrentIndex()
Keys.onDownPressed: incrementCurrentIndex()
}
}
Loader {
objectName: "loader1"
sourceComponent: myList
x: 5
onLoaded: item.model = ['a', 'b', 'c', 'd']
}
Loader {
objectName: "loader2"
sourceComponent: myList
x: window.width / 2
onLoaded: item.model = ['1', '2', '3', '4', '5']
}
}
At this point I would just give up and force active focus:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: window
visible: true
width: 400
height: 160
color: 'darkgray'
onActiveFocusItemChanged: print(activeFocusItem)
Component {
id: row
Rectangle {
id: root
objectName: ListView.view.objectName + "Rectangle" + index
property int i: index
property bool current: ListView.isCurrentItem
property bool focused: ListView.view.activeFocus
width: parent.width
height: 20
color: current ? (focused ? 'pink' : 'lightblue') : 'lightgray'
MouseArea {
anchors.fill: parent
onClicked: {
root.ListView.view.currentIndex = i
root.ListView.view.forceActiveFocus()
}
}
Text {
anchors.fill: parent
text: modelData
}
}
}
Component {
id: myList
ListView {
objectName: parent.objectName + "ListView"
delegate: row
width: window.width / 2 - 10
height: window.height
y: 5
Keys.onUpPressed: decrementCurrentIndex()
Keys.onDownPressed: incrementCurrentIndex()
}
}
Loader {
id: loader1
objectName: "loader1"
sourceComponent: myList
x: 5
onLoaded: item.model = ['a', 'b', 'c', 'd']
}
Loader {
objectName: "loader2"
sourceComponent: myList
x: window.width / 2
onLoaded: item.model = ['1', '2', '3', '4', '5']
}
}
I really dislike the focus system. I'm not saying I could come up with something better, but it's just not easy to use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With