PyQt5-Beginner-tutorial-part 2

原文链接

引言

这篇文章是前作的续篇(part 2), 为了达到最好的教学效果, 请务必先阅读前作.

在这篇续作当中, 我们依旧是通过实例代码来学习PyQt5, 我会涉及到下一篇教程(part 3)的一些内容.

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QGridLayout, QHBoxLayout, QLabel, QLineEdit,
QMessageBox, QPushButton, QTextEdit, QVBoxLayout, QWidget)

class SortedDict(dict):
class Iterator(object):
def __init__(self, sorted_dict):
self._dict = sorted_dict
self._keys = sorted(self._dict.keys())
self._nr_items = len(self._keys)
self._idx = 0

def __iter__(self):
return self

def next(self):
if self._idx >= self._nr_items:
raise StopIteration

key = self._keys[self._idx]
value = self._dict[key]
self._idx += 1

return key, value

__next__ = next

def __iter__(self):
return SortedDict.Iterator(self)

iterkeys = __iter__

class AddressBook(QWidget):
def __init__(self, parent=None):
super(AddressBook, self).__init__(parent)

self.contacts = SortedDict()
self.oldName = ''
self.oldAddress = ''

nameLabel = QLabel("Name:")
self.nameLine = QLineEdit()
self.nameLine.setReadOnly(True)

addressLabel = QLabel("Address:")
self.addressText = QTextEdit()
self.addressText.setReadOnly(True)

self.addButton = QPushButton("&Add")
self.addButton.show()
self.submitButton = QPushButton("&Submit")
self.submitButton.hide()
self.cancelButton = QPushButton("&Cancel")
self.cancelButton.hide()
self.nextButton = QPushButton("&Next")
self.nextButton.setEnabled(False)
self.previousButton = QPushButton("&Previous")
self.previousButton.setEnabled(False)

self.addButton.clicked.connect(self.addContact)
self.submitButton.clicked.connect(self.submitContact)
self.cancelButton.clicked.connect(self.cancel)
self.nextButton.clicked.connect(self.next)
self.previousButton.clicked.connect(self.previous)

buttonLayout1 = QVBoxLayout()
buttonLayout1.addWidget(self.addButton, Qt.AlignTop)
buttonLayout1.addWidget(self.submitButton)
buttonLayout1.addWidget(self.cancelButton)
buttonLayout1.addStretch()

buttonLayout2 = QHBoxLayout()
buttonLayout2.addWidget(self.previousButton)
buttonLayout2.addWidget(self.nextButton)

mainLayout = QGridLayout()
mainLayout.addWidget(nameLabel, 0, 0)
mainLayout.addWidget(self.nameLine, 0, 1)
mainLayout.addWidget(addressLabel, 1, 0, Qt.AlignTop)
mainLayout.addWidget(self.addressText, 1, 1)
mainLayout.addLayout(buttonLayout1, 1, 2)
mainLayout.addLayout(buttonLayout2, 3, 1)

self.setLayout(mainLayout)
self.setWindowTitle("Simple Address Book")

def addContact(self):
self.oldName = self.nameLine.text()
self.oldAddress = self.addressText.toPlainText()

self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(False)
self.nameLine.setFocus(Qt.OtherFocusReason)
self.addressText.setReadOnly(False)

self.addButton.setEnabled(False)
self.nextButton.setEnabled(False)
self.previousButton.setEnabled(False)
self.submitButton.show()
self.cancelButton.show()

def submitContact(self):
name = self.nameLine.text()
address = self.addressText.toPlainText()

if name == "" or address == "":
QMessageBox.information(self, "Empty Field",
"Please enter a name and address.")
return

if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
"\"%s\" has been added to your address book." % name)
else:
QMessageBox.information(self, "Add Unsuccessful",
"Sorry, \"%s\" is already in your address book." % name)
return

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def cancel(self):
self.nameLine.setText(self.oldName)
self.addressText.setText(self.oldAddress)

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def next(self):
name = self.nameLine.text()
it = iter(self.contacts)

try:
while True:
this_name, _ = it.next()

if this_name == name:
next_name, next_address = it.next()
break
except StopIteration:
next_name, next_address = iter(self.contacts).next()

self.nameLine.setText(next_name)
self.addressText.setText(next_address)

def previous(self):
name = self.nameLine.text()

prev_name = prev_address = None
for this_name, this_address in self.contacts:
if this_name == name:
break

prev_name = this_name
prev_address = this_address
else:
self.nameLine.clear()
self.addressText.clear()
return

if prev_name is None:
for prev_name, prev_address in self.contacts:
pass

self.nameLine.setText(prev_name)
self.addressText.setText(prev_address)

if __name__ == '__main__':
import sys

from PyQt5.QtWidgets import QApplication

app = QApplication(sys.argv)

addressBook = AddressBook()
addressBook.show()

sys.exit(app.exec_())

代码分析

我将会从第33行 Addressbook 这个类开始讲解, 因为大部分代码我在前作有讲解过, 我只会讲解前作没有涉及到的地方.

38-39行: 预先声明两个变量 oldNameoldAddress 以便后续代码调用.
42-43行: 我们声明一个文本编辑控件, 并且设置其只读属性为真, 当我们点击这个文本框的时候, 是无论如何都无法输入任何内容的.
49-58行: 我们为窗体添加一些按钮控件, 在第50行, show()方法会使 addButton 可见. 另外, submitButtoncancelButton 将会被我们隐式的创建. nextButtonpreviousButton 将会在窗体中显示出来, 但是他俩是灰色不可用的状态, 用户不能点击.
60-64行: 我们为控件添加点击触发的事件.
对于学习过前作的你来说, 看懂剩下的类简直是易如反掌了.
当我们第一次运行这个程序, 只有 Add 按钮是可以点击的, 这个按钮触发事件的代码在第87行.
88-89行: 还记得我们在38-39行定义的俩变量吗? 现在把 nameLineaddressText 的值赋给他俩.
91-96行: 我们把 nameLineaddressText 的文本框内容清空, 并且使用setReadOnly(false) 来让这俩变成可输入的状态, 最后把输入光标聚焦到nameLine 这里.
98-102行: 我们把 Add , PreviousNext 这三个按钮禁用掉, 然后吧 SubmitCancel 按钮设置为可见状态. 其中 cancel 按钮的出发事件代码从第137行开始.
138-139行: 还记得88-89行的那俩变量吗? 现在把 nameLineaddressText 的值赋回去.
141-143行: 但如果这些变量并没有存放任何数据, 我们便相应的设置文本框为空.
149-151行: self.contacts 是一个包含了我们的 address book 输入值的字典, 我们获取这个字典的大小并且赋给一个叫做 number 的变量, 如果输入的变量大于2个的话, 我们就把 nextButtonpreviousButton 设置为可用. 其中, Submit 按钮的触发事件代码在第104行开始.
105-106行: 把 nameaddress 这俩变量的值赋给文本框.
108-110行: 只要这些变量其中有为空值的, 我们就会报错.
113-120行: 这个字典的一个属性必须是唯一的键值对, 如果这个键值对不在我们的字典里面, 我们将会加到字典里并且显示一个 “success” 对话框. 同样的, 如果这个键值对存在于这个字典了, 我们就会显示错误. 其中, 字典的触发事件代码从第5行开始, 这个类会生成一个有序字典, 在代码的第36行被调用了.
7-11行: 初始化一些私有变量, _idx 是字典的索引, _nr_items 存放着字典的大小.
16-24行: 把列表进行排序, 当 _idx_nr_items 大的时候, 将会触发一个exception, 而正常情况我们会步进字典, 这个方法返回以index为索引, 自增长为1的键值对.
如果你想了解更多关于Python字典的内容, see this page
157-158行: 这个方法会在我们点击 next 按钮的时候触发, 输入框的值会赋给 name 变量, iter() 方法会给字典存在的变量赋值, 返回不在字典的键.
160-168行: 我们同样准备了 try-catch 代码, 在第162行的try语句里, 我们给键值对赋值.
170-171行: 我们把 next_name , next_address 的值赋给 nameLineaddressText. 其中 previous 按钮的触发事件代码从第173行开始.
第176行: 初始化 prev_name , prev_address 这两个变量, 并赋值为None.
177-182行: 我们迭代字典来储存 this_name , this_address 这两个变量的值, 如果 this_name 等于 nameLine 里面的值, 我们就退出迭代, 反之则把 prev_name , prev_address 赋给我们得到的变量.

189-190行: pass 这条语句本身毫无意义, 只是确保语法正确, 所以当 prev_name 为空时, 我们默认设置其值为空字符串.

总结

希望你能喜欢这篇文章 :-)