如何解决按 Tab 键时移动到下一个选项卡并专注于相应的小部件
在我的应用程序中,我有一个 QTabWidget,它包含可变数量的看似“相同”的选项卡和可变数量的小部件。 我希望,一旦按下 TAB(或 shift-TAB)按钮,应用程序的焦点就会移动到下一个(或上一个)选项卡,并专注于该选项卡的相应小部件(与小部件对应的小部件)焦点直到按下按键)。
以简单的方式解决这个问题的最佳方法是什么?我尝试使用 QShortcut 来捕捉按键,但我似乎无法找到一种方法来在下一个或上一个选项卡中获取相应的小部件并专注于此。
这是代码的一个最小示例,它只是移动到下一个选项卡而不是相应的小部件:
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 的回答,我改进了功能:
- 滚动条位置的帐户(如果存在)
- 使用双向字典 (implemented here) 而不是线性查找
- 使用
update_map()
方法动态添加所有相关小部件,而不必手动添加每个小部件。
发布以防有人觉得这有用。
from PyQt5.QtCore import Qt,pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QWidget,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)
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)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。