Estoy tratando de usar descendientes QValidator (en realidad en PyQt5, pero eso no debería importar) para validar una serie de ediciones de línea.

Un pequeño extracto es:

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())

Esto funciona como se anuncia, pero el usuario no recibe comentarios, ya que tratar de ingresar caracteres "incorrectos" simplemente los descarta.

No encontré ninguna forma de dar retroalimentación además de conectarme a la señal QLineEdit.textChanged y hacer la validación "manualmente" (es decir: sin configurar un validador, de lo contrario en el error text ganado no cambie y no se emitirá ninguna señal). La respuesta preferida sería cambiar el color del borde de la edición de línea.

Esto de alguna manera anula el propósito del validador mismo. Parece que me falta algo, ya que no puedo ver cómo activar los comentarios de QValidator.

¿Cuál es la "forma estándar" de manejar esto?

1
ZioByte 8 oct. 2019 a las 02:57

3 respuestas

La mejor respuesta

Se puede usar una señal personalizada para indicar cambios en el estado de validación reimplementando el validar método en una subclase. A continuación se muestra un script que demuestra este enfoque. (Tenga en cuenta que la firma de validate es diferente en PyQt, porque no muta los argumentos como en 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_())
2
ekhumoro 8 oct. 2019 a las 11:16

Si desea verificar si el texto de QLineEdit es válido, debe utilizar el método 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_())
1
eyllanesc 8 oct. 2019 a las 02:48

Acepté la respuesta @ekhumoro como esencialmente correcta, pero también publicaré mi código de prueba actual que es (¡en mi humilde opinión!) Más fácil de mantener a largo plazo.

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_())

Tengo dos (nitpick) problemas con este código; pero eso es cuestión de otra pregunta;)

0
ZioByte 8 oct. 2019 a las 14:55
58278508