按tab键时移动到下一个选项卡(并关注相应的小部件)



在我的应用程序中,我有一个QTabWidget,它拥有一个看似"相同"的变量数量。具有可变数量小部件的选项卡。我想,一旦TAB(或shift-TAB)按钮被按下,应用程序的焦点移动到下一个(或上一个)选项卡,并专注于该选项卡的相应小部件(对应于小部件的焦点,直到按下键)。

用一种简单的方式来解决这个问题的最好方法是什么?我试过使用快捷键来捕捉按键,但我似乎找不到一种方法来在下一个或上一个标签中获得相应的小部件并专注于此。

下面是一个最小的代码示例,它只是移动到下一个选项卡,而不是相应的小部件:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import *

class tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self)
shortcut.activated.connect(self.on_tab)
shortcut2 = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Backtab), self)
shortcut2.activated.connect(self.on_shift_tab)

self.tabs = []
for i in range(num_tabs):
newtab = QWidget()
self.tabs.append(newtab)
self.addTab(newtab, f'Tab {i}')
self.add_widgets_to(newtab)

def add_widgets_to(self, tab):
layout = QVBoxLayout()
tab.setLayout(layout)
layout.addWidget(QSpinBox())
layout.addWidget(QCheckBox())
gender = QHBoxLayout()
gender.addWidget(QRadioButton("Male"))
gender.addWidget(QRadioButton("Female"))
layout.addLayout(gender)
@QtCore.pyqtSlot()
def on_tab(self):
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab + 1) % self.count())
# TODO find current widget in focus, and find the corresponding one in the next tab, and focus on that one... note that widgets could be complex (i.e., not direct children...)


@QtCore.pyqtSlot()
def on_shift_tab(self):
print("do_something")
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab - 1) % self.count())

def main():
app = QApplication(sys.argv)
ex = tabdemo()
ex.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()

由于OP表明每个页面将具有相同的组件,因此可以关联索引,以便在更改选项卡之前获得选项卡的索引,然后设置小部件的焦点,然后将焦点设置为其他相应的小部件。

import sys
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QHBoxLayout,
QRadioButton,
QShortcut,
QSpinBox,
QTabWidget,
QVBoxLayout,
QWidget,
)

class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
spinbox = QSpinBox()
checkbox = QCheckBox()
male_radio = QRadioButton("Male")
female_radio = QRadioButton("Female")
layout = QVBoxLayout(self)
layout.addWidget(spinbox)
layout.addWidget(checkbox)
gender = QHBoxLayout()
gender.addWidget(male_radio)
gender.addWidget(female_radio)
layout.addLayout(gender)
for i, widget in enumerate((spinbox, checkbox, male_radio, female_radio)):
widget.setProperty("tab_index", i)

class Tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)
for i in range(num_tabs):
page = Page()
self.addTab(page, f"Tab {i}")
@pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
@pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, index):
focus_widget = QApplication.focusWidget()
tab_index = focus_widget.property("tab_index") if focus_widget else None
self.setCurrentIndex(index)
if tab_index is not None and self.currentWidget() is not None:
for widget in self.currentWidget().findChildren(QWidget):
i = widget.property("tab_index")
if i == tab_index:
widget.setFocus(True)

def main():
app = QApplication(sys.argv)
ex = Tabdemo()
ex.show()
sys.exit(app.exec_())

if __name__ == "__main__":
main()

基于eyllanesc的回答,我将功能改进为:

  • 滚动条位置的帐户(如果存在)
  • 使用双向字典(在这里实现)而不是线性查找
  • 使用update_map()方法动态添加所有相关小部件,而不必手动添加每个小部件。

张贴,以防有人发现有用。

from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QWidget, QTabWidget, QShortcut, QApplication, QScrollArea

class BidirectionalDict(dict):
def __init__(self, *args, **kwargs):
super(BidirectionalDict, self).__init__(*args, **kwargs)
self.inverse = {}
for key, value in self.items():
self.inverse.setdefault(value, []).append(key)
def __setitem__(self, key, value):
if key in self:
self.inverse[self[key]].remove(key)
super(BidirectionalDict, self).__setitem__(key, value)
self.inverse.setdefault(value, []).append(key)
def __delitem__(self, key):
self.inverse.setdefault(self[key], []).remove(key)
if self[key] in self.inverse and not self.inverse[self[key]]:
del self.inverse[self[key]]
super(BidirectionalDict, self).__delitem__(key)
def get_first_inv(self, key):
return self.inverse.get(key, [None])[0]

class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.widgets_map = BidirectionalDict()
# ... add your widgets ...
self.update_map()

def update_map(self):
widgets = self.findChildren(QWidget)
for i, widget in enumerate(widgets):
self.widgets_map[i] = widget

class MyQTabWidget(QTabWidget):
def __init__(self):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)

@pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
@pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, new_tab_index):
old_tab: Page = self.currentWidget()
focus_widget = QApplication.focusWidget()
widget_index = old_tab.widgets_map.get_first_inv(focus_widget) if focus_widget else None
self.setCurrentIndex(new_tab_index)
new_tab: Page = self.currentWidget()
if new_tab is not None and widget_index is not None:
corresponding_widget: QWidget = new_tab.widgets_map[widget_index]
corresponding_widget.setFocus(True)
# Move scrollbar to the corresponding position
if hasattr(old_tab, 'scrollbar'):
# Tabs are identical so new_tab must have scrollbar as well
old_y = old_tab.scrollbar.verticalScrollBar().value()
scrollbar: QScrollArea = new_tab.scrollbar
scrollbar.verticalScrollBar().setValue(old_y)

最新更新