如何允许在QComboBox中循环导航项目



我正在编写的Qt桌面应用程序在UI中包含一个QCombox(由Designer制作(。选择Q组合框后,我可以通过滚动鼠标滚轮或按下键盘上的上/下箭头来更改所选项目。这一切都很好。

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

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

理想情况下,我更喜欢一种适用于键盘箭头导航、鼠标滚轮导航和任何其他可能试图激活";下一个";或";先前的";QComboBox中的项目。

我没有尝试这个解决方案,但我凭直觉猜测它是正确的。我认为你需要做:

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

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

我希望这个解决方案能帮助你。

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

  1. 当扩展QComboBox以显示所有项时,一个优雅的语义解决方案是覆盖QAbstractItemView::moveCursor()方法。解决方案的这一部分不需要低级别的事件处理程序,因为CCD_;下一个";以及";先前的";。遗憾的是,这只有在扩展QComboBox时才有效。请注意,在这种情况下,项目在导航过程中实际上并没有被激活,直到出现另一个手势,如点击或输入。

  2. QComboBox被折叠以一次显示一个项目时(通常情况下(,我们必须采用低级别的方法来捕捉每个相关的手势,正如Mohammed Deifallah的回答所描绘的那样。我希望Qt在这里有一个类似于QAbstractItemView::moveCursor()的抽象,但它没有。在下面的代码中,我们捕捉了按键和鼠标滚轮事件,这是我目前所知道的唯一手势。如果还需要其他手势,我们需要独立地实现每一个手势。因为Qt建筑师没有将";下一个";以及";先前的";对于这些情况,他们对CCD_ 11所做的那样。

以下代码为实现这些原则的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()

最新更新