pythonqtpyqtpyqt5qvalidator

Get visual feedback from QValidator


I am trying to use QValidator descendants (actually in PyQt5, but that shouldn't matter) to validate a series of line-edits.

A small excerpt is:

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

This works as advertised, but the user gets no feedback, since trying to input "wrong" characters simply discards them.

I didn't find any way to give feedback beside hooking into the QLineEdit.textChanged signal and doing validation "manually" (i.e.: without setting a validator, otherwise on error text won't change and no signal will be emitted). The preferred feedback would be to change the line-edit's border-color.

This somehow defeats the purpose of the validator itself. It seems I'm missing something, since I can't see how to trigger feedback from QValidator.

What is the "standard way" to handle this?


Solution

  • A custom signal can be used to indicate validation-state changes by reimplementing the validate method in a subclass. Below is a script that demonstrates this approach. (Note that the signature of validate is different in PyQt, because it does not mutate the arguments as in 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_())