Получите визуальную обратную связь от QValidator - PullRequest
1 голос
/ 08 октября 2019

Я пытаюсь использовать 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())

Это работает так, как рекламируется, но пользователь не получает обратной связи, так как попытка ввести "неправильные" символы просто отбрасывает их.

Я не нашел способа дать отзыв, кроме перехвата QLineEdit.textChangedсигнализировать и выполнять проверку «вручную» (то есть: без установки валидатора, в противном случае ошибка text не изменится и сигнал не будет издан). Предпочтительной обратной связью будет изменение цвета границы редактирования строки.

Это как-то противоречит цели самого валидатора. Кажется, я что-то упустил, так как не вижу, как вызвать обратную связь от QValidator.

Что такое "стандартный способ" справиться с этим?

Ответы [ 3 ]

2 голосов
/ 08 октября 2019

Пользовательский сигнал может использоваться для указания изменений состояния проверки путем повторной реализации метода 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_())
1 голос
/ 08 октября 2019

Если вы хотите проверить, является ли текст 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_())
0 голосов
/ 08 октября 2019

Я принял ответ @ekhumoro как по существу правильный, но я также опубликую свой текущий тестовый код, который (ИМХО!) Легче поддерживать в долгосрочной перспективе.

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

У меня есть два (придирки)проблемы с этим кодом;но это вопрос другого вопроса;)

...