Неожиданное поведение QLineEdit с использованием QRegExpValidator

Я использую виджет QLineEdit для ввода адресов электронной почты и настроил QRegExpValidor для проверки ввода.

Валидатор работает до тех пор, пока предотвращает ввод символов, не разрешенных в QRegExp, но, как ни странно, он позволяет вводить промежуточный ввод либо нажатием клавиши ввода, либо запуском сигнала завершения редактирования.

Я проверил правильный возврат валидатора (промежуточный или приемлемый).

Проверка документации PyQt5 показывает, что промежуточное состояние валидатора не мешает смещению фокуса на другой виджет. Кроме того, он останавливает срабатывание сигналов editFinished и returnPressed, поэтому пользователь может ввести неправильный адрес, поскольку он частично проходит RegExp. Ссылка: https://doc.qt.io/qt-5/qlineedit.html#acceptableInput-prop

Я смог решить свои проблемы, удалив валидатор из виджета QLineEdit и поместив метод checkValidator, связанный с курсоромPositionChanged виджета QLineEdit, отбрасывая последний введенный символ, когда он неприемлем, и возвращая фокус обратно к QLineEdit, когда он проверяет промежуточное значение. Он работает нормально, но когда фокус был сброшен, другие сигналы на других виджетах срабатывали по одному. На мгновение я обрабатываю это, проверяя, имеет ли отправитель фокус в начале методов (см. lookForFile)

Несмотря на то, что я мог справиться с этой проблемой, я очень благодарен всем, кто объясняет мне, как правильно использовать RegExpValidator и почему сброс фокуса запускает другие сигналы ни с того ни с сего.

 def setUI(self):
    self.setWindowTitle("EMail Settings")
    self.setModal(True)

    rx = QRegExp(r"[a-z0-9_%]+@[a-z0-9%_]+\.[a-z0-9%_]{3,3}")
    lblAddress = QLabel("EMail Address")
    self.lineAddress = QLineEdit(self)
    self.mailValidator = QRegExpValidator(rx, self.lineAddress)
    #self.lineAddress.setValidator(self.mailValidator)
    self.lineAddress.cursorPositionChanged.connect(self.checkValidator)
    self.lineAddress.returnPressed.connect(self.checkValidator)

    lblPassword = QLabel("Password")
    self.linePwd = QLineEdit()
    self.linePwd.setEchoMode(QLineEdit.PasswordEchoOnEdit)

    lblOauth2 = QLabel("Oauth2 Token")
    self.lineOauth = QLineEdit()
    pushOauth = QPushButton("...")
    pushOauth.setObjectName("token")
    pushOauth.clicked.connect(self.lookForFile)
    pushOauth.setFixedWidth(30)



@pyqtSlot()
def checkValidator(self):
    self.lineAddress.blockSignals(True)
    v = self.mailValidator.validate(self.lineAddress.text(), len(self.lineAddress.text()))
    if v[0] == 0:
        self.lineAddress.setText(self.lineAddress.text()[:-1])
    elif v[0] == 1:
        self.lineAddress.setFocus()
    elif v[0] == 2:
        pass
    print("validates", v)
    self.lineAddress.blockSignals(False)

 @pyqtSlot()
def lookForFile(self):
    try:
        if not self.sender().hasFocus():
            return
        baseDir = "C"
        obj = self.sender()
        if obj.objectName() == "Draft":
            capt = "Email Draft"
            baseDir = os.getcwd() + "\\draft"
            fileType = "Polo Management Email (*.pad)"
            dialog = QFileDialog(self, directory=os.getcwd())
            dialog.setFileMode(QFileDialog.Directory)
            res = dialog.getExistingDirectory()

        elif obj.objectName() == "token":
            capt = "Gmail Outh2 token File"
            fileType = "Gmail token Files (*.json)"
            baseDir = self.lineOauth.text()
            res = QFileDialog.getOpenFileName(self, caption=capt, directory=baseDir, filter=fileType)[0]
        fileName = res
        if obj.objectName() == "Draft":
            self.lineDraft.setText(fileName)
        elif obj.objectName() == "tokenFile":
            self.lineOauth.setText(fileName)
    except Exception as err:
        print("settings: lookForFile", err.args)

Надеюсь ответить на запрос @eyllanesc и Qmusicmante с помощью этого минимального воспроизводимого примера. Я меняю регулярное выражение на простое, позволяющее использовать строку строчных букв az, за которой следует точка и еще три символа нижнего регистра.

Я имею в виду, что валидатор не позволяет пользователю вводить неправильный ввод. В примере разрешены xxxzb.ods, но также разрешены, например, xxxzb или xxxzb.o. Короче говоря, не позволяя пользователю вводить неправильный ввод.

Это мой минимальный воспроизводимый пример:

class CheckValidator(QDialog):
    def __init__(self, parent=None):
         super().__init__()
         self.parent = parent
         self.setUI()

    def setUI(self):
        self.setWindowTitle("EMail Settings")
        self.setModal(True)

        rx = QRegExp(r"[a-z]+\.[a-z]{3}")
        lblAddress = QLabel("Check Line")
        self.lineAddress = QLineEdit()
        self.mailValidator = QRegExpValidator(rx, self.lineAddress)
        self.lineAddress.setValidator(self.mailValidator)
        self.lineAddress.cursorPositionChanged[int, int].connect(lambda 
             oldPos, newPos: self.printValidator(newPos))

        lblCheck = QLabel("Check")
        lineCheck = QLineEdit()

        formLayout = QFormLayout()
        formLayout.addRow(lblAddress, self.lineAddress)
        formLayout.addRow(lblCheck, lineCheck)
        self.setLayout(formLayout)

    @pyqtSlot(int)
    def printValidator(self, pos):
        print(self.mailValidator.validate(self.lineAddress.text(), pos))

