使用 QRegExpValidator 的 QLineEdit 意外行为

QLineEdit unexpected behavior using QRegExpValidator

我正在使用 QLineEdit 小部件输入电子邮件地址,我设置了 QRegExpValidor 来验证输入。

验证器正在努力防止输入 QRegExp 中不允许的字符,但有趣的是,它允许输入中间输入,方法是按下回车键或触发“editingfinished”信号。

我验证了验证器的 return 是正确的(中等或可接受)。

检查 PyQt5 文档发现验证器的中间状态不会阻止焦点更改到另一个小部件。此外,它停止触发 editingFinished 和 returnedPressed 信号,因此用户可能会输入错误的地址,因为它部分地运行了 RegExp。 参考:https://doc.qt.io/qt-5/qlineedit.html#acceptableInput-prop

我能够通过从 QLineEdit 小部件中删除验证器来解决我的需求,并放置一个链接到 QLineEdit 小部件的 cursorPositionChanged 的​​方法“checkValidator”,当不可接受时丢弃最后输入的字符,并将焦点设置回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 的请求。我将正则表达式更改为一个简单的正则表达式,允许一串小写 a-z 后跟一个点和三个小写字符。

我想要的是验证器不允许用户输入错误的输入。该示例允许使用“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()

我找到了一个解决方案,我 post 在这里为这种情况提供它可能对其他人有帮助。 首先,我从 QLineEdit 小部件中删除了 QRegExpValidator。原因是 QLineEdit 仅在验证器存在时 QRegExpValidator returns QValidator.Acceptable 触发 editingFinished (我们将需要它)。

然后我们设置一个由 QlineEdit 小部件的 'cursorPositionchanged' 信号触发的方法。在这个方法中,我们使用 QRegExpValidator 确定最后输入的字符是否有效。如果不是,我们将其删除。

最后,我设置了一个由 'editingFinished' 信号触发的方法,使用 RegEx exactMatch 函数来确定条目是否有效。如果不是,我们为用户提供清除条目或 return 到小部件以继续输入数据的选项。使用的正则表达式仅用于测试目的,有关电子邮件验证的更多信息,请查看@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()