【问题标题】:How to allow circular navigation of items in a QComboBox?如何允许对 QComboBox 中的项目进行循环导航?
【发布时间】:2020-08-03 20:50:11
【问题描述】:

我正在编写的 Qt 桌面应用程序在 UI 中包含一个 QCombobox(使用 Designer 制作)。选择 QCombobox 后,我可以通过滚动鼠标滚轮或按键盘上的向上/向下箭头来更改所选项目。一切正常。

使用键盘向下箭头导航时,例如,当我到达列表中的底部项目时,向下箭头不再更改所选项目。我知道这是预期的行为。

但是对于这个特定的 QComboBox,我希望能够在到达列表中的最后一项后继续按下向下箭头,然后“换行”回第一项,这样我就可以继续循环浏览这些项目。我在https://doc.qt.io/qt-5/qcombobox.html 研究了QComboBox 的文档,在https://doc.qt.io/qt-5/qabstractitemmodel.html 研究了QAbstractItemModel 的文档,但我找不到任何方法来实现我想要的。

理想情况下,我更喜欢一种适用于键盘箭头导航、鼠标滚轮导航以及任何其他可能尝试激活 QComboBox 中“下一个”或“上一个”项目的 UI 手势的解决方案。

【问题讨论】:

    标签: qt qcombobox


    【解决方案1】:

    我没有尝试过这个解决方案,但我猜直觉是对的。 我认为你需要这样做:

    1. 覆盖 keyPressEvent(QKeyEvent *e) 以检测向上和向下箭头。
    2. 如果按下向下箭头,则使用 currentIndex() const 函数检查它是否是最后一个索引,与组合框本身的大小相比。
    3. 如果是这样,请使用 setCurrentIndex(int index) 将当前索引更改为第一个。
    4. 如果您到达第一个索引,请对向上箭头执行相同操作。

    附:由于currentIndex() 在按下后返回索引,这可能会使其从倒数第二个索引跳转到第一个索引。因此,我建议在第一次满足条件时使用私有布尔成员进行切换。

    我希望这个解决方案对您有所帮助。

    【讨论】:

    • 谢谢。这种方法可能会起作用,同时也改变 mouseWheelEvent() 。但我仍然希望有更高层次的方法。 atm 我正在考虑覆盖 QListView::moveCursor(...) ,这听起来可能是最好的方法。但它还没有工作。
    • @ChristopherBruns 如果可行,请将其标记为已接受的答案。此外,我寻找更强大的解决方案但徒劳无功。如果你愿意,我希望你能找到一个更好的。
    【解决方案2】:

    这个问题的完整解决方案有几个不同的方面。

    1. QComboBox 展开以显示所有项目时,一个优雅的语义解决方案是覆盖QAbstractItemView::moveCursor() 方法。这部分解决方案不需要低级事件处理程序,因为moveCursor() 封装了“下一个”和“上一个”的概念。遗憾的是,这只适用于 QComboBox 展开时。请注意,在这种情况下,这些项目在导航过程中实际上并没有被激活,直到出现另一个手势,如单击或输入。

    2. QComboBox 被折叠以一次显示一个项目(通常情况下)时,我们必须采用捕获每个相关手势的低级方法,正如 Mohammed Deifalah 的回答中所描绘的那样。我希望 Qt 在这里有一个类似于QAbstractItemView::moveCursor() 的抽象,但它没有。在下面的代码中,我们捕获了按键和鼠标滚轮事件,这是我目前知道的唯一手势。如果还需要其他手势,我们将需要独立实现每一个。因为 Qt 架构师没有像 QAbstractItemView::moveCursor() 那样对这些案例的“下一个”和“上一个”概念进行概括。

    以下代码为实现这些原则的 QComboBox 定义了一个替换类。

    from PySide2 import QtCore, QtGui, QtWidgets
    from PySide2.QtCore import Qt
    
    
    # CircularListView allows circular navigation when the ComboBox is expanded to show all items
    class CircularListView(QtWidgets.QListView):
        """
        CircularListView allows circular navigation.
        So moving down from the bottom item selects the top item,
        and moving up from the top item selects the bottom item.
        """
    
        def moveCursor(
            self,
            cursor_action: QtWidgets.QAbstractItemView.CursorAction,
            modifiers: Qt.KeyboardModifiers,
        ) -> QtCore.QModelIndex:
            selected = self.selectedIndexes()
            if len(selected) != 1:
                return super().moveCursor(cursor_action, modifiers)
            index: QtCore.QModelIndex = selected[0]
            top = 0
            bottom = self.model().rowCount() - 1
            ca = QtWidgets.QAbstractItemView.CursorAction
            # When trying to move up from the top item, wrap to the bottom item
            if index.row() == top and cursor_action == ca.MoveUp:
                return self.model().index(bottom, index.column(), index.parent())
            # When trying to move down from the bottom item, wrap to the top item
            elif index.row() == bottom and cursor_action == ca.MoveDown:
                return self.model().index(top, index.column(), index.parent())
            else:
                return super().moveCursor(cursor_action, modifiers)
    
    
    class CircularCombobox(QtWidgets.QComboBox):
        def __init__(self, *args, **kwargs) -> None:
            super().__init__(*args, **kwargs)
            view = CircularListView(self.view().parent())
            self.setView(view)
    
        def _activate_next(self) -> None:
            index = (self.currentIndex() + 1) % self.count()
            self.setCurrentIndex(index)
    
        def _activate_previous(self):
            index = (self.currentIndex() - 1) % self.count()
            self.setCurrentIndex(index)
    
        def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
            if event.key() == Qt.Key_Down:
                self._activate_next()
            elif event.key() == Qt.Key_Up:
                self._activate_previous()
            else:
                super().keyPressEvent(event)
    
        def wheelEvent(self, event: QtGui.QWheelEvent) -> None:
            delta = event.angleDelta().y()
            if delta < 0:
                self._activate_next()
            elif delta > 0:
                self._activate_previous()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 2015-07-26
      • 2022-01-17
      • 2015-09-23
      • 2011-07-18
      • 1970-01-01
      相关资源
      最近更新 更多