if __name__ == '__main__':
app = QApplication(sys.argv)
tst = CheckValidator()
tst.show()
app.exec()

person Erick    schedule 08.04.2021    source источник
comment
Если вы пытаетесь предоставить минимальный воспроизводимый пример, убедитесь, что он действительно является минимально и воспроизводимым. Ваш код содержит ненужные части, которые не имеют смысла для вашего вопроса, он также неполный и имеет проблемы с отступами. Несмотря на это, я не уверен, что понимаю, о чем вы спрашиваете, но учтите, что тот факт, что ввод недействителен, не препятствует автоматическому изменению фокуса (и не должен!)   -  person musicamante    schedule 08.04.2021
comment
Также обратите внимание, что почтовое регулярное выражение довольно неполное и подвержено ошибкам: в нем отсутствуют важные символы, такие как плюс и точка (но, на самом деле, все следующее также должно быть технически разрешено: !#$%&'*+-/=?^_`{|}~), и оно не разрешает многоуровневые домены (@ кое-что.co.uk). См. Как проверить адрес электронной почты с помощью регулярного выражения?.   -  person musicamante    schedule 08.04.2021
comment
пожалуйста, предоставьте минимальный воспроизводимый пример. Ваша цель состоит в том, чтобы пользователь не мог изменить фокус, если не размещен действительный адрес электронной почты?   -  person eyllanesc    schedule 08.04.2021
comment
Из того, что я понимаю для вас, X=xxxzb и Y=xxxzb.o недействительны, с другой стороны, Z=xxxzb.ods действительна, но согласно логике, чтобы написать Z, вы должны сначала написать X, а затем Y, чтобы пользователь не мог запись X или Y сделает невозможным для него запись Z. Z является допустимым значением, но X и Y являются промежуточными значениями, поскольку они позволяют нам написать Z   -  person eyllanesc    schedule 08.04.2021
comment
Ваш комментарий @Elyllanesc правильный. То, что я прыгал, - это способ сохранить поведение QRegExpValidator при обеспечении точного совпадения при редактированииFinished. Я сам нашел решение, вынув RegExpValidator из QLineEdit (потому что срабатывает только editFinished при возврате QValidator.Acceptable) и установив два метода: один для cursorPositionChanged, используя результаты проверки, чтобы исключить последнюю запись, когда она неверна; и другие в сигнале editFinished с использованием точного соответствия RecExp, чтобы определить, является ли это допустимой записью. Я опубликую подробности в качестве ответа.   -  person Erick    schedule 08.04.2021


Ответы (1)


Я нашел решение, и я размещаю его здесь для такого случая, это может помочь кому-то еще. Сначала я удаляю QRegExpValidator из виджета QLineEdit. Причина в том, что QLineEdit запускает editFinished (и он нам понадобится) только тогда, когда QRegExpValidator возвращает QValidator.Acceptable, пока присутствует валидатор.

Затем мы устанавливаем метод, запускаемый сигналом cursorPositionchanged виджета QlineEdit. В этом методе с помощью QRegExpValidator мы определяем, является ли последний введенный символ допустимым. Если нет, то удалим.

Наконец, я устанавливаю метод, запускаемый сигналом 'editingFinished', используя функцию RegEx ExactMatch, чтобы определить, действительна ли запись. Если это не так, мы даем пользователю возможность очистить запись или вернуться к виджету, чтобы продолжить ввод данных. Используемое регулярное выражение предназначено только для целей тестирования. Для получения дополнительной информации о проверке электронной почты см. комментарии @Musicamante.

Это задействованный код:

    def setUI(self):
        ................
        ................
        rx = QRegExp(r"[a-z0-9_%]+@[a-z0-9%_]+\.[a-z0-9%_]{3,3}")
    
        lblAddress = QLabel("EMail Address")
        self.lineAddress = QLineEdit(self)
        self.mailValidator = QRegExpValidator(rx, self.lineAddress)
        self.lineAddress.cursorPositionChanged[int, int].connect(lambda oldPos, 
              newPos: self.checkValidator(newPos))
        self.lineAddress.editingFinished.connect(lambda : self.checkRegExp(rx))

    @pyqtSlot(int)
    def checkValidator(self, pos):
        v = self.mailValidator.validate(self.lineAddress.text(), pos ))
        if v[0] == 0:
            self.lineAddress.setText(self.lineAddress.text()[:-1])

    @pyqtSlot(QRegExp)
    def checkRegExp(self, rx):
        if not rx.exactMatch(self.lineAddress.text()) and self.lineAddress.text():
            if QMessageBox.question(self, "Leave the Address field",
               "The string entered is not a valid email address! \n" 
               "Do you want to clear the field?", QMessageBox.Yes|QMessageBox.No) == 
                QMessageBox.Yes:
                self.lineAddress.clear()
            else:
                self.lineAddress.setFocus()
person Erick    schedule 08.04.2021