【引子】

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

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

【概览】

1、界面:

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(后一个 按钮)

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

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

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

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

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 看起来是这个样子:

 # -*- coding: utf-8 -*-

 from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QDialog from Ui_addressbook import Ui_Dialog class Dialog(QDialog, Ui_Dialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setupUi(self) @pyqtSlot()
def on_pushButton_add_clicked(self):
pass @pyqtSlot()
def on_pushButton_edit_clicked(self):
pass @pyqtSlot()
def on_pushButton_remove_clicked(self):
pass @pyqtSlot()
def on_pushButton_find_clicked(self):
pass @pyqtSlot()
def on_pushButton_submit_clicked(self):
pass @pyqtSlot()
def on_pushButton_cancel_clicked(self):
pass @pyqtSlot()
def on_pushButton_load_clicked(self):
pass @pyqtSlot()
def on_pushButton_save_clicked(self):
pass @pyqtSlot()
def on_pushButton_export_clicked(self):
pass @pyqtSlot()
def on_pushButton_previous_clicked(self):
pass @pyqtSlot()
def on_pushButton_next_clicked(self):
pass if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv)
dlg = Dialog()
dlg.show()
sys.exit(app.exec_())

4、下面添加逻辑代码

# -*- 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_())

5、程序运行界面

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

  1. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列01)--Hello world!

    [题记] 我是一个菜鸟,这个系列是我的学习笔记. PyQt5 出来有一段时间了, PyQt5 较之 PyQt4 有一些变化,而网上流传的几乎都是 PyQt4 的教程,照搬的话大多会出错. eric6 ...

  2. 用 eric6 与 PyQt5 实现python的极速GUI编程(35篇PyQT和200多篇Python)

    [题记] 我是一个菜鸟,这个系列是我的学习笔记. PyQt5 出来有一段时间了, PyQt5 较之 PyQt4 有一些变化,而网上流传的几乎都是 PyQt4 的教程,照搬的话大多会出错. eric6 ...

  3. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列03)---- Drawing(绘图)(2)-- 画点

    [概览] 本文实现如下的程序:(在窗体中绘画出[-100, 100]两个周期的正弦函数图像) 主要步骤如下: 1.在eric6中新建项目,新建窗体 2.(自动打开)进入PyQt5 Desinger,编 ...

  4. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列03)---- Drawing(绘图)(1)-- 绘写文字

    [概览] 本文实现如下的程序:(在窗体中绘画出文字) 主要步骤如下: 1.在eric6中新建项目,新建窗体 2.(自动打开)进入PyQt5 Desinger,编辑图形界面,保存 3.回到eric 6, ...

  5. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列02)---- 省市县(区)下拉列表多级联动

    [概览] 本文实现如下的程序: 主要步骤如下: 1.在eric6中新建项目,新建窗体 2.(自动打开)进入PyQt5 Desinger,编辑图形界面,保存 3.回到eric 6,对上一步得到的界面文件 ...

  6. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列03)---- Drawing(绘图)(3)-- 画线

    [概览] 本文实现如下的程序:(在窗体中绘画出各种不同风格的线条) 主要步骤如下: 1.在eric6中新建项目,新建窗体 2.(自动打开)进入PyQt5 Desinger,编辑图形界面,保存 3.回到 ...

  7. python基础知识-GUI编程-TK-StringVar

    1.如何引出StringVar 之前一直认为StringVar就是类似于Java的String类型的对象变量,今天在想要设置StringVar变量的值的时候,通过搜索发现StringVar并不是pyt ...

  8. [python]小练习__创建你自己的命令行 地址簿 程序

    创建你自己的命令行 地址簿 程序. 在这个程序中,你可以添加.修改.删除和搜索你的联系人(朋友.家人和同事等等)以及它们的信息(诸如电子邮件地址和/或电话号码). 这些详细信息应该被保存下来以便以后提 ...

  9. Python gui编程pyQt5安装步骤t

    Python gui编程pyQt5安装步骤         pip install PyQt5 Pip3 install PyQt5               https://riverbankco ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(7):索引器

    在开始正题之前,先补充一点前面的内容. 在方法中,如果要引用方法参数,前面的示例中,老周使用的是 CodeVariableReferenceExpression 类,它用于引用变量,也适用于引用方法参 ...

  2. “.Net 社区虚拟大会”(dotnetConf) 2016 Day 3 Keynote: Scott Hanselman

    美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.9 是第三天, Scott Hanselman 做Keynote.今天主题围绕的是.NET ...

  3. linux基础学习笔记

    我用的是centOS7.0版本的系统.linux的shell终端窗口类似于wind的command窗口 shell命令提示符格式:用户名@主机名:目录名 提示符 @前面的是已登录的用户名,@之后的为计 ...

  4. 在 Laravel 中使用图片处理库 Integration/Image

    系统需求 PHP >= 5.3 Fileinfo Extension GD Library (>=2.0) … or … Imagick PHP extension (>=6.5.7 ...

  5. PHP以接口方式实现多重继承(完全模拟)--学习笔记

     1.UML类图: 2.PHP代码: <?php /** * Created by PhpStorm. * User: andy * Date: 16-11-23 * Time: 下午7:57 ...

  6. DDD 领域驱动设计-商品建模之路

    最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念: 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独 ...

  7. Vertica 数据库知识汇总篇

    Vertica 数据库知识汇总篇(更新中..) 1.Vertica 集群软件部署,各节点硬件性能测试 2.Vertica 创建数据库,创建业务用户测试 3.Vertica 数据库参数调整,资源池分配 ...

  8. 拼图小游戏之计算后样式与CSS动画的冲突

    先说结论: 前几天写了几个非常简单的移动端小游戏,其中一个拼图游戏让我郁闷了一段时间.因为要获取每张图片的位置,用`<style>`标签写的样式,直接获取计算后样式再用来交换位置,结果就悲 ...

  9. 编写高质量代码:改善Java程序的151个建议(第6章:枚举和注解___建议88~92)

    建议88:用枚举实现工厂方法模式更简洁 工厂方法模式(Factory Method Pattern)是" 创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其它子类" ...

  10. 多线程同步工具——Lock

    本文原创,转载请注明出处. 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> 锁分独占锁与共享锁, ...