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

简介: 【引子】 在PyQt5自带教程中,地址簿(address book)程序没有完全实现界面与业务逻辑分离。 本文我打算用eric6+PyQt5对其进行改写,以实现界面与逻辑完全分离。   【概览】 1、界面: 2、功能简介:程序有三种操作模式:浏览模式、添加模式、编辑模式。

【引子】

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

 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、下面添加逻辑代码

# -*- 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、程序运行界面

 

 

 

目录
相关文章
|
11天前
|
JSON 数据可视化 API
Python 中调用 DeepSeek-R1 API的方法介绍,图文教程
本教程详细介绍了如何使用 Python 调用 DeepSeek 的 R1 大模型 API,适合编程新手。首先登录 DeepSeek 控制台获取 API Key,安装 Python 和 requests 库后,编写基础调用代码并运行。文末包含常见问题解答和更简单的可视化调用方法,建议收藏备用。 原文链接:[如何使用 Python 调用 DeepSeek-R1 API?](https://apifox.com/apiskills/how-to-call-the-deepseek-r1-api-using-python/)
|
22天前
|
IDE 测试技术 项目管理
【新手必看】PyCharm2025 免费下载安装配置教程+Python环境搭建、图文并茂全副武装学起来才嗖嗖的快,绝对最详细!
PyCharm是由JetBrains开发的Python集成开发环境(IDE),专为Python开发者设计,支持Web开发、调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试和版本控制等功能。它有专业版、教育版和社区版三个版本,其中社区版免费且适合个人和小型团队使用,包含基本的Python开发功能。安装PyCharm前需先安装Python解释器,并配置环境变量。通过简单的步骤即可在PyCharm中创建并运行Python项目,如输出“Hello World”。
197 13
【新手必看】PyCharm2025 免费下载安装配置教程+Python环境搭建、图文并茂全副武装学起来才嗖嗖的快,绝对最详细!
|
2月前
|
Unix Linux 程序员
[oeasy]python053_学编程为什么从hello_world_开始
视频介绍了“Hello World”程序的由来及其在编程中的重要性。从贝尔实验室诞生的Unix系统和C语言说起,讲述了“Hello World”作为经典示例的起源和流传过程。文章还探讨了C语言对其他编程语言的影响,以及它在系统编程中的地位。最后总结了“Hello World”、print、小括号和双引号等编程概念的来源。
126 80
|
23天前
|
存储 缓存 Java
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
58 5
Python高性能编程:五种核心优化技术的原理与Python代码
|
2月前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
52 14
|
2月前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
116 2
|
2月前
|
数据可视化 DataX Python
Seaborn 教程-绘图函数
Seaborn 教程-绘图函数
87 8
|
2月前
|
Python
Seaborn 教程-模板(Context)
Seaborn 教程-模板(Context)
57 4
|
2月前
|
人工智能 数据可视化 数据挖掘
探索Python编程:从基础到高级
在这篇文章中,我们将一起深入探索Python编程的世界。无论你是初学者还是有经验的程序员,都可以从中获得新的知识和技能。我们将从Python的基础语法开始,然后逐步过渡到更复杂的主题,如面向对象编程、异常处理和模块使用。最后,我们将通过一些实际的代码示例,来展示如何应用这些知识解决实际问题。让我们一起开启Python编程的旅程吧!
|
2月前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。

热门文章

最新文章

推荐镜像

更多