QLineEdit是使用频率最高的控件之一,当咱们想获取用户输入时天然而然得会用到它。python
一般咱们会将QLineEdit的信号或其余控件的信号绑定至槽函数,而后获取并处理编辑器内的数据。你会以为咱们拿到的是第一手的“热乎着”的数据,因此理所固然地将过滤和验证逻辑都加入槽函数中,然而事实并不是如此。那么数据究竟经过了哪些流程最终才经由信号被咱们获取呢?app
或者你但愿QLineEdit能拥有自动补全或是输入联想的功能,这又如何实现呢?编辑器
若是你对上面的问题毫无头绪,那么本文就是为你量身打造的,请继续往下阅读吧!函数
本文索引
这一节将带你概览QLineEdit对数据的处理,并以一个示例引出后续章节的内容。你能够先在此处找到一些粗浅的回答,后续则会有详细的解释。测试
若是要简单的回答第一问,那么在咱们获取到text内容前须要通过两个步骤:ui
它们分别由inputMask
和QValidator
实现,前者负责过滤用户的输入,后者则用于过滤后的信息的验证。3d
inputMask
和validator
的表现很类似,有时它们的功能还会有一些重合,那么它们是否能取代彼此呢?答案是否认的,看起来像鸭子的鸟有时候其实不是和鸭子不要紧,后面咱们仔细说明。code
如今轮到回答第二个问题了。要实现补全和自动联想,你只须要将一个设置好的QCompleter
对象传递给QLineEdit。是否是够简单?大部分时间也确实如此,然而“设置好的”这一形容词的很抽象的概念,因此有的时候你可能要失望了,不过别担忧,后面咱们也会详细介绍它的使用。orm
两问回答完毕,如今该来看看本文的示例了。此次咱们将本身实现一个DateEdit
(我知道有现成的QDateEdit,不过这里请容许我为了实践所学而造一个粗糙的轮子),并根据用户输入的日期计算当天是周几,效果以下:对象
在本节中咱们将逐步实现CustomDateEdit,并详细介绍引论中提到的概念。
按照流程图的顺序,咱们首先要讲解的即是输入数据的过滤——inputMask
的功能。
在具体介绍一个能控制显示效果的特性前,我习惯于先描述其大体功能和具体的显示效果。
inputMask的功能:它是一串特定的规则,全部不符合规则的用户输入都会被丢弃,用户不论是从信号仍是text
槽都只能获取符合mask要求的输入数据,固然这个“用户”包括咱们后面要介绍的QValidator
及其派生类。
inputMask的显示效果:你只能输入合法的字符,输入非法字符是输入内容没法显示,光标停留在原处;若是你设置了mask的填充字符,则这些字符会显示在edit中,当输入合法字符时将覆盖它们,mask中的保留字符一样显示在edit中,但输入时会被跳过不可覆盖(相似占位符),引论中的效果图就是很好的例子。
inputMask就是一串由特殊字符组成的规则,经过规则给定的格式来控制文本的输入,具体的规则见下表:
特殊字符 | 对应规则 |
---|---|
A | 必须输入的ascii字母,包括A-Z,a-z |
a | 和A 同样,可是可选,也就是不输入这个字符也能够,占位符将保留 |
N | 必须输入的ascii字母和数字,包括A-Z,a-z,0-9 |
n | 和N 同样,可是可选 |
X | 必须输入的任意字符 |
x | 和X 同样,可是可选 |
9 | 必填的ascii的数字字符,包括0-9 |
0 | 和9 同样,可是可选 |
D | 必填的数字,包括1-9 |
d | 和D 同样,但可选 |
# | 可选的数字或者加减号 |
H | 必填的16进制的数字,包括A-F, a-f, 0-9 |
h | 和H 同样,但可选 |
B | 必填的二进制数字,包括0和1 |
b | 和B 同样,但可选 |
> | 全部在这个特殊字符以后的字符转换为大写 |
< | 全部在这个特殊字符以后的字符转换为小写 |
! | 关闭前面的大小写转换 |
[ ] { } | 保留的特殊字符 |
\ | 将特殊字符转义为普通字符 |
inputMask的格式为:([特殊字符]|[普通字符])*;占位符
,分号后跟的是占位符,用于填充特殊字符留下的空位,默认为空格。下面看些例子:
000,000.00;_
:用于输入一个最大6位,有两位小数的值,用_
填充空位,edit会显示出相似___,___.__
的效果>AAAA-AAAA!-AAAA-AAAA
:用于输入一个由连字符分割的字母数字组成的uuid或license key,且前八个字母会被转换为大写,在edit中显示为- - -
9999年09月09日
:用于输入年月日的时间格式,能够输入2019年03月14日
或2019年3月14日
,显示效果在引论的效果图中。你能够经过setInputMask
设置mask,或inputMask
获取当前的mask。
经过上面的说明和例子你应该已经学会了inputMask的使用,如今能够看看它与validator的区别了:
最主要的区别是这两点。上一节提到inputMask不能替代validator,如今咱们揭晓缘由:inputMask只能保证输入数据的格式,但并不保证数据有意义,好比例子3中咱们能够在月份上输入20,但明显日期中没有20月,而这种错误是inputMask没法处理的,这就是为何咱们说有时候一只看起来像鸭子的鸟也许和鸭子没有半点关系的缘由。
所以想要得到正确的数据,咱们还须要验证器来帮忙。
如今该验证咱们的输入了。由于有了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
能够看到验证器的逻辑其实很简单。整个验证器加上帮助函数一共作了三件事:
其余的细节都已经在注释中说明。
如此一来咱们既验证了数据的合法性又处理了全部可能的输入状况。固然,一般我更建议你使用现有的QDoubleValidator
和QRegExpValidator
等现有的验证器,或将它们组合使用,这样更简单也更不容易出错。
咱们已经讲解了输入的过滤和验证,最后该讲讲补全了。
能够说过滤和验证是比较经常使用的功能的话,那补全就没有那么常见了。或者说,一般咱们不须要关心它,好比QComboBox
自带了QCompleter,它工做得也很好,因此咱们每每忽略了它的存在。固然不仅是下拉框,在QLineEdit
中咱们也能够用它和它的派生类实现补全效果。
功能:QCompleter包含了一个叫completeModel的数据模型,里面包含了用于根据输入信息进行补全的全部数据,一般是个listModel,也能够是设置了补全所用数据位于哪一列的tableModel,固然你还能够用treeModel,不过这超过了咱们的讨论范围。
显示效果:completer从你输入的第一个字符开始匹配,若是在completeModel中找到了以输入内容开头的信息则会在edit下把全部匹配项一次放入一个下拉框并显示,你也能够设置为将第一个匹配项的数据替换放入edit。
还有一点我想额外补充一下,补全时弹出的下拉框实际上是个view视图对象,所以你能够选择本身须要的视图以显示补全时想显示的自定义效果。
你能够经过completer
和setCompleter
获取和设置completer。
能够看到只要把咱们用于补全输入的数据放入合适的model中,再把model设置给completer,就能实现补全功能了。
下面看个设置completer的例子:
# model是一个QStandardItemModel,后面咱们也会使用这个model来设置completer completer = QCompleter() model.setParent(completer) completer.setModel(model) edit.setCompleter(completer)
另外completer获得的数据是通过验证的,因此咱们无需关心数据的格式和合法性。
如今咱们已经把QLineEdit
的数据处理流程介绍了一遍,有了这些预备知识下面该实现CustomDateEdit
了。
咱们先来看代码,细节问题基本在注释中给出了说明:
class CustomDateEdit(QLineEdit): def __init__(self, parent=None): super().__init__(parent=parent) self.setInputMask(self.tr('9999年09月09日')) # 设置日期格式的inputMask validator = CustomDateValidator() self.setValidator(validator) # 设置validator # 设置completer self._completer = QCompleter() self.setCompleter(self._completer) self.completerModel = QStandardItemModel(parent=self._completer) self._completer.setModel(self.completerModel) # 预先填充一些待补全内容 self.addDateRecord("2019年03月14日") self.addDateRecord("2019年03月15日") def addDateRecord(self, text: str): """当有合法的输入被确认时就将其添加至completerModel,以便再次输入时补全 """ if self.completerModel.findItems(text): # 避免重复添加 return item = QStandardItem(text) self.completerModel.appendRow(item) def weekDayInfo(self, weekDay: int): """返回weekDay对应的名称,后面测试中会被使用 """ week = { 0: self.tr('周一'), 1: self.tr('周二'), 2: self.tr('周三'), 3: self.tr('周四'), 4: self.tr('周五'), 5: self.tr('周六'), 6: self.tr('周日'), } return week[weekDay]
整个dateEdit的实现也很简单,全部复杂的逻辑都已经交给了inputMask,验证器和completer,而咱们惟一要作的是为completer添加新输入的合法的数据,这在类方法addDateRecord
中完成了。
实现CustomDateEdit
以后,咱们就要动手实现引论一节中的程序了。
前面已经说过,最终经过信号传递或者由槽函数获取到的值必定是经过了过滤和验证经过的值。因此想实现引论中的程序咱们只须要正确处理CustomDateEdit
的信号便可。
下面直接上测试代码:
class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent=parent) center = QWidget() self.dateEdit = CustomDateEdit() self.info = QLabel(self.tr('所选日期是')) self.dateEdit.textEdited.connect(lambda: self.info.setText(self.tr('所选日期是'))) # 输入结束后按回车触发该信号,同时只有输入数据经过过滤和验证后这个信号才会被发送 self.dateEdit.returnPressed.connect(self.calcWeekDay) layout = QVBoxLayout() layout.addWidget(self.dateEdit) layout.addWidget(self.info, alignment=Qt.AlignCenter) center.setLayout(layout) self.setCentralWidget(center) def calcWeekDay(self): # 计算所选日期是周几 t = arrow.get(self.dateEdit.text(), self.dateEdit.validator().dateFormat()) weekDayInfo = self.dateEdit.weekDayInfo(t.weekday()) self.info.setText(self.tr('所选日期是') + weekDayInfo) # 添加记录 self.dateEdit.addDateRecord(self.dateEdit.text()) if __name__ == '__main__': app = QApplication(sys.argv) win = MainWindow() win.show() app.exec_()
当用户输入一个完整的日期后,按下回车键,程序会自动计算结果并更新到下方的label上。很简单的程序,主要就是为了测试咱们的CustomDateEdit
:
程序的行为和预想的差很少,如今你已经初步掌握所学的知识了。
另外也许你会奇怪,为何要大量使用self.tr
这个函数,不用担忧,这只是为了之后介绍国际化时作的准备,如今忽略它也没问题。
若是你发现了任何错误疏漏,或者仍有疑问,欢迎提出,共同进步!