在 ListView 之间传递焦点

Passing focus between ListView

我正在创建一个支持多选的 QML ListView,并将在我的应用程序中实例化其中的多个。我需要为特定列表提供键盘焦点,处理该列表的按键,并根据焦点为选定的行绘制突出显示。

但是,将 focus=true 赋予 ListView 委托或 ListView 本身不会导致 activeFocus 出现在 ListView 上,也不会导致触发按键信号。

我创建了一个简单的示例应用来展示我的问题:

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

单击任一列表不会将所选行变成粉红色,并且按 up/down 键不会调整选择。

如何将焦点传递给特定的 ListView 以便 (a) 一次只有一个 ListView 持有它,并且 (b) 我可以检测到 ListView 何时持有此焦点,并且 (c) 它允许键盘信号仅在聚焦时为该 ListView 工作?

我正在使用 Qt 5.7,以防万一。


我尝试过但未成功的事情:


注意:我知道标准 ListView 使用 highlight 来显示选择,并使用键盘导航来调整 currentItem。但是因为我的需求需要多选并发,所以我必须专门管理高亮和键盘。


编辑:这是一个更简单的测试用例,它的行为与我预期的不同:

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

点击每个矩形使其变为红色,并且在任何给定时间只允许一个为红色。 (很好。)但是,文本 "focused" 没有出现,因为 Rectangle 没有 activeFocus。此外,按下 space 永远不会在任何一个被聚焦时记录消息。

编辑 2:哇哦。如果我删除 FocusScope,它会按预期工作:焦点仍然是独占的,activeFocus 被授予,并且 space 正常工作。也许我上面的问题是因为 ListViewFocusScope (根据文档)。

编辑 3:根据下面 Mitch 的评论,在 FocusScope 上设置 focus:true 还可以让一切都与 FocusScope 一起正常工作。但是,根据我在下面的回答(以及 Mitch 的评论),FocusScope 并不是让我的任何一个简化示例应用程序正常工作所必需的。

我原来的示例应用程序中的问题似乎与我使用 Loader 有关。忽略 Window 和委托组件代码,用显式列表替换我的 Loader 代码有效:

ListView {
  width:window.width/2-6; height:window.height-8; x:4; y:4
  delegate: row
  model:    ['a','b','c','d']
  Keys.onUpPressed:   decrementCurrentIndex()
  Keys.onDownPressed: incrementCurrentIndex()
}
ListView {
  width:window.width/2-6; height:window.height-8; x:window.width/2+2; y:4
  delegate: row
  model:    ['1','2','3','4','5']
  Keys.onUpPressed:   decrementCurrentIndex()
  Keys.onDownPressed: incrementCurrentIndex()
}

...如果我使用基于文件的组件,它也可以工作:

MyList.qml

import QtQuick 2.7
ListView {
    delegate: row
    width:window.width/2-6; height:window.height-8; y:4
    Keys.onUpPressed:   decrementCurrentIndex()
    Keys.onDownPressed: incrementCurrentIndex()
}

main.qml

// ...Window and row component as in the question above..
MyList { x:4;                model:['a','b','c','d']      }
MyList { x:window.width/2+2; model: ['1','2','3','4','5'] }

...但是这个原始代码不起作用,因为 ListView 从未给出 activeFocus:

Component {
  id: myList
  ListView {
    delegate: row
    width:window.width/2-6; height:window.height-8; y:4
    Keys.onUpPressed:   decrementCurrentIndex()
    Keys.onDownPressed: incrementCurrentIndex()
  }
}
Loader {
  sourceComponent:myList; x:4
  onLoaded: item.model = ['a','b','c','d']
}
Loader {
  sourceComponent:myList; x:window.width/2+2
  onLoaded: item.model = ['1','2','3','4','5']
}

如果您打印出原始示例中的活动焦点项

onActiveFocusItemChanged: print(activeFocusItem)

您可以看到 none 的加载程序获得了焦点;它始终是 window.

的根项

如果您在其中一个加载程序上设置 focus: true,那么它将获得焦点:

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

但是,现在当您单击其代理时,第二个视图没有焦点。如果我们将它们都声明为具有焦点,我们将无法控制哪一个以焦点结束。事实上,即使我们确实将它们声明为都具有焦点,单击第二个视图的委托也不会为该视图提供活动焦点,因为当第一个加载器获得它时,它处于失去焦点的加载器。换句话说,要让它工作,你还必须给予 loader 焦点,这违背了将委托组件捆绑在一起的全部目的:

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

在这一点上我会放弃并强制主动关注:

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

我真的不喜欢对焦系统。我并不是说我可以想出更好的东西,但它并不容易使用。