PyQt-数据录入软件实战

传送门:PyQt图像软件(基于项目的学习)

需求分析

某医院发育筛查测试 量化软件

输入

患者基本信息
筛查测试表(已有模板表格)

输出

各项原始分值 运动、社会适应、智力;
各项计算分值 IQ EQ
对应建议

报告格式-固定

保存至本地(PDF/Word),考虑模板替换

框架设计

基于python 3.6 + QT

界面层 - QtDesigner

整体风格

因为量化表有三大分类,且各部分参数指标较多,因此采用堆叠布局的方式
主界面由两个frame构成
在这里插入图片描述

多行文本框

https://blog.csdn.net/qq_28077617/article/details/121876632

子页面

信息

对于有多个输入框(lineEdit)时,可以考虑引入Tab快捷键,具体设置参考这篇文章;

日期嵌套

日历下拉选择时间

https://blog.csdn.net/weixin_39626452/article/details/86700178?spm=1001.2014.3001.5506
UI

self.dateEdit = QtWidgets.QDateEdit(self.gridLayoutWidget)
        self.dateEdit.setObjectName("dateEdit")
        # 设置日期显示格式
        self.dateEdit.setDisplayFormat('yyyy-MM-dd')
        # 设置允许日历控件弹出
        self.dateEdit.setCalendarPopup(True)
                # 当日期改变时触发槽函数
        self.dateEdit.dateChanged.connect(self.onTimeChanged)

业务代码

扫描二维码关注公众号,回复: 15407968 查看本文章
    def onTimeChanged(self,time):
        birthtime = time
        print(birthtime)
获取当前时间并显示
    # 获取当前时间
    testTime = datetime.datetime.now().strftime('%Y-%m-%d')
    # 回传子页面 setText设置输入框显示值
    self.lineEdit_TestTime.setText(testTime)
    print(testTime)
设置日历选择的日期范围
   # 设置日期范围
   self.dateEdit.setMinimumDate(QDate.currentDate().addDays(-365*10))
   self.dateEdit.setMaximumDate(QDate.currentDate().addDays(0))

堆叠设计

在这里插入图片描述
Stacked Widget,然后参考上面那篇文章,下面主要是注意事项;

信号与槽
        # 建立堆叠布局信号与槽连接
        self.pushButton_Sports.clicked.connect(self.frameController)
        self.pushButton_Social.clicked.connect(self.frameController)
        self.pushButton_Iq.clicked.connect(self.frameController)

如果将信号与槽建立的代码放在按钮创建代码前,就会出现
AttributeError: 'AppMainWindow' object has no attribute 'pushButton_Sports' 的错误

窗口自适应屏幕大小

关于grid layout 没有自动生成网格线

在这里插入图片描述

qt-designer添加自定义图片

查看这篇文章
进行转换,
pyrcc5 -o re_rc.py 你自己的资源文件名.qrc在这里插入图片描述

业务层

遍历获取checkBox状态,并换算为分值计算

场景再现:如上图,我有很多复选框,我想依次获取复选框的选取状态;应该指出每个checkBox都有独一无二的名字,其格式一般为checkBox_XXX (xxx为编号),这种命名方式为我们遍历提供了便利;
参考这个回答,传统的for循环+拼接对象字符串是不可行的,因为你获得的是一个字符串而非对象名;**我们使用getattr()**方法来生成checkBOx对象名

# 这里的self是页面父类-主窗口
           try:
        # 针对运动表,判断其最后一个得分项
        for i in range(1, 31):
            # 遍历checkBox对象
            m = getattr(self, "checkBox_%d" % i)
            if m.isChecked():
                #     print("checked" + m)
                print("num" + str(i) + ": " + str(m.isChecked()))
                sports_pass = i
        print("the final score is:" + str(sports_pass))

    except Exception as e:
        print(e)

这里要说明的是,如果你使用print(m)想打出对象名字是会报错的,报错信息大概应该是:
must be str, not QCheckBox (这里也是为什么要进行异常捕获的原因)
这里的m可以认为是一个对象引用,print函数只能打印字符串,所以这里报错没毛病,我想这也是前面那篇回答有人运行会报错的原因;
如果硬要打印出m,可以使用str(m),这时你会发现它是一个object,打印出来的应该是地址?

docx 生成报告模板

dict(zip)

主要用于生成关键字字典,来进行对比替换

py-docx 三方库

参考这篇文章

保存文件到指定位置(弹出保存文件对话框)

pyqt5已经提供了文件资源对话框,即getSaveFileName;

filepath, type = QFileDialog.getSaveFileName(self, '报告保存', '/'+ '%s_报告单.docx' % model, 'docx(*.docx)')
# '报告保存' 是对话框的标题,中间那一长串是保存的路径,后面是默认保存位置
# 这里我是根据患者测试时间和姓名 默认给出了文件名字,使用%s 占位符 将字符串model和 路径以及后缀名拼接
doc_t.save(filepath)
# 再使用doc_t.save将文件保存至指定filepath

生成报告及保存至指定路径完整代码

参考这篇文章

注意事项

在打开保存对话框之后,如果取消保存,这时候程序会返回一个空路径(‘’),这时会报错,因此需要判断程序路径是否为空。

import datetime

import docx
from PyQt5.QtWidgets import QFileDialog


def createReport(self,list):

    try:

            # 需制作缴费通知单数据
        report_data = list
        # print(list)

            # 模板内设置的标志:
        tags_1 = ['name','id','gender','birthday','preweek','testdate','months','sports',
                      'social','intel','min','mi','dqn','dq','advice']
        # print(report_data)

            # 生成字典类型的数据集
        notice_dict = dict(zip(tags_1, report_data))
        # print(notice_dict)

        doc_t, runs_t = get_runs('报告模板.docx')
            # 遍历模板run对象 和 notice_dict key 匹配
            # 匹配成功则替换 run 内容
        for run_t in runs_t:
            if run_t.text in notice_dict.keys():
                run_t.text = str(notice_dict[run_t.text])
        #生成格式化文件名
        model = datetime.datetime.now().strftime('%Y%m%d') + '_'+ list[0]

        # 文件保存路径
        save_doc(self,doc_t,model)


    except Exception as e:
        print(e)


# 定义获取 word 模板正文段落、表格 run 对象函数
def get_runs(path):
    doc = docx.Document(path)
    runs_g = []
    # 获取段落 run 对象
    # print(doc.paragraphs)
    for par_g in doc.paragraphs:
        for run_g in par_g.runs:
            runs_g.append(run_g)
            # print(run_g.text)
    # # 获取表格 run 对象
    # table_g = doc.tables[0]
    # for cell_g in table_g._cells:
    #     for cell_par_g in cell_g.paragraphs:
    #         for cell_run_g in cell_par_g.runs:
    #             runs_g.append(cell_run_g)
    #             print(cell_run_g.text)
    return doc, runs_g

# 文件保存路径
def save_doc(self,doc_t,model):
    filepath, type = QFileDialog.getSaveFileName(self, '文件保存', '/'+ '%s_报告单.docx' % model, 'docx(*.docx)')
    print(filepath)
    doc_t.save(filepath)
#

菜单栏点击 弹出 帮助文档

 def trigger_helpDoc(self):
        webbrowser.open('Help.pdf', new=1)

打开最近保存路径

使用外部 word/WPS 软件预览生成的文档

网上搜了很多,大部分是调用word接口,然后再Pyqt框架内展示文档;我这里的需求是使用python调用外部软件,使文档在相应软件中打开;
使用pywin32com库pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pypiwin32

from win32com.client import Dispatch
# 这里定义了打开的程序接口
app = Dispatch('Word.Application')
# 这里是打开文件的路径
app.Documents.Open(self.pathtemp)
# 设置word文件可见
app.Visible = True
time.sleep(0.5)

这里有一个问题,那就是我的报告必须是生成并保存了之后才能预览,我想在报告保存前进行预览,不过失败了

# 具体过程参见前面的 docx文件生成并保存
doc_t, runs_t = get_runs('报告模板.docx')

app.Documents.Open(doc_t)

软件打包

关于spec配置文件

UPX is not available

Please convert your ‘.png’ file to a ‘.ico’ and try again.

exe.notanexecutable

生成不可执行文件

Unable to open icon file \image\logo.ico

1、据了解是图标大小不对 16*16
2、ico文件路径 image\logo.ico 而不是\image\logo.ico

图标消失

需要将图标文件拷贝到exe执行文件下,

pyinstaller打包文件过大

使用pipenv

这篇文章是yyds
建议在pipenv环境下依然使用 pyinstaller XXX.spec 配置文件来输出程序
我的软件从原来的200多M缩小到了40多兆;

安装PyQt5失败

在这里插入图片描述

直接先安装pip install PyQt5-sip== 12.9.1
然后再安装PyQt5成功

将资源文件与exe进行二次打包

可以看到打包前资源文件全是散在文件夹下面,看起来很low
在这里插入图片描述
使用Winrar二次打包之后,文件变为一个整体:
在这里插入图片描述

使用Winrar
参考文章2
不过打包下来这个图标改不了,后面发现是我的Winrar版本问题,没有更改图标这个选项

Q问题汇总

Python问题:‘Nonetype’ object is not iterable

这里一般是定义了函数 但没有给出返回值

Error tokenizing data. C error: Expected 1 fields in line 3, saw 2

这个不i管用
后面发现是我直接把.xlsx文档改为.csv的原因

ModuleNotFoundError: No module named ‘exceptions‘

参考这篇文字

关于使用pydocx遍历段落,但某些部分被略过

下图中的训练建议的 advice字段按理来说应该被替换掉
在这里插入图片描述

def get_runs(path):
    doc = docx.Document(path)
    runs_g = []
    # 获取段落 run 对象
    print(doc.paragraphs)
    for par_g in doc.paragraphs:
        for run_g in par_g.runs:
            runs_g.append(run_g)
            print(run_g.text)
    # 获取表格 run 对象
    # table_g = doc.tables[0]
    # for cell_g in table_g._cells:
    #     for cell_par_g in cell_g.paragraphs:
    #         for cell_run_g in cell_par_g.runs:
    #             runs_g.append(cell_run_g)
    return doc, runs_g

为了查找问题,打印出遍历的过程,注意这里要使用run_g.text方法才能打出具体内容,否则只是打印出对象地址;
打印出来发现最后得到的是 a dvice,也就是advice被拆分开来了,所以最终替换的时候也没有找对对应的关键字;
解决办法是把模板中、程序中的advice和关键字又打了一遍,不知道为什么有这个问题???玄学???
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_54594861/article/details/123955868