最主要的区别是这两点。上一节提到inputMask不能替代validator,现在我们揭晓原因:inputMask只能保证输入数据的格式,但并不保证数据有意义,比如例子3中我们可以在月份上输入20,但明显日期中没有20月,而这种错误是inputMask无法处理的,这就是为什么我们说有时候一只看起来像鸭子的鸟也许和鸭子没有半点关系的原因。
因此想要获得正确的数据,我们还需要验证器来帮忙。
数据验证——QValidator现在该验证我们的输入了。因为有了inputMask的帮助,现在我们只需要验证数据本身是否正确而不用操心它的格式了,真是谢天谢地。
等等,这么说好像不太对,validator拿到的数据里居然还保留着mask的占位符?你没看错,这不是bug,能在edit里显示出来的数据那么一定能被获得,mask本身的占位符是能通过过滤的,所以它会原封不动地传给validator,只有用户输入合法的数据后这些占位符才会被覆盖。所以在写自己的验证器的时候要小心了——我们需要先删除所有的占位符,因为它们不是数据的一部分!
下面我们来看看validator的功能和显示效果。
功能:验证数据是否合法,不合法会被丢弃,同时还要识别出数据是否输入完成,这就是validator返回的第三种状态。
显示效果:和inputMask一样。如果数据未输入完则保留在edit中。
大致概览后我们可以深入了解一下QValidator了,所有的验证器都是它的派生类。
QValidator本身是一个纯虚基类,派生类需要实现QValidator::State QValidator::validate(QString &input, int &pos) const进行数据的验证,还有一个可选的fixup函数用于修复输入,不过一般来说很少有自行修复输入的需求,所以这里使用默认的实现,也就是什么都不做。
validate验证数据后返回数据是否合法,有QValidator::State类型的值表示:
QValidator::Invalid 数据不合法
QValidator::Intermediate 数据不完整需要进一步的输入
QValidator::Acceptable 数据合法
PyQt5中的接口稍微有些不同,处理第一个返回值的为QValidator::State之外还需要把input和pos原封不动地作为第二和第三个值返回,否则edit无法正确显示输入的数据。
你可以通过validator和setValidator来获取和设置验证器。
因为额外引入了第三种状态,所以实现一个validator远比设置inputMask来的复杂,这里我们实现一个自定义的日期验证器用于配合CustomDateEdit(我知道这个工作交给QRegExpValidator会很简单),同时介绍如何实现一个验证器。
下面看看具体的代码,首先我们不需要为validator额外增加内容,只需要实现几个方法,因此不要要关注构造等行为:
class CustomDateValidator(QValidator): """验证输入的是否是合法的年月日 """ def validate(self, input: str, pos: int): date = input.replace(' ', '') # 去除占位符 y, m, d = self.splitDate(date) if not (y and m and d): return QValidator.Intermediate, input, pos try: arrow.get(date, self.dateFormat()) # 如果解析失败代表日期输入不合法 except Exception: return QValidator.Invalid, input, pos return QValidator.Acceptable, input, pos def dateFormat(self): """返回arrow库使用的日期解析格式,具体参见文档,这里与CustomDateEdit的inputMask保持一致 """ return self.tr('YYYY年M月D日') def splitDate(self, date: str): """分割日期成年,月,日,以便判断数据是否输入完整, 只要有某一部分为空就表明数据未输入结束 """ y, date = date.split(self.tr('年')) m, date = date.split(self.tr('月')) d = date.split(self.tr('日'))[0] return y, m, d可以看到验证器的逻辑其实很简单。整个验证器加上帮助函数一共做了三件事:
首先去除占位符,如前文所述
接着将输入信息按年月日分割,如果有某一部分为空则代表输入不完整
对于完整的输入则使用arrow解析成时间对象,失败则表示输入数据错误
其他的细节都已经在注释中说明。