如何在 PyQt5 中实现虚拟 Tab 键 – nextInFocusChain() returns QFocusFrame
How to implement a Virtual Tab Key in PyQt5 – nextInFocusChain() returns QFocusFrame
我正在尝试在 PyQt5 中实现一个按钮,它的作用与按 Tab 键相同。为此,我获得了焦点项目,并调用 nextInFocusChain() 以按 Tab 键顺序获取下一个项目并将其设置为焦点。如果它不是 QLineEdit 对象,我重复。
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
遗憾的是,这个片段没有环绕。在最后一个 QLineEdit 之后,它在连续打印时卡在循环中
...
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
这是一个重现的例子...
from collections import OrderedDict
from PyQt5.QtWidgets import QDialog, QFormLayout, QLineEdit, QCheckBox, QDialogButtonBox, QVBoxLayout, QPushButton
class AddressEditor(QDialog):
def __init__(self,fields:OrderedDict,parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout(self)
self.inputs = OrderedDict()
last = None
for k,v in fields.items():
self.inputs[k] = QLineEdit(v)
if last is not None:
self.setTabOrder(self.inputs[last],self.inputs[k])
last = k
form.addRow(k, self.inputs[k])
layout.addLayout(form)
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
def tabpressed(self):
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
dialog = AddressEditor({"Text":"Default","Empty":"","True":"True","False":"False"})
if dialog.exec():
pass
exit(0)
尝试实现“手动”焦点导航时需要考虑两个重要方面:
- 焦点链考虑 所有 小部件,而不仅仅是那些可以“明显”接受焦点的小部件;这包括具有
NoFocus
策略的小部件、父小部件(包括顶级 window)、隐藏的小部件以及可以通过样式“注入”的任何其他小部件,包括“助手”,例如QFocusFrame;
- 小部件可以有一个
focus proxy
可以将焦点“推回”到前一个小部件,这可能会导致递归问题,就像您的情况一样;
除此之外,您的实施还有其他问题:
- 该按钮接受焦点,因此无论何时按下它都会重置焦点链;
- 要比较 class 你应该使用
isinstance
而不是 type
;
form
不应该有任何参数,因为它将作为嵌套布局添加,并且在任何情况下都不应该是 self
,因为已经设置了布局;
- Tab 键顺序必须在将两个小部件添加到具有相同祖先widget/window;
的父级后设置
- 通常会根据 when/where 将小部件添加到父级来自动设置选项卡顺序:只要按顺序插入所有字段,表单布局通常是不必要的;
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout() # <- no argument
layout.addLayout(form)
self.inputs = OrderedDict()
last = None
for k, v in fields.items():
new = self.inputs[k] = QLineEdit(v)
form.addRow(k, new) # <- add the widget *before* setTabOrder
if last is not None:
self.setTabOrder(last, new)
last = new
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
button.setFocusPolicy(Qt.NoFocus) # <- disable focus for the button
def tabpressed(self):
nextWidget = self.focusWidget().nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
如果要保持按钮的焦点策略以便可以通过 Tab 到达,唯一的可能是跟踪应用程序的焦点变化,因为一旦用鼠标按钮按下按钮,它就已经获得焦点:
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
# ...
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
QApplication.instance().focusChanged.connect(self.checkField)
self.lastField = tuple(self.inputs.values())[0]
def checkField(self, old, new):
if isinstance(new, QLineEdit) and self.isAncestorOf(new):
self.lastField = new
def tabpressed(self):
nextWidget = self.lastField.nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
我正在尝试在 PyQt5 中实现一个按钮,它的作用与按 Tab 键相同。为此,我获得了焦点项目,并调用 nextInFocusChain() 以按 Tab 键顺序获取下一个项目并将其设置为焦点。如果它不是 QLineEdit 对象,我重复。
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
遗憾的是,这个片段没有环绕。在最后一个 QLineEdit 之后,它在连续打印时卡在循环中
...
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
这是一个重现的例子...
from collections import OrderedDict
from PyQt5.QtWidgets import QDialog, QFormLayout, QLineEdit, QCheckBox, QDialogButtonBox, QVBoxLayout, QPushButton
class AddressEditor(QDialog):
def __init__(self,fields:OrderedDict,parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout(self)
self.inputs = OrderedDict()
last = None
for k,v in fields.items():
self.inputs[k] = QLineEdit(v)
if last is not None:
self.setTabOrder(self.inputs[last],self.inputs[k])
last = k
form.addRow(k, self.inputs[k])
layout.addLayout(form)
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
def tabpressed(self):
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
dialog = AddressEditor({"Text":"Default","Empty":"","True":"True","False":"False"})
if dialog.exec():
pass
exit(0)
尝试实现“手动”焦点导航时需要考虑两个重要方面:
- 焦点链考虑 所有 小部件,而不仅仅是那些可以“明显”接受焦点的小部件;这包括具有
NoFocus
策略的小部件、父小部件(包括顶级 window)、隐藏的小部件以及可以通过样式“注入”的任何其他小部件,包括“助手”,例如QFocusFrame; - 小部件可以有一个
focus proxy
可以将焦点“推回”到前一个小部件,这可能会导致递归问题,就像您的情况一样;
除此之外,您的实施还有其他问题:
- 该按钮接受焦点,因此无论何时按下它都会重置焦点链;
- 要比较 class 你应该使用
isinstance
而不是type
; form
不应该有任何参数,因为它将作为嵌套布局添加,并且在任何情况下都不应该是self
,因为已经设置了布局;- Tab 键顺序必须在将两个小部件添加到具有相同祖先widget/window; 的父级后设置
- 通常会根据 when/where 将小部件添加到父级来自动设置选项卡顺序:只要按顺序插入所有字段,表单布局通常是不必要的;
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout() # <- no argument
layout.addLayout(form)
self.inputs = OrderedDict()
last = None
for k, v in fields.items():
new = self.inputs[k] = QLineEdit(v)
form.addRow(k, new) # <- add the widget *before* setTabOrder
if last is not None:
self.setTabOrder(last, new)
last = new
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
button.setFocusPolicy(Qt.NoFocus) # <- disable focus for the button
def tabpressed(self):
nextWidget = self.focusWidget().nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
如果要保持按钮的焦点策略以便可以通过 Tab 到达,唯一的可能是跟踪应用程序的焦点变化,因为一旦用鼠标按钮按下按钮,它就已经获得焦点:
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
# ...
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
QApplication.instance().focusChanged.connect(self.checkField)
self.lastField = tuple(self.inputs.values())[0]
def checkField(self, old, new):
if isinstance(new, QLineEdit) and self.isAncestorOf(new):
self.lastField = new
def tabpressed(self):
nextWidget = self.lastField.nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()