hustlei 2020-05-19
无聊写写。最近学习做python GUI, 感觉比网页落后好多。我只是为了完成老师布置的任务, 做一个配合ZBar扫描条形码的小程序, 不打算过多深究二维码什么的。由于pyqt5貌似不是很火爆, 没多少成系统的教程。我能找到的就是这个网站。不用挨着挨着看, 滑到最下面show you the code
先上一张图, 看看效果
简单得不能再简单了, 但第一次写有点磕磕绊绊的
工具:pycharm + qt Designer + pyUICqt Designer
的主要功能就是把图形界面做出来pyUIC
的主要功能就是把图形界面翻译成.py
文件
主要实现的功能有: 用外部程序ZBar扫描图片、用xlwt库导出数据到excel, 其它的就是pyqt5的一些操作比如选择文件、换ico图标之类的
安装pyqt5pip install pyqt5
安装pyqt5-toolspip install pyqt5-tools
完成了之后qt designer就安装在你的python根目录下的\Lib\site-packages\pyqt5_tools\Qt\bin\
下
接下来就是配置pycharmFile
->settings
->Tools
->External Tools
->+
配置qt designer
配置pyUIC
参数:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
工具的位置:
至此工具已经安装好
pycharmnew
一个python项目出来Tools
->External Tools
->qt Designer
创建一个Main Window
模板(我第一次创建了个对话框, 怎么也运行不了)
左边工具栏Display Widgets
拖一个Label
到窗口中间, 安静地敲下Helloworld
右击HelloWorld->change styleSheet
这应该是你熟悉的css, 我们看到字有点超出边界了, 这里有个小技巧
右击Helloword->Layout
->Adjust Size
这时候Helloworld饱满了Ctrl+S
保存到项目文件夹
左键选中helloworld.ui(一定要选中) 寻找工具pyUIC 点击 自动生成了helloworld.py文件
在项目目录中新建main.py文件作为主入口, 与图形界面的代码分离(试过就知道很方便), 并敲下如下代码:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow import helloworld if __name__ == "__main__": app = QApplication(sys.argv) mw = QMainWindow() ui = helloworld.Ui_MainWindow() ui.setupUi(mw) mw.show() # 逻辑代码 sys.exit(app.exec_())
运行main.py
安装到你项目的目录中
进入ZBar/bin/
目录, 在此处打开控制台
准备一张条形码图片(ZBar/example
目录中有一张条形码)
扫描出来就证明成功了, bin/下的另一个zbarcam.exe是用来扫视频中的条形码的
Tools
->External Tools
->Qt Desginer
创建一个Main Window
先把界面摆好Buttons
->Push Button
拖出两个按钮Item Widgets
->Table Widget
拖出两个表格框Display Widgets
->Label
拖出标签
文件数:和0是两个不同标签
接下来开始为每个组件命名, 要做到见名知意
选中导入按钮命名为importBtn
依次地
保存到项目目录为barcode.ui->左键点击选中->Tools
->External Tools
->pyUIC
得到barcode.py 图形界面的代码
首先在main.py文件中进口barcode.py模块, 更改barcode模块生成ui对象
有两个对象ui对象和MainWindow对象
通过ui对象可以管理按钮、标签等组件, 通过MainWindow可以进行关闭窗口等操作
通过点击按钮 触发事件
先写最简单的退出按钮
# main.py import sys from PyQt5.QtWidgets import QApplication, QMainWindow import barcode def exitEvent(mw): mw.close() if __name__ == "__main__": app = QApplication(sys.argv) mw = QMainWindow() ui = barcode.Ui_MainWindow() ui.setupUi(mw) mw.show() # 逻辑代码 ui.exitBtn.clicked.connect(lambda: exitEvent(mw)) sys.exit(app.exec_())
ui.exitBtn.clicked.connect(lambda: exitEvent(mw))
这里使用lambda表达式主要是为了解决传参问题,还有一种partial传参方式了解就行了。把mw主窗口对象传递到exitEvent()函数中, 对mw对象进行操作, 实现操作的模块化, 比如加个退出的确认框呀什么的
运行main.py 点击退出按钮 退出界面
打开barcode.ui 双击第一个表格框 写出各个字段
然后点击保存, 直接用pyUIC转换成barcode.py文件, 这就是分离的好处, 只管界面, 不管逻辑。
我们可以通过观察barcode.py学习这个表格是怎么注入数据的, 如这一段
# def setupUi() item = QtWidgets.QTableWidgetItem() self.fileList.setItem(0, 0, item) # 在(0,0)位置处生成一个单元格
# def retranslateUi() item = self.fileList.item(0, 0) item.setText(_translate("MainWindow", "1")) # 在(0,0)处注入1这个字符
可以看到setupUi()方法专注于创建, 而retranslateUi()方法专注于注入数据, 就像盖楼一样, 我们先把框架搭好, 然后再在里面添砖加瓦
from PyQt5 import QtCore, QtGui import barcode import sys import time import os from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QTableWidgetItem # 文件集合, 确保元素不重复 fileSet = set() # method def importEvent(mw, ui): files = QFileDialog.getOpenFileNames(mw, ‘打开文件‘, ‘./‘, ("Images (*.png *.jpg *.bmp *.gif *.raw *.tif *.xpm)")) for i in files[0]: fileSet.add(i) ui.totalFileNum.setText(str(len(fileSet))) # 文件总数 ui.fileList.clearContents() ui.fileList.setRowCount(0) for file in fileSet: row = ui.fileList.rowCount() ui.fileList.insertRow(row) # 文件名 fileNameItem = QTableWidgetItem(os.path.basename(file)) fileNameItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件大小 fileSizeItem = QTableWidgetItem(str("%.2f" % (os.path.getsize(file) / 1024)) + "KB") fileSizeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件绝对路径 filePathItem = QTableWidgetItem(file) filePathItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件创建时间 createTimeItem = QTableWidgetItem(timeStampToStrTime(os.path.getctime(file))) createTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件修改时间 modifyTimeItem = QTableWidgetItem(timeStampToStrTime(os.path.getmtime(file))) modifyTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) ui.fileList.setItem(row, 0, fileNameItem) ui.fileList.setItem(row, 1, fileSizeItem) ui.fileList.setItem(row, 2, filePathItem) ui.fileList.setItem(row, 3, createTimeItem) ui.fileList.setItem(row, 4, modifyTimeItem) def exitEvent(mw): """退出事件""" mw.close() if __name__ == "__main__": app = QApplication(sys.argv) mw = QMainWindow() ui = barcode.Ui_MainWindow() ui.setupUi(mw) mw.show() # 主体代码 # 字段显示宽度 ui.fileList.setColumnWidth(0, 150) ui.fileList.setColumnWidth(1, 150) ui.fileList.setColumnWidth(2, 500) ui.fileList.setColumnWidth(3, 200) ui.fileList.setColumnWidth(4, 200) ui.resultList.setColumnWidth(0, 150) ui.resultList.setColumnWidth(1, 150) ui.resultList.setColumnWidth(2, 400) ui.resultList.setColumnWidth(3, 400) # 按钮 ui.importBtn.clicked.connect(lambda: importEvent(mw, ui)) # 导入按钮 ui.exitBtn.clicked.connect(lambda: exitEvent(mw)) # 退出按钮 sys.exit(app.exec_())
这里用到文件选择器, 要用QFileDialog.getOpenFileNames(), 按住Ctrl可以选择多个文件。 别用getOpenfileName()这个一次只能选择一个文件。
返回的files是一个元组, ([文件1绝对路径, 文件2绝对路径, ...], 文件类型)
files[0]返回的是选中的文件的绝对路径, 有了绝对路径就好办事了, 文件名、大小、创建时间等属性可以由os.path得到
创建一个集合fileSet用于保存文件的绝对路径, 这样就不怕选择了重复的文件
("Images (*.png *.jpg *.bmp *.gif *.raw *.tif *.xpm)")是对文件类型的限制, 去掉这个参数就是可以添加任何文件
每次新加入文件就把原来的表格内容清空同时行号置0
ui.fileList.clearContents() ui.fileList.setRowCount(0)
其它没啥好说的, 一些pyqt组件基本属性操作
运行main.py
打开barcode.ui 双击第二个表格 依次写出各个字段
from PyQt5 import QtCore, QtGui import barcode import sys import time import os from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QTableWidgetItem # 文件集合, 确保元素不重复 fileSet = set() # method def importEvent(mw, ui): files = QFileDialog.getOpenFileNames(mw, ‘打开文件‘, ‘./‘, ("Images (*.png *.jpg *.bmp *.gif *.raw *.tif *.xpm)")) if !files: return for i in files[0]: fileSet.add(i) ui.totalFile.setText(str(len(fileSet))) # 文件总数 ui.fileList.clearContents() ui.fileList.setRowCount(0) for file in fileSet: row = ui.fileList.rowCount() ui.fileList.insertRow(row) # 文件名 fileNameItem = QTableWidgetItem(os.path.basename(file)) fileNameItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件大小 fileSizeItem = QTableWidgetItem(str("%.2f" % (os.path.getsize(file) / 1024)) + "KB") fileSizeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件绝对路径 filePathItem = QTableWidgetItem(file) filePathItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件创建时间 createTimeItem = QTableWidgetItem(timeToStrTime(os.path.getctime(file))) createTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件修改时间 modifyTimeItem = QTableWidgetItem(timeToStrTime(os.path.getmtime(file))) modifyTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) ui.fileList.setItem(row, 0, fileNameItem) ui.fileList.setItem(row, 1, fileSizeItem) ui.fileList.setItem(row, 2, filePathItem) ui.fileList.setItem(row, 3, createTimeItem) ui.fileList.setItem(row, 4, modifyTimeItem) def timeToStrTime(timestamp): """将时间戳转化为格式时间字符串""" timeStruct = time.localtime(timestamp) return time.strftime(‘%Y-%m-%d %H:%M‘, timeStruct) def startScan(mw, ui): if len(fileSet) == 0: QMessageBox.information(mw, ‘提示‘, ‘你没有选择任何条形码图片文件!‘, QMessageBox.Ok, QMessageBox.Ok) return successCount = 0 # 成功检测数 failCount = 0 # 失败检测数 cmd = ".\\ZBar\\bin\\zbarimg.exe " ui.resultList.clearContents() ui.resultList.setRowCount(0) for file in fileSet: array = execCmd(cmd + file) # 执行命令.\zbarimg.exe imgPath row = ui.resultList.rowCount() ui.resultList.insertRow(row) fileNameItem = QTableWidgetItem(os.path.basename(file)) fileNameItem.setTextAlignment(QtCore.Qt.AlignCenter) if len(array) == 0: # 设置失败红色字体 statusItem = QTableWidgetItem("扫描失败") statusItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) statusItem.setForeground(brush) typeItem = QTableWidgetItem("请调整图片清晰度") typeItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) typeItem.setForeground(brush) valueItem = QTableWidgetItem("请调整图片清晰度") valueItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) valueItem.setForeground(brush) failCount += 1 else: # 设置成功绿色字体 statusItem = QTableWidgetItem("扫描成功") statusItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(35, 177, 32)) brush.setStyle(QtCore.Qt.NoBrush) statusItem.setForeground(brush) result = array[0].split(":") # 分离字符串得到条形码格式和内容 codebarType = result[0] # 条形码格式 codebarValue = result[1].strip() # 条形码内容, 去掉两端的空格、制表符trim()函数 typeItem = QTableWidgetItem(codebarType) typeItem.setTextAlignment(QtCore.Qt.AlignCenter) valueItem = QTableWidgetItem(codebarValue) valueItem.setTextAlignment(QtCore.Qt.AlignCenter) successCount += 1 ui.resultList.setItem(row, 0, fileNameItem) ui.resultList.setItem(row, 1, statusItem) ui.resultList.setItem(row, 2, typeItem) ui.resultList.setItem(row, 3, valueItem) ui.successScanNum.setText(str(successCount)) # 设置成功扫描的文件数 ui.failScanNum.setText(str(failCount)) def exitEvent(mw): """退出事件""" mw.close() def execCmd(cmd): """执行控制台命令""" result = os.popen(cmd) text = result.readlines() result.close() return text if __name__ == "__main__": app = QApplication(sys.argv) mw = QMainWindow() ui = barcode.Ui_MainWindow() ui.setupUi(mw) mw.show() # 主体代码 # 字段显示宽度 ui.fileList.setColumnWidth(0, 150) ui.fileList.setColumnWidth(1, 150) ui.fileList.setColumnWidth(2, 500) ui.fileList.setColumnWidth(3, 200) ui.fileList.setColumnWidth(4, 200) ui.resultList.setColumnWidth(0, 150) ui.resultList.setColumnWidth(1, 150) ui.resultList.setColumnWidth(2, 400) ui.resultList.setColumnWidth(3, 400) # 按钮 ui.scanBtn.clicked.connect(lambda: startScan(mw, ui)) # 扫描按钮 ui.importBtn.clicked.connect(lambda: importEvent(mw, ui)) # 导入按钮 ui.exitBtn.clicked.connect(lambda: exitEvent(mw)) # 退出按钮 sys.exit(app.exec_())
通过execCmd()方法执行控制台指令, cmd = ".\\ZBar\\bin\\zbarimg.exe "
即执行main.py运行的当前目路下的ZBar/bin/zbarimg.exe程序, 只要后面追加一个图片的绝对路径就可以实现扫描操作, 要注意的是zbarimg.exe与图片绝对路径直接有个空格。execCmd()通过执行命令得到结果, 通过把结果加载到内存中, 然后再分离各个字符串, 得到扫描结果return的text是一个列表, 里面要么只有1个元素就是扫描结果, 要么就没有元素未扫描出结果, 很简单的就可以判定
运行main.py
使用xlwt模块pip install xlwt
# main.py def exportEvent(mw, ui): """保存到excel""" if len(fileSet) == 0: QMessageBox.information(mw, ‘提示‘, ‘你没有选择任何条形码图片文件!‘, QMessageBox.Ok, QMessageBox.Ok) return # 创建一个workbook 设置编码 workbook = xlwt.Workbook(encoding=‘utf-8‘) # 创建一个worksheet worksheet = workbook.add_sheet(‘sheet1‘) # 样式 t = xlwt.Font() t.colour_index = 10 # 红色字体 redColorFont = xlwt.XFStyle() redColorFont.font = t t = xlwt.Font() t.colour_index = 57 # 绿色字体 greenColorFont = xlwt.XFStyle() greenColorFont.font = t # 参数对应 行, 列, 值 worksheet.write(0, 0, label=‘文件名‘) worksheet.col(0).width = 256 * 20 worksheet.write(0, 1, label=‘扫描状态‘) worksheet.write(0, 2, label=‘格式‘) worksheet.write(0, 3, label=‘值‘) worksheet.col(3).width = 256 * 20 worksheet.write(0, 4, label=‘文件路径‘) worksheet.col(4).width = 256 * 80 cmd = ".\\ZBar\\bin\\zbarimg.exe " row = 1 for file in fileSet: worksheet.write(row, 0, label=os.path.basename(file)) array = execCmd(cmd + file) if len(array) == 0: worksheet.write(row, 1, u‘扫描失败‘, redColorFont) else: worksheet.write(row, 1, u‘扫描成功‘, greenColorFont) result = array[0].split(":") worksheet.write(row, 2, label=result[0]) worksheet.write(row, 3, label=result[1].strip()) worksheet.write(row, 4, label=file) row += 1 # 保存 workbook.save(‘.\\data.xls‘)
# main.py from PyQt5 import QtCore, QtGui import barcode import sys import time import xlwt import os from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QTableWidgetItem # 文件集合, 确保元素不重复 fileSet = set() # method def importEvent(mw, ui): files = QFileDialog.getOpenFileNames(mw, ‘打开文件‘, ‘./‘, ("Images (*.png *.jpg *.bmp *.gif *.raw *.tif *.xpm)")) if not files[0]: return for i in files[0]: fileSet.add(i) ui.totalFileNum.setText(str(len(fileSet))) # 文件总数 ui.fileList.clearContents() ui.fileList.setRowCount(0) for file in fileSet: row = ui.fileList.rowCount() ui.fileList.insertRow(row) # 文件名 fileNameItem = QTableWidgetItem(os.path.basename(file)) fileNameItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件大小 fileSizeItem = QTableWidgetItem(str("%.2f" % (os.path.getsize(file) / 1024)) + "KB") fileSizeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件绝对路径 filePathItem = QTableWidgetItem(file) filePathItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件创建时间 createTimeItem = QTableWidgetItem(timeToStrTime(os.path.getctime(file))) createTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) # 文件修改时间 modifyTimeItem = QTableWidgetItem(timeToStrTime(os.path.getmtime(file))) modifyTimeItem.setTextAlignment(QtCore.Qt.AlignCenter) ui.fileList.setItem(row, 0, fileNameItem) ui.fileList.setItem(row, 1, fileSizeItem) ui.fileList.setItem(row, 2, filePathItem) ui.fileList.setItem(row, 3, createTimeItem) ui.fileList.setItem(row, 4, modifyTimeItem) def timeToStrTime(timestamp): """将时间戳转化为格式时间字符串""" timeStruct = time.localtime(timestamp) return time.strftime(‘%Y-%m-%d %H:%M‘, timeStruct) def startScan(mw, ui): if len(fileSet) == 0: QMessageBox.information(mw, ‘提示‘, ‘你没有选择任何条形码图片文件!‘, QMessageBox.Ok, QMessageBox.Ok) return successCount = 0 # 成功检测数 failCount = 0 # 失败检测数 cmd = ".\\ZBar\\bin\\zbarimg.exe " ui.resultList.clearContents() ui.resultList.setRowCount(0) for file in fileSet: array = execCmd(cmd + file) # 执行命令.\zbarimg.exe imgPath row = ui.resultList.rowCount() ui.resultList.insertRow(row) fileNameItem = QTableWidgetItem(os.path.basename(file)) fileNameItem.setTextAlignment(QtCore.Qt.AlignCenter) if len(array) == 0: # 设置失败红色字体 statusItem = QTableWidgetItem("扫描失败") statusItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) statusItem.setForeground(brush) typeItem = QTableWidgetItem("请调整图片清晰度") typeItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) typeItem.setForeground(brush) valueItem = QTableWidgetItem("请调整图片清晰度") valueItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(234, 31, 42)) brush.setStyle(QtCore.Qt.NoBrush) valueItem.setForeground(brush) failCount += 1 else: # 设置成功绿色字体 statusItem = QTableWidgetItem("扫描成功") statusItem.setTextAlignment(QtCore.Qt.AlignCenter) brush = QtGui.QBrush(QtGui.QColor(35, 177, 32)) brush.setStyle(QtCore.Qt.NoBrush) statusItem.setForeground(brush) result = array[0].split(":") # 分离字符串得到条形码格式和内容 codebarType = result[0] # 条形码格式 codebarValue = result[1].strip() # 条形码内容, 去掉两端的空格、制表符trim()函数 typeItem = QTableWidgetItem(codebarType) typeItem.setTextAlignment(QtCore.Qt.AlignCenter) valueItem = QTableWidgetItem(codebarValue) valueItem.setTextAlignment(QtCore.Qt.AlignCenter) successCount += 1 ui.resultList.setItem(row, 0, fileNameItem) ui.resultList.setItem(row, 1, statusItem) ui.resultList.setItem(row, 2, typeItem) ui.resultList.setItem(row, 3, valueItem) ui.successScanNum.setText(str(successCount)) # 设置成功扫描的文件数 ui.failScanNum.setText(str(failCount)) def exportEvent(mw, ui): """保存到excel""" if len(fileSet) == 0: QMessageBox.information(mw, ‘提示‘, ‘你没有选择任何条形码图片文件!‘, QMessageBox.Ok, QMessageBox.Ok) return # 创建一个workbook 设置编码 workbook = xlwt.Workbook(encoding=‘utf-8‘) # 创建一个worksheet worksheet = workbook.add_sheet(‘sheet1‘) # 样式 t = xlwt.Font() t.colour_index = 10 # 红色字体 redColorFont = xlwt.XFStyle() redColorFont.font = t t = xlwt.Font() t.colour_index = 57 # 绿色字体 greenColorFont = xlwt.XFStyle() greenColorFont.font = t # 参数对应 行, 列, 值 worksheet.write(0, 0, label=‘文件名‘) worksheet.col(0).width = 256 * 20 worksheet.write(0, 1, label=‘扫描状态‘) worksheet.write(0, 2, label=‘格式‘) worksheet.write(0, 3, label=‘值‘) worksheet.col(3).width = 256 * 20 worksheet.write(0, 4, label=‘文件路径‘) worksheet.col(4).width = 256 * 80 cmd = ".\\ZBar\\bin\\zbarimg.exe " row = 1 for file in fileSet: worksheet.write(row, 0, label=os.path.basename(file)) array = execCmd(cmd + file) if len(array) == 0: worksheet.write(row, 1, u‘扫描失败‘, redColorFont) else: worksheet.write(row, 1, u‘扫描成功‘, greenColorFont) result = array[0].split(":") worksheet.write(row, 2, label=result[0]) worksheet.write(row, 3, label=result[1].strip()) worksheet.write(row, 4, label=file) row += 1 # 保存 workbook.save(‘.\\data.xls‘) def exitEvent(mw): """退出事件""" mw.close() def execCmd(cmd): """执行控制台命令""" result = os.popen(cmd) text = result.readlines() result.close() return text if __name__ == "__main__": app = QApplication(sys.argv) mw = QMainWindow() ui = barcode.Ui_MainWindow() ui.setupUi(mw) mw.show() # 主体代码 # 字段显示宽度 ui.fileList.setColumnWidth(0, 150) ui.fileList.setColumnWidth(1, 150) ui.fileList.setColumnWidth(2, 500) ui.fileList.setColumnWidth(3, 200) ui.fileList.setColumnWidth(4, 200) ui.resultList.setColumnWidth(0, 150) ui.resultList.setColumnWidth(1, 150) ui.resultList.setColumnWidth(2, 400) ui.resultList.setColumnWidth(3, 400) # 按钮 ui.exportBtn.clicked.connect(lambda: exportEvent(mw, ui)) # 导出按钮 ui.scanBtn.clicked.connect(lambda: startScan(mw, ui)) # 扫描按钮 ui.importBtn.clicked.connect(lambda: importEvent(mw, ui)) # 导入按钮 ui.exitBtn.clicked.connect(lambda: exitEvent(mw)) # 退出按钮 sys.exit(app.exec_())
run main.py
运行良好
以上即是在学习pythonGUI上的记录, 说实话真的没啥必要, Django不香吗 vue不香吗
源码在这里