用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)

时间:2022-01-23 04:25:39

【引子】

在PyQt5自带教程中,地址簿(address book)程序没有完全实现界面与业务逻辑分离。

本文我打算用eric6+PyQt5对其进行改写,以实现界面与逻辑完全分离。

 

【概览】


1、界面:
用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)


2、功能简介:
程序有三种操作模式:浏览模式、添加模式、编辑模式。 其实现的功能都显式的体现在各个按钮上


3、主要步骤:
1)、在eric6中新建项目,新建窗体,取名为 addressbook.ui 文件

2)、(自动打开)进入PyQt5 Desinger,编辑图形界面,保存

3)、回到eric 6,对上一步得到的界面文件 addressbook.ui 文件右击,编译窗体,得到 Ui_addressbook.py 文件

4)、然后再对 addressbook.ui 文件右击,生成对话框代码,得到 addressbook.py 文件。(在addressbook.py中添加自己的程序逻辑)

5)、py2exe打包成exe文件(此步略)


4、涉及的知识点:
import sys, pickle

from PyQt5.QtCore import pyqtSlot, QFile, QIODevice, Qt, QTextStream
from PyQt5.QtWidgets import QWidget, QDialog, QLabel, QLineEdit, QPushButton, QHBoxLayout,  QMessageBox, QFileDialog,  QApplication



【正文】
1、一般的步骤省略不表,接上面主要步骤第二步:

在Qt设计师中,将行编辑框(lineEdit)、文本编辑框(textEdit)、及十一个按钮(pushButton)的对象名(objectName)分别设置如下:

lineEdit_name(姓名输入框)

textEdit_address(地址输入框)

pushButton_add(添加 按钮)

pushButton_edit(编辑 按钮)

pushButton_remove(删除 按钮)

pushButton_find(查找 按钮)

pushButton_submit(提交 按钮)

pushButton_cancel(取消 按钮)

pushButton_load(导入 按钮)

pushButton_save(保存 按钮)

pushButton_export(导出 按钮)

pushButton_previous(前一个 按钮)

pushButton_next(后一个 按钮)

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)

 

2、关闭Qt设计师,回到eric6

先右击addressbook.ui 文件,编译窗体,得到 Ui_addressbook.py 文件

然后再次右击addressbook.ui 文件,生成对话框代码,

在弹窗中勾选十一个按钮的 on_x_clicked() 事件,确定,得到 addressbook.py 文件。

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)

 

3、对addressbook.py 文件执行下面四步处理

1)、清空所有注释

2)、去掉一个多余的点,将

from .Ui_addressbook import Ui_Form

变成:

from Ui_addressbook import Ui_Form

3)、将所有clicked()下的代码改写为pass

    @pyqtSlot()
def on_pushButton_add_clicked(self):
pass

@pyqtSlot()
def on_pushButton_edit_clicked(self):
pass

# ...

4)、在 addressbook.py 文件最后面加上下面几句代码:

if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication

app
= QApplication(sys.argv)
dlg
= Dialog()
dlg.show()
sys.exit(app.exec_())

最后,addressbook.py 看起来是这个样子:

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)
 1 # -*- coding: utf-8 -*-
2
3 from PyQt5.QtCore import pyqtSlot
4 from PyQt5.QtWidgets import QDialog
5
6 from Ui_addressbook import Ui_Dialog
7
8
9 class Dialog(QDialog, Ui_Dialog):
10 def __init__(self, parent=None):
11 super(Dialog, self).__init__(parent)
12 self.setupUi(self)
13
14 @pyqtSlot()
15 def on_pushButton_add_clicked(self):
16 pass
17
18 @pyqtSlot()
19 def on_pushButton_edit_clicked(self):
20 pass
21
22 @pyqtSlot()
23 def on_pushButton_remove_clicked(self):
24 pass
25
26 @pyqtSlot()
27 def on_pushButton_find_clicked(self):
28 pass
29
30 @pyqtSlot()
31 def on_pushButton_submit_clicked(self):
32 pass
33
34 @pyqtSlot()
35 def on_pushButton_cancel_clicked(self):
36 pass
37
38 @pyqtSlot()
39 def on_pushButton_load_clicked(self):
40 pass
41
42 @pyqtSlot()
43 def on_pushButton_save_clicked(self):
44 pass
45
46 @pyqtSlot()
47 def on_pushButton_export_clicked(self):
48 pass
49
50 @pyqtSlot()
51 def on_pushButton_previous_clicked(self):
52 pass
53
54 @pyqtSlot()
55 def on_pushButton_next_clicked(self):
56 pass
57
58 if __name__ == '__main__':
59 import sys
60 from PyQt5.QtWidgets import QApplication
61
62 app = QApplication(sys.argv)
63 dlg = Dialog()
64 dlg.show()
65 sys.exit(app.exec_())
View Code

 

4、下面添加逻辑代码

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)
# -*- coding: utf-8 -*-

import pickle

from PyQt5.QtCore import pyqtSlot, QFile, QIODevice, Qt, QTextStream
from PyQt5.QtWidgets import QWidget, QDialog, QLabel, QLineEdit, QPushButton, QHBoxLayout, QMessageBox, QFileDialog


from Ui_addressbook import Ui_Dialog


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 FindDialog(QDialog):
def __init__(self, parent=None):
super(FindDialog, self).
__init__(parent)

findLabel
= QLabel('输入要查找的联系人姓名:')
self.lineEdit
= QLineEdit()

self.findButton
= QPushButton('查找')
self.findText
= ''

layout
= QHBoxLayout()
layout.addWidget(findLabel)
layout.addWidget(self.lineEdit)
layout.addWidget(self.findButton)

self.setLayout(layout)
self.setWindowTitle(
'查找联系人')

self.findButton.clicked.connect(self.findClicked)
self.findButton.clicked.connect(self.accept)

def findClicked(self):
text
= self.lineEdit.text()

if not text:
QMessageBox.information(self,
'姓名不能为空', '请输入一个姓名')
return

self.findText
= text
self.lineEdit.clear()
self.hide()

def getFindText(self):
return self.findText


class Dialog(QDialog, Ui_Dialog):
NavigationMode, AddingMode, EditingMode
= range(3)

def __init__(self, parent=None):
super(Dialog, self).
__init__(parent)
self.setupUi(self)

self.pushButton_submit.hide()
self.pushButton_cancel.hide()

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

self.dialog
= FindDialog()

@pyqtSlot()
def on_pushButton_add_clicked(self):
self.oldName
= self.lineEdit_name.text()
self.oldAddress
= self.textEdit_address.toPlainText()

self.lineEdit_name.clear()
self.textEdit_address.clear()

self.updateInterface(self.AddingMode)

@pyqtSlot()
def on_pushButton_edit_clicked(self):
self.oldName
= self.lineEdit_name.text()
self.oldAddress
= self.textEdit_address.toPlainText()

self.updateInterface(self.EditingMode)

@pyqtSlot()
def on_pushButton_remove_clicked(self):
name
= self.lineEdit_name.text()
address
= self.textEdit_address.toPlainText()

if name in self.contacts:
button
= QMessageBox.question(self, '确定删除','你真的确定要删除 {} 吗?'.format(name), QMessageBox.Yes | QMessageBox.No)

if button == QMessageBox.Yes:
self.on_pushButton_previous_clicked()
del self.contacts[name]

QMessageBox.information(self,
'删除成功','{}已经从你的地址簿删除了!'.format(name))

self.updateInterface(self.NavigationMode)

@pyqtSlot()
def on_pushButton_find_clicked(self):
self.dialog.show()

if self.dialog.exec_() == QDialog.Accepted:
contactName
= self.dialog.getFindText()

if contactName in self.contacts:
self.lineEdit_name.setText(contactName)
self.textEdit_address.setText(self.contacts[contactName])
else:
QMessageBox.information(self,
'找不到','抱歉,{}不在你的地址簿内!'.format(contactName))
return

self.updateInterface(self.NavigationMode)

@pyqtSlot()
def on_pushButton_submit_clicked(self):
name
= self.lineEdit_name.text()
address
= self.textEdit_address.toPlainText()

if name == "" or address == "":
QMessageBox.information(self,
'不能为空', '请输入姓名及地址!')
return

if self.currentMode == self.AddingMode:
if name not in self.contacts:
self.contacts[name]
= address
QMessageBox.information(self,
'添加成功', '{} 已经添加到你的地址簿!'.format(name))
else:
QMessageBox.information(self,
'添加失败', '{} 已经存在于你的地址簿!'.format(name))
return

elif self.currentMode == self.EditingMode:
if self.oldName != name:
if name not in self.contacts:
QMessageBox.information(self,
'编辑成功','{} 已经被编辑到你的地址簿!'.format(self.oldName))

del self.contacts[self.oldName]
self.contacts[name]
= address
else:
QMessageBox.information(self,
'编辑失败','抱歉,{} 已经存在于你的地址簿!'.format(name))
return
elif self.oldAddress != address:
QMessageBox.information(self,
'编辑成功','{} 已经被编辑到你的地址簿!'.format(name))
self.contacts[name]
= address

self.updateInterface(self.NavigationMode)

@pyqtSlot()
def on_pushButton_cancel_clicked(self):
self.lineEdit_name.setText(self.oldName)
self.textEdit_address.setText(self.oldAddress)

self.updateInterface(self.NavigationMode)

@pyqtSlot()
def on_pushButton_load_clicked(self):
fileName, _
= QFileDialog.getOpenFileName(self, '打开地址簿', '', '地址簿文件 (*.abk);;所有文件 (*)')

if not fileName:
return

try:
in_file
= open(str(fileName), 'rb')
except IOError:
QMessageBox.information(self,
'不能打开文件','打开文件 {} 时发生错误!'.format(fileName))
return

self.contacts
= pickle.load(in_file)
in_file.close()

if len(self.contacts) == 0:
QMessageBox.information(self,
'文件中无联系人','你打开的文件中无联系人!')
else:
for name, address in self.contacts:
self.lineEdit_name.setText(name)
self.textEdit_address.setText(address)

self.updateInterface(self.NavigationMode)

@pyqtSlot()
def on_pushButton_save_clicked(self):
fileName, _
= QFileDialog.getSaveFileName(self, '保存地址簿', '', '地址簿文件 (*.abk);;所有文件 (*)')

if not fileName:
return

try:
out_file
= open(str(fileName), 'wb')
except IOError:
QMessageBox.information(self,
'不能打开文件','打开文件 {} 时发生错误!'.format(fileName))
return

pickle.dump(self.contacts, out_file)
out_file.close()

@pyqtSlot()
def on_pushButton_export_clicked(self):
name
= str(self.lineEdit_name.text())
address
= self.textEdit_address.toPlainText()

nameList
= name.split()

if len(nameList) > 1:
firstName
= nameList[0]
lastName
= nameList[-1]
else:
firstName
= name
lastName
= ''

fileName, _
= QFileDialog.getSaveFileName(self, '导出联系', '', 'vCard 文件 (*.vcf);;所有文件 (*)')

if not fileName:
return

out_file
= QFile(fileName)

if not out_file.open(QIODevice.WriteOnly):
QMessageBox.information(self,
'不能打开文件', out_file.errorString())
return

out_s
= QTextStream(out_file)

out_s
<< 'BEGIN:VCARD' << '\n'
out_s
<< 'VERSION:2.1' << '\n'
out_s
<< 'N:' << lastName << ';' << firstName << '\n'
out_s
<< 'FN:' << ' '.join(nameList) << '\n'

address.replace(
';', '\\;')
address.replace(
'\n', ';')
address.replace(
',', ' ')

out_s
<< 'ADR;HOME:;' << address << '\n'
out_s
<< 'END:VCARD' << '\n'

QMessageBox.information(self,
'导出成功','{} 已经被导出为 vCard !'.format(name))

@pyqtSlot()
def on_pushButton_previous_clicked(self):
name
= self.lineEdit_name.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.lineEdit_name.clear()
self.textEdit_address.clear()
return

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

self.lineEdit_name.setText(prev_name)
self.textEdit_address.setText(prev_address)

@pyqtSlot()
def on_pushButton_next_clicked(self):
name
= self.lineEdit_name.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.lineEdit_name.setText(next_name)
self.textEdit_address.setText(next_address)

def updateInterface(self, mode):
self.currentMode
= mode

if self.currentMode in (self.AddingMode, self.EditingMode):
self.lineEdit_name.setReadOnly(False)
self.lineEdit_name.setFocus(Qt.OtherFocusReason)
self.textEdit_address.setReadOnly(False)

self.pushButton_add.setEnabled(False)
self.pushButton_edit.setEnabled(False)
self.pushButton_remove.setEnabled(False)

self.pushButton_next.setEnabled(False)
self.pushButton_previous.setEnabled(False)

self.pushButton_submit.show()
self.pushButton_cancel.show()

self.pushButton_load.setEnabled(False)
self.pushButton_save.setEnabled(False)
self.pushButton_export.setEnabled(False)

elif self.currentMode == self.NavigationMode:
if not self.contacts:
self.lineEdit_name.clear()
self.textEdit_address.clear()

self.lineEdit_name.setReadOnly(True)
self.textEdit_address.setReadOnly(True)
self.pushButton_add.setEnabled(True)

number
= len(self.contacts)
self.pushButton_edit.setEnabled(number
>= 1)
self.pushButton_remove.setEnabled(number
>= 1)
self.pushButton_find.setEnabled(number
> 2)
self.pushButton_next.setEnabled(number
> 1)
self.pushButton_previous.setEnabled(number
>1 )

self.pushButton_submit.hide()
self.pushButton_cancel.hide()

self.pushButton_export.setEnabled(number
>= 1)

self.pushButton_load.setEnabled(True)
self.pushButton_save.setEnabled(number
>= 1)

if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication

app
= QApplication(sys.argv)
dlg
= Dialog()
dlg.show()
sys.exit(app.exec_())
View Code

 

5、程序运行界面

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)

 

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)

 

用 eric6 与 PyQt5 实现python的极速GUI编程(系列04)---- PyQt5自带教程:地址簿(address book)