从 QValidator 获得视觉反馈
Get visual feedback from QValidator
我正在尝试使用 QValidator
后代(实际上是在 PyQt5 中,但这不重要)来验证一系列行编辑。
一小段摘录是:
class IPv4(QWidget):
def __init__(self):
super(IPv4, self).__init__()
uic.loadUi('ipv4.ui', self)
self.address.inputMask = ''
rx = QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
self.address.setValidator(QRegularExpressionValidator(rx, self.address))
self.netmask.setValidator(QRegularExpressionValidator(rx, self.netmask))
self.gateway.setValidator(QRegularExpressionValidator(rx, self.gateway))
self.broadcast.setValidator(QRegularExpressionValidator(rx, self.broadcast))
self.dns1.setValidator(QRegularExpressionValidator(rx, self.dns1))
self.dns2.setValidator(QRegularExpressionValidator(rx, self.dns2))
self.on_dhcp_clicked(self.dhcp.isChecked())
这与宣传的一样有效,但用户没有得到任何反馈,因为尝试输入 "wrong" 个字符只会丢弃它们。
除了连接到 QLineEdit.textChanged
信号并进行验证 "manually"(即 没有 设置验证器之外,我没有找到任何方法来提供反馈, 否则错误 text
不会改变并且不会发出信号)。首选的反馈是更改行编辑的边框颜色。
这在某种程度上违背了验证器本身的目的。看来我遗漏了什么,因为我看不到如何触发 QValidator
.
的反馈
什么是 "standard way" 来处理这个问题?
如果要验证 QLineEdit 文本是否有效,则必须使用 hasAcceptableInput()
方法:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
rx = QtCore.QRegularExpression(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
self.le = QtWidgets.QLineEdit()
self.le.setValidator(QtGui.QRegularExpressionValidator(rx, self.le))
self.le.textChanged.connect(self.on_textChanged)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.le)
@QtCore.pyqtSlot()
def on_textChanged(self):
le = self.sender()
if isinstance(le, QtWidgets.QLineEdit):
le.setStyleSheet(
"border: 5px solid {color}".format(
color="green" if le.hasAcceptableInput() else "red"
)
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
自定义信号可用于通过在子类中重新实现 validate 方法来指示验证状态更改。下面是演示此方法的脚本。 (请注意,validate
的签名在 PyQt 中是不同的,因为它不会像在 C++ 中那样改变参数)。
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class RegExpValidator(QtGui.QRegularExpressionValidator):
validationChanged = QtCore.pyqtSignal(QtGui.QValidator.State)
def validate(self, input, pos):
state, input, pos = super().validate(input, pos)
self.validationChanged.emit(state)
return state, input, pos
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
regexp = QtCore.QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
validator = RegExpValidator(regexp, self)
validator.validationChanged.connect(self.handleValidationChange)
self.edit = QtWidgets.QLineEdit()
self.edit.setValidator(validator)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
def handleValidationChange(self, state):
if state == QtGui.QValidator.Invalid:
colour = 'red'
elif state == QtGui.QValidator.Intermediate:
colour = 'gold'
elif state == QtGui.QValidator.Acceptable:
colour = 'lime'
self.edit.setStyleSheet('border: 3px solid %s' % colour)
QtCore.QTimer.singleShot(1000, lambda: self.edit.setStyleSheet(''))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
我接受@ekhumoro 的回答基本上是正确的,但我也会 post 我当前的测试代码(恕我直言!)在长期 运行.
中更容易维护
from __future__ import annotations
import typing
from PyQt5 import QtCore, QtGui, QtWidgets
class MyValidator(QtGui.QRegularExpressionValidator):
def validate(self, text: str, pos: int) -> typing.Tuple[QtGui.QValidator.State, str, int]:
state, text, pos = super(MyValidator, self).validate(text, pos)
selector = {
QtGui.QValidator.Invalid: 'invalid',
QtGui.QValidator.Intermediate: 'intermediate',
QtGui.QValidator.Acceptable: 'acceptable'
}[state]
if selector == 'invalid':
sel = self.parent().property('selector')
def restore():
self.parent().setProperty('selector', sel)
self.parent().setStyleSheet('/**/')
QtCore.QTimer.singleShot(1000, restore)
self.parent().setProperty('selector', selector)
self.parent().setStyleSheet('/**/')
return state, text, pos
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.le = QtWidgets.QLineEdit()
regexp = QtCore.QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
self.le.setValidator(MyValidator(regexp, self.le))
self.le.setProperty('selector', 'none')
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.le)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet('''\
*[selector="invalid"] {border-radius: 3px; border: 1px solid red;}
*[selector="intermediate"] {border-radius: 3px; border: 1px solid gold;}
*[selector="acceptable"] {border-radius: 3px; border: 1px solid green;}
''')
w = Widget()
w.show()
sys.exit(app.exec_())
这段代码有两个(吹毛求疵的)问题;但这对另一个问题很重要;)
我正在尝试使用 QValidator
后代(实际上是在 PyQt5 中,但这不重要)来验证一系列行编辑。
一小段摘录是:
class IPv4(QWidget):
def __init__(self):
super(IPv4, self).__init__()
uic.loadUi('ipv4.ui', self)
self.address.inputMask = ''
rx = QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
self.address.setValidator(QRegularExpressionValidator(rx, self.address))
self.netmask.setValidator(QRegularExpressionValidator(rx, self.netmask))
self.gateway.setValidator(QRegularExpressionValidator(rx, self.gateway))
self.broadcast.setValidator(QRegularExpressionValidator(rx, self.broadcast))
self.dns1.setValidator(QRegularExpressionValidator(rx, self.dns1))
self.dns2.setValidator(QRegularExpressionValidator(rx, self.dns2))
self.on_dhcp_clicked(self.dhcp.isChecked())
这与宣传的一样有效,但用户没有得到任何反馈,因为尝试输入 "wrong" 个字符只会丢弃它们。
除了连接到 QLineEdit.textChanged
信号并进行验证 "manually"(即 没有 设置验证器之外,我没有找到任何方法来提供反馈, 否则错误 text
不会改变并且不会发出信号)。首选的反馈是更改行编辑的边框颜色。
这在某种程度上违背了验证器本身的目的。看来我遗漏了什么,因为我看不到如何触发 QValidator
.
什么是 "standard way" 来处理这个问题?
如果要验证 QLineEdit 文本是否有效,则必须使用 hasAcceptableInput()
方法:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
rx = QtCore.QRegularExpression(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
self.le = QtWidgets.QLineEdit()
self.le.setValidator(QtGui.QRegularExpressionValidator(rx, self.le))
self.le.textChanged.connect(self.on_textChanged)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.le)
@QtCore.pyqtSlot()
def on_textChanged(self):
le = self.sender()
if isinstance(le, QtWidgets.QLineEdit):
le.setStyleSheet(
"border: 5px solid {color}".format(
color="green" if le.hasAcceptableInput() else "red"
)
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
自定义信号可用于通过在子类中重新实现 validate 方法来指示验证状态更改。下面是演示此方法的脚本。 (请注意,validate
的签名在 PyQt 中是不同的,因为它不会像在 C++ 中那样改变参数)。
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class RegExpValidator(QtGui.QRegularExpressionValidator):
validationChanged = QtCore.pyqtSignal(QtGui.QValidator.State)
def validate(self, input, pos):
state, input, pos = super().validate(input, pos)
self.validationChanged.emit(state)
return state, input, pos
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
regexp = QtCore.QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
validator = RegExpValidator(regexp, self)
validator.validationChanged.connect(self.handleValidationChange)
self.edit = QtWidgets.QLineEdit()
self.edit.setValidator(validator)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
def handleValidationChange(self, state):
if state == QtGui.QValidator.Invalid:
colour = 'red'
elif state == QtGui.QValidator.Intermediate:
colour = 'gold'
elif state == QtGui.QValidator.Acceptable:
colour = 'lime'
self.edit.setStyleSheet('border: 3px solid %s' % colour)
QtCore.QTimer.singleShot(1000, lambda: self.edit.setStyleSheet(''))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
我接受@ekhumoro 的回答基本上是正确的,但我也会 post 我当前的测试代码(恕我直言!)在长期 运行.
中更容易维护from __future__ import annotations
import typing
from PyQt5 import QtCore, QtGui, QtWidgets
class MyValidator(QtGui.QRegularExpressionValidator):
def validate(self, text: str, pos: int) -> typing.Tuple[QtGui.QValidator.State, str, int]:
state, text, pos = super(MyValidator, self).validate(text, pos)
selector = {
QtGui.QValidator.Invalid: 'invalid',
QtGui.QValidator.Intermediate: 'intermediate',
QtGui.QValidator.Acceptable: 'acceptable'
}[state]
if selector == 'invalid':
sel = self.parent().property('selector')
def restore():
self.parent().setProperty('selector', sel)
self.parent().setStyleSheet('/**/')
QtCore.QTimer.singleShot(1000, restore)
self.parent().setProperty('selector', selector)
self.parent().setStyleSheet('/**/')
return state, text, pos
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.le = QtWidgets.QLineEdit()
regexp = QtCore.QRegularExpression(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
self.le.setValidator(MyValidator(regexp, self.le))
self.le.setProperty('selector', 'none')
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.le)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet('''\
*[selector="invalid"] {border-radius: 3px; border: 1px solid red;}
*[selector="intermediate"] {border-radius: 3px; border: 1px solid gold;}
*[selector="acceptable"] {border-radius: 3px; border: 1px solid green;}
''')
w = Widget()
w.show()
sys.exit(app.exec_())
这段代码有两个(吹毛求疵的)问题;但这对另一个问题很重要;)