飞道的博客

PyQt5开发的数据编辑器(一)

666人阅读  评论(0)

前言

一直在做游戏服务器的开发工作,很多时候都要与数据打交道,很多数据都是手动修改,费时间也不易维护。一直想写一个工具一键导出游戏数据,不知从何写起;恰好现在新开发游戏,开发新游戏的同时,也写一个简简单单的数据导出工具,方便策划和程序查看或修改数据,协助新项目的进行。

本次使用的主要语言是Python,导出语言是后端的Lua表,以及前端的Json数据。为了方便策划规划、修改数据,数据来源主要是Excel,后期可能会做成数据库,但从策划的效率上来看,还是Excel方便些。

开始

开发环境

首先安装Python,我在做项目时已经更新到了3.9,由于很多库(例如pandas)都不支持2.0,因此3.0是一个不错的选择。

Python的集成开发环境我推荐使用PyCharm,社区版完全足够个人学习或开发,没有安装的童鞋可以前往这里学习并安装PyCharm和Python。

接下来需要安装此次必要的开发包,需要用到Python的pip安装工具,pip会到https://pythonhosted.org/下载指定包,但外网访问速度过慢,可使用参数-i指定下载地址,具体网址如下:

清华 https://pypi.tuna.tsinghua.edu.cn/simple/
阿里云 http://mirrors.aliyun.com/pypi/simple/
中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
华中理工大学 http://pypi.hustunique.com/
山东理工大学 http://pypi.sdutlinux.org/ 
豆瓣 http://pypi.douban.com/simple/

也可配置pip,永久性指定pip安装源:

  • Linux下,修改 ~/.pip/pip.conf (没有就创建一个文件夹及文件。文件夹要加“.”,表示是隐藏文件夹),填写以下内容:

  
  1. [global]
  2. index-url = http://mirrors.aliyun.com/pypi/simple/
  3. [install]
  4. trusted-host=mirrors.aliyun.com
  • windows下,在User目录中创建一个pip目录,如:C:\Users\xx\pip,新建文件pip.ini。内容同上。

 

pip安装完成之后,在WIndows PowerShell即可安装必要库,pyqt5,pyqt5-tools,pandas,xlrd等包。命令如下:

 pip3 install pyqt5, pyqt5-tools, pandas, xlrd

其中, PyQt5是一套Python绑定Digia QT5应用的框架,Pandas是一个强大的分析结构化数据的工具集, xlrd则是读取Excel的必要包。

创建项目

1、打开PyCharm,选择新建项目,并正确的选择Python版本,并勾选inhert global site-packages(使用系统的库),漏选可在项目中重新构建项目解释器,如下图:

新建项目:

项目解释器:

2、为PyCharm添加工具

打开PyCharm的Settings,选择Tools -> External Tools,点'+'添加工具。

(1. 配置QtDesigner:


  
  1. name:QtDesigner
  2. Program:选择Qt的designer.exe的路径
  3. Working directory:$ProjectFileDir$

(2. 配置PyUIC


  
  1. name: PyUIC
  2. Program:选择python.exe的路径
  3. Arguments:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
  4. Working directory:$FileDir$

(3. 配置PyRCC


  
  1. Name: PyRCC
  2. Program: 选择pyrcc5.exe的路径
  3. Arguments: $FileName$ -o $FileNameWithoutExtension$_rc.py
  4. Working directory: $FileDir$

3、制作界面

在工程目录下新建文件夹ui,用于存放工程所需的界面文件、图标文件。

在工程目录下新建文件夹excel,用于存放数据编辑器开发阶段用的开发用例。

在工程目录下新建文件夹db,用于存放数据编辑器所产生的个人数据(也可用xml、conf等格式),例如界面状态,界面比例等等。

 

打开designer.exe,可以直接通过我们开篇设置的工具打开,有以下三种方式:

  1. 在Project中选择任意文件或文件夹,右键选择External Tools -> QtDesigner。
  2. 在菜单栏中,点击Tools -> External Tools -> QtDesigner。
  3. 在编辑窗口中,右键选择External Tools -> QtDesigner。

打开designer之后,如下所示:

选择创建一个MainWindow,保存到项目目录下的ui文件夹中,保存名为mainwindow_main.ui。

再次打开designer,开始设计主界面布局,重点添加菜单栏、工具栏、状态栏,以及工作区,同时为了美观,加入一些小图标,小图标必须放在ui或ui目录下的文件夹中,如图所示:

选择MainWindow,在右侧的属性编辑器中设置MainWindow的属性,如名称等等,这里我们需要设置程序的初始大小以及最小大小,防止在使用过程中将程序拉缩过小引起不必要的BUG,如下图所示:

设置完成之后添加菜单栏,工具栏,在窗口右键选择添加工具栏,具体如下:

添加基本的菜单,其中,一级菜单栏后面添加(&字母)可以使用Alt+快捷键,例如文件菜单可使用文件(&F)(使用搜狗输入法会默认占用Alt+F等快捷键,可在设置中关闭该功能,并且其他应用也可能占用该快捷键,望知晓),编辑菜单可使用编辑(&E)

在资源管理器窗口中添加小图标资源(误关闭在视图中勾选打开),选择铅笔按钮,添加图片,具体如下:

在动作编辑窗口中添加action,并为action添加图标、名称、快捷键等信息,具体如下:

建立完成后,可以拖拽action到菜单栏或工具栏中,添加action,具体如下:

 

按照上述方法,建立完整的菜单栏,工具栏,效果如下(使用Ctrl+R即可预览):

在工作区中左侧添加一个Tree View,右侧添加一个Table Widget,如下图所示:

在空白处右键点击布局 - 在窗体布局中布局,如下图所示:

也可以使用一个Frame包裹Table Widget,用于后续的扩展,此时Table Widget要在Frame中,并且使用栅格布局,如下图所示:

在对象查看器中选择MainWIndow,右键添加状态栏。

使用Ctrl+R预览,要保证窗口不能拉缩过小,Tree Widget的宽度不变,高度占满程序高度,Table Widget宽高占用剩余部分,最终效果如下:

再次保存文件至ui中的mainwindow_main.ui,退出designer,开始开发逻辑部分。

这里我添加了一个登陆Dialog,暂时没有用到,留着后续使用:

(1.选择文件,新建一个Dialog,不需要按钮。

(2.设置窗口的最小大小和最大大小,登陆窗口操作者不能改变大小,同时设置这两个参数便可阻止操作者做不可预料的事情:

(3.为窗口添加控件,并且使用占位控件,设置控件之间的距离,效果如下:

(4.设置设置账号输入框和密码输入框QLineEdit属性中的maxLength为16个字符,密码输入框的QLineEdit属性中的echoMode为password,用于隐藏输入信息,效果如下:

保存并取名为dialog_login.ui,登录界面制作完成。

4、编写响应逻辑

打开PyCharm,要在Python中使用designer制作的界面文件,需要把ui文件和qrc文件转换成py文件,这里就需要我们开始设置的另外两个工具PyUIC和PyRCC了。

选中Project窗口中的ui.qrc(必须要选中),使用PyRCC生成ui_rc.py,即可生成二级制的Python资源。

选中Project窗口中的mainwindow_main.ui(必须要选中),使用PyUIC生成mainwindow_main.py,即可生成我们编写的界面Python文件。

注意:由于我们没有在main.py文件所在目录下创建界面,因此要手动删除mainwindow_main.py文件中引用ui_rc的代码,在main.py中再引用

修改main.py文件,删除原有内容,写入以下内容:


  
  1. # coding:utf-8
  2. import sys
  3. import ui.mainwindow_main
  4. import ui.ui_rc
  5. from PyQt5.QtWidgets import QApplication, QMainWindow
  6. class Application(QMainWindow):
  7. def __init__(self, parent=None):
  8. super(QMainWindow, self).__init__(parent)
  9. self.ui = ui.mainwindow_main.Ui_MainWindow()
  10. self.ui.setupUi(self)
  11. if __name__ == '__main__':
  12. app = QApplication(sys.argv)
  13. main = Application()
  14. main.show()
  15. sys.exit(app.exec_())

在代码中设置默认最大化显示。并为动作添加信号和槽。绑定局部按钮、菜单的响应函数。


  
  1. class Application(QMainWindow):
  2. def __init__(self, parent=None):
  3. super(QMainWindow, self).__init__(parent)
  4. self.ui = ui.mainwindow_main.Ui_MainWindow()
  5. self.ui.setupUi(self)
  6. # 最大化显示
  7. self.showMaximized()
  8. self.initHandle()
  9. def initHandle(self):
  10. self.ui.action_exit.triggered.connect(qApp.quit)
  11. self.ui.action_import.triggered.connect(self.importData)
  12. self.ui.action_export.triggered.connect(self.exportData)
  13. def importData(self):
  14. pass
  15. def exportData(self):
  16. pass

在主目录中新建文件夹excel, 添加excel文档, 并按照以下规则添加内容:

Excel内容说明示例
Id 数据类型(extra) 数据类型(special) 数据类型(table) 数据类型(int) 数据类型(float) 数据类型(bool) 数据类型(string) 数据类型(vector)
id time condition data num cd isEntity name arr
int extra special table int float bool string vector
  TimeData   TimeData          
  1 TimeData,1 1,2   1.10  1 游戏玩家 1
2 2 ExtraData,1 2,3 2 1.20  0 游戏玩家 1,2
3 3 TimeData,2 1,3 3 1.30  TRUE 游戏玩家 1.0,3.1
4 4 ExtraData,2 2,4 4 1.40  FALSE 游戏玩家 true,false
5 5 TimeData,1 1,4 5 1.50  true 游戏玩家 "你好","世界"
6 6 ExtraData,1 2,5 9999999 1.60  false 游戏玩家 "50%","100%"

 

ExampleData的数据:

TimeData的数据:

其中, 第一行为说明文字,便于理解,第二行是字段名,第三行是字段类型,由于有可能涉及到跨表,所以第四行是跨表的表名。并且规定table是列表式的扩展;extra是单数据的扩展;special是指定表名的扩展。

在主类中添加读取Excel模块, 添加对Excel的解析类, 具体内容如下:


  
  1. # coding:utf-8
  2. import sys
  3. import os
  4. import re
  5. import ui.mainwindow_main
  6. import ui.ui_rc
  7. from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QTreeWidgetItem, qApp, QTableWidgetItem
  8. from PyQt5.QtCore import Qt
  9. from PyQt5 import QtCore, QtGui
  10. import pandas as pd
  11. import threading
  12. import numpy as np
  13. class ExcelData:
  14. def __init__(self, filepath):
  15. self.filepath = filepath
  16. self.sheets = {}
  17. self.finish = False
  18. # self.readSheet()
  19. self.read_thread = threading.Thread(target=self.readSheet, args={ 0})
  20. self.read_thread.start()
  21. def readSheet(self, name=0):
  22. if name in self.sheets:
  23. return self.sheets[name]
  24. sheet = pd.read_excel(self.filepath, sheet_name=name)
  25. data = {}
  26. data[ 'row'] = sheet.shape[ 0] - 3
  27. data[ 'col'] = sheet.shape[ 1]
  28. data[ 'title'] = sheet.columns.values.tolist()
  29. row_values_header = np.array(sheet.iloc[[ 0]]).tolist()[ 0]
  30. row_values_type = np.array(sheet.iloc[[ 1]]).tolist()[ 0]
  31. row_values_table = np.array(sheet.iloc[[ 2]]).tolist()[ 0]
  32. data[ 'header'] = []
  33. data[ 'type'] = []
  34. data[ 'table'] = []
  35. for i in range(data[ 'col']):
  36. data[ 'header'].append(row_values_header[i])
  37. data[ 'type'].append(row_values_type[i])
  38. data[ 'table'].append(row_values_table[i])
  39. pass
  40. for t in data[ 'table']:
  41. if t == t:
  42. self.readSheet(t)
  43. pass
  44. data[ 'record'] = {}
  45. for i in range( 3, data[ 'row']+ 3):
  46. row_values_list = np.array(sheet.iloc[[i]]).tolist()[ 0]
  47. for j in range(data[ 'col']):
  48. data[ 'record'][(i -3, j)] = row_values_list[j]
  49. pass
  50. self.sheets[name] = data
  51. self.finish = True
  52. class Application(QMainWindow):
  53. def __init__(self, parent=None):
  54. super(QMainWindow, self).__init__(parent)
  55. self.global_data = {}
  56. self.ui = ui.mainwindow_main.Ui_MainWindow()
  57. self.ui.setupUi(self)
  58. self.showMaximized()
  59. self.readExcel()
  60. self.initHandle()
  61. def initHandle(self):
  62. self.ui.action_exit.triggered.connect(qApp.quit)
  63. self.ui.action_import.triggered.connect(self.importData)
  64. self.ui.action_export.triggered.connect(self.exportData)
  65. def setTableWidgetData(self, qmodeLindex):
  66. name = self.ui.treeWidget.currentItem().text( 0)
  67. item = self.global_data[name]
  68. if not item.finish:
  69. QMessageBox.critical(self, ( '提示'), ( '正在加载中, 请稍后......'), QMessageBox.Ok)
  70. return
  71. data = item.sheets[ 0]
  72. self.ui.tableWidget.setRowCount(data[ 'row'])
  73. self.ui.tableWidget.setColumnCount(data[ 'col'])
  74. self.ui.tableWidget.setHorizontalHeaderLabels(data[ 'title'])
  75. for i in range(data[ 'row']):
  76. for j in range(data[ 'col']):
  77. item = QTableWidgetItem(str(data[ 'record'][(i, j)]))
  78. item.setTextAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
  79. self.ui.tableWidget.setItem(i, j, item)
  80. pass
  81. def readExcel(self):
  82. self.ui.treeWidget.doubleClicked.connect(self.setTableWidgetData)
  83. self.ui.treeWidget.setColumnCount( 1)
  84. self.ui.treeWidget.setHeaderLabels([ 'Key'])
  85. for root, dirs, files in os.walk( 'excel'):
  86. for file in files:
  87. res = re.findall( '\\~\\$', file)
  88. if len(res) > 0:
  89. continue
  90. name = file.split( '.')[ 0]
  91. root = QTreeWidgetItem(self.ui.treeWidget)
  92. root.setText( 0, name)
  93. self.global_data[name] = ExcelData( 'excel/' + file)
  94. pass
  95. def importData(self):
  96. pass
  97. def exportData(self):
  98. pass
  99. if __name__ == '__main__':
  100. app = QApplication(sys.argv)
  101. main = Application()
  102. main.show()
  103. sys.exit(app.exec_())

从Excel导出的数据中, 很多都与我们设计的初衷有出路, 比如Excel中的小数导入到程序中会变成很多的有效位的数字, 再比如在填表中错误的将字符串填入数字类型中, 简而言之我们需要程序有纠错机制, 也就是对数据的检查, 并及时的通知程序员某些数据有问题需要修正. 因此, 需要对数据进行修正, 具体如下:


  
  1. TYPE_INT = [ 'int', 'sint', 'cint', 'dint']
  2. TYPE_FLO = [ 'float', 'sfloat', 'cfloat', 'dfloat']
  3. TYPE_STR = [ 'string', 'sstring', 'cstring', 'dstring']
  4. TYPE_BOL = [ 'bool', 'sbool', 'cbool', 'dbool']
  5. TYPE_VEC = [ 'vector', 'svector', 'cvector', 'dvector']
  6. TYPE_EXT = [ 'extra', 'sextra', 'cextra', 'iextra', 'ciextra', 'dextra']
  7. TYPE_TAB = [ 'table', 'stable', 'ctable', 'itable', 'citable', 'dtable']
  8. TYPE_SPE = [ 'special', 'sspecial', 'cspecial', 'dspecial']
  9. TYPE_S = [ 'int', 'sint', 'float', 'sfloat', 'string', 'sstring', 'bool', 'sbool', 'vector', 'svector', 'extra', 'sextra', 'iextra', 'table', 'stable', 'itable', 'special', 'sspecial']
  10. TYPE_S_INT = [ 'int', 'sint']
  11. TYPE_S_FLO = [ 'float', 'sfloat']
  12. TYPE_S_STR = [ 'string', 'sstring']
  13. TYPE_S_BOL = [ 'bool', 'sbool']
  14. TYPE_S_VEC = [ 'vector', 'svector']
  15. TYPE_S_EXT = [ 'extra', 'sextra', 'iextra']
  16. TYPE_S_TAB = [ 'table', 'stable', 'itable']
  17. TYPE_S_SPE = [ 'special', 'sspecial']
  18. TYPE_C = [ 'int', 'cint', 'float', 'cfloat', 'string', 'cstring', 'bool', 'cbool', 'vector', 'cvector', 'extra', 'cextra', 'iextra', 'ciextra', 'table', 'ctable', 'itable', 'citable', 'special', 'cspecial']
  19. TYPE_C_INT = [ 'int', 'cint']
  20. TYPE_C_FLO = [ 'float', 'cfloat']
  21. TYPE_C_STR = [ 'string', 'cstring']
  22. TYPE_C_BOL = [ 'bool', 'cbool']
  23. TYPE_C_VEC = [ 'vector', 'cvector']
  24. TYPE_C_EXT = [ 'extra', 'cextra', 'iextra', 'ciextra']
  25. TYPE_C_EXI = [ 'iextra', 'ciextra']
  26. TYPE_C_TAB = [ 'table', 'ctable', 'itable', 'citable']
  27. TYPE_C_TAI = [ 'itable', 'citable']
  28. TYPE_C_SPE = [ 'special', 'cspecial']

  
  1. for i in range( 3, data[ 'row']+ 3):
  2. row_values_list = np.array(sheet.iloc[[i]]).tolist()[ 0]
  3. for j in range(data[ 'col']):
  4. data[ 'record'][(i -3, j)] = self.serialize(row_values_list[j], data[ 'type'][j])
  5. pass

  
  1. def serialize(self, data, type=None):
  2. if data != data:
  3. data = ''
  4. value = str(data)
  5. state = 0
  6. try:
  7. if type in TYPE_INT:
  8. value = int(value)
  9. elif type in TYPE_FLO:
  10. value = float( '%.3f' % float(value))
  11. elif type in TYPE_STR:
  12. value = str(value)
  13. elif type in TYPE_BOL:
  14. if data in [ 'false', 'False', 'FALSE', 0, 0.0]:
  15. value = False
  16. elif data in [ 'true', 'True', 'TRUE', 1, 1.0]:
  17. value = True
  18. else:
  19. value = 1
  20. state = 1
  21. elif type in TYPE_VEC:
  22. if not re.match( '^((true|false|-?\d+|".+"|-?\d+\.\d*),)*(true|false|-?\d+|".+"|-?\d+\.\d*)?$', value):
  23. state = 2
  24. elif type in TYPE_TAB:
  25. pass
  26. elif type in TYPE_EXT:
  27. pass
  28. elif type in TYPE_SPE:
  29. pass
  30. else:
  31. state = 2
  32. except ValueError:
  33. state = 2
  34. info = { 'data':value, 'state':state}
  35. return info

  
  1. for i in range(data[ 'row']):
  2. for j in range(data[ 'col']):
  3. table = data[ 'record'][(i, j)]
  4. item = QTableWidgetItem(str(table[ 'data']))
  5. item.setTextAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
  6. item.setBackground(self.setItemColor(table[ 'state']))
  7. self.ui.tableWidget.setItem(i, j, item)
  8. pass
  9. def setItemColor(self, state):
  10. if state == 1:
  11. return QtGui.QColor( 255, 255, 0)
  12. elif state == 2:
  13. return QtGui.QColor( 255, 0, 0)
  14. return QtGui.QColor( 255, 255, 255)

然后就是导出数据相关的设置了,在导出的过程中,首先将数据格式化成字典,然后将字典转化成所需的格式。这里可以加载外部函数,做到导出逻辑可分离模式;也可直接在程序中指定规则,不过在发布成程序后,定制性功能不好做而已。具体按照需求定制,其中,导出Json,可以使用Python的Json库,Lua则可以仿照Json书写。

将在下一篇文章中制作,这里就不做演示了。

本篇文章的最终完整代码如下:


  
  1. # coding:utf-8
  2. import sys
  3. import os
  4. import re
  5. import ui.dialog_login
  6. import ui.mainwindow_main
  7. import ui.ui_rc
  8. from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QTreeWidgetItem, qApp, QTableWidgetItem
  9. from PyQt5.QtCore import Qt
  10. from PyQt5 import QtCore, QtGui
  11. from PyQt5.QtSql import QSqlDatabase, QSqlQuery
  12. import pandas as pd
  13. import threading
  14. import numpy as np
  15. TYPE_INT = [ 'int', 'sint', 'cint', 'dint']
  16. TYPE_FLO = [ 'float', 'sfloat', 'cfloat', 'dfloat']
  17. TYPE_STR = [ 'string', 'sstring', 'cstring', 'dstring']
  18. TYPE_BOL = [ 'bool', 'sbool', 'cbool', 'dbool']
  19. TYPE_VEC = [ 'vector', 'svector', 'cvector', 'dvector']
  20. TYPE_EXT = [ 'extra', 'sextra', 'cextra', 'iextra', 'ciextra', 'dextra']
  21. TYPE_TAB = [ 'table', 'stable', 'ctable', 'itable', 'citable', 'dtable']
  22. TYPE_SPE = [ 'special', 'sspecial', 'cspecial', 'dspecial']
  23. TYPE_S = [ 'int', 'sint', 'float', 'sfloat', 'string', 'sstring', 'bool', 'sbool', 'vector', 'svector', 'extra', 'sextra', 'iextra', 'table', 'stable', 'itable', 'special', 'sspecial']
  24. TYPE_S_INT = [ 'int', 'sint']
  25. TYPE_S_FLO = [ 'float', 'sfloat']
  26. TYPE_S_STR = [ 'string', 'sstring']
  27. TYPE_S_BOL = [ 'bool', 'sbool']
  28. TYPE_S_VEC = [ 'vector', 'svector']
  29. TYPE_S_EXT = [ 'extra', 'sextra', 'iextra']
  30. TYPE_S_TAB = [ 'table', 'stable', 'itable']
  31. TYPE_S_SPE = [ 'special', 'sspecial']
  32. TYPE_C = [ 'int', 'cint', 'float', 'cfloat', 'string', 'cstring', 'bool', 'cbool', 'vector', 'cvector', 'extra', 'cextra', 'iextra', 'ciextra', 'table', 'ctable', 'itable', 'citable', 'special', 'cspecial']
  33. TYPE_C_INT = [ 'int', 'cint']
  34. TYPE_C_FLO = [ 'float', 'cfloat']
  35. TYPE_C_STR = [ 'string', 'cstring']
  36. TYPE_C_BOL = [ 'bool', 'cbool']
  37. TYPE_C_VEC = [ 'vector', 'cvector']
  38. TYPE_C_EXT = [ 'extra', 'cextra', 'iextra', 'ciextra']
  39. TYPE_C_EXI = [ 'iextra', 'ciextra']
  40. TYPE_C_TAB = [ 'table', 'ctable', 'itable', 'citable']
  41. TYPE_C_TAI = [ 'itable', 'citable']
  42. TYPE_C_SPE = [ 'special', 'cspecial']
  43. class DBManager():
  44. def __init__(self):
  45. self.db = QSqlDatabase.addDatabase( 'QSQLITE')
  46. self.db.setDatabaseName( './db/DataEditor.db')
  47. if not self.db.open():
  48. QMessageBox.critical( None, ( '无法打开数据库'), ( '提示'), QMessageBox.Cancel)
  49. return
  50. self.query = QSqlQuery()
  51. self.query.exec_( 'create table users (name varchar(30), password varchar(30))')
  52. def exec(self, sql):
  53. res = self.query.exec_(sql)
  54. pass
  55. class LoginDialog(QDialog):
  56. ok_signal = QtCore.pyqtSignal(str, str)
  57. cancel_signal = QtCore.pyqtSignal()
  58. def __init__(self, parent=None):
  59. super(QDialog, self).__init__(parent)
  60. self.ui = ui.dialog_login.Ui_dlg_login()
  61. self.ui.setupUi(self)
  62. self.setWindowFlags(Qt.FramelessWindowHint)
  63. self.setWindowModality(Qt.WindowModal)
  64. self.ui.btn_login.clicked.connect(self.ok)
  65. self.ui.btn_cancel.clicked.connect(qApp.quit)
  66. self.ui.btn_login.setDefault( True)
  67. def ok(self):
  68. account = self.ui.edt_account.text()
  69. password = self.ui.edt_password.text()
  70. if len(account) < 2 or len(password) < 6:
  71. QMessageBox.critical(self, ( '提示'), ( '请正确输入用户名或密码'), QMessageBox.Ok)
  72. return
  73. self.ok_signal.emit(self.ui.edt_account.text(), self.ui.edt_password.text())
  74. self.close()
  75. class ExcelData:
  76. def __init__(self, filepath):
  77. self.filepath = filepath
  78. self.sheets = {}
  79. self.finish = False
  80. # self.readSheet()
  81. self.read_thread = threading.Thread(target=self.readSheet, args={ 0})
  82. self.read_thread.start()
  83. def readSheet(self, name=0):
  84. if name in self.sheets:
  85. return self.sheets[name]
  86. sheet = pd.read_excel(self.filepath, sheet_name=name)
  87. data = {}
  88. data[ 'row'] = sheet.shape[ 0] - 3
  89. data[ 'col'] = sheet.shape[ 1]
  90. data[ 'title'] = sheet.columns.values.tolist()
  91. row_values_header = np.array(sheet.iloc[[ 0]]).tolist()[ 0]
  92. row_values_type = np.array(sheet.iloc[[ 1]]).tolist()[ 0]
  93. row_values_table = np.array(sheet.iloc[[ 2]]).tolist()[ 0]
  94. data[ 'header'] = []
  95. data[ 'type'] = []
  96. data[ 'table'] = []
  97. for i in range(data[ 'col']):
  98. data[ 'header'].append(row_values_header[i])
  99. data[ 'type'].append(row_values_type[i])
  100. data[ 'table'].append(row_values_table[i])
  101. pass
  102. for t in data[ 'table']:
  103. if t == t:
  104. self.readSheet(t)
  105. pass
  106. data[ 'record'] = {}
  107. for i in range( 3, data[ 'row']+ 3):
  108. row_values_list = np.array(sheet.iloc[[i]]).tolist()[ 0]
  109. for j in range(data[ 'col']):
  110. data[ 'record'][(i -3, j)] = self.serialize(row_values_list[j], data[ 'type'][j])
  111. pass
  112. self.sheets[name] = data
  113. self.finish = True
  114. def dispalyint(self, str):
  115. pass
  116. def serialize(self, data, type=None):
  117. if data != data:
  118. data = ''
  119. value = str(data)
  120. state = 0
  121. try:
  122. if type in TYPE_INT:
  123. value = int(value)
  124. elif type in TYPE_FLO:
  125. value = float( '%.3f' % float(value))
  126. elif type in TYPE_STR:
  127. value = str(value)
  128. elif type in TYPE_BOL:
  129. if data in [ 'false', 'False', 'FALSE', 0, 0.0]:
  130. value = False
  131. elif data in [ 'true', 'True', 'TRUE', 1, 1.0]:
  132. value = True
  133. else:
  134. value = 1
  135. state = 1
  136. elif type in TYPE_VEC:
  137. if not re.match( '^((true|false|-?\d+|".+"|-?\d+\.\d*),)*(true|false|-?\d+|".+"|-?\d+\.\d*)?$', value):
  138. state = 2
  139. elif type in TYPE_TAB:
  140. pass
  141. elif type in TYPE_EXT:
  142. pass
  143. elif type in TYPE_SPE:
  144. pass
  145. else:
  146. state = 2
  147. except ValueError:
  148. state = 2
  149. info = { 'data':value, 'state':state}
  150. return info
  151. class Application(QMainWindow):
  152. def __init__(self, parent=None):
  153. super(QMainWindow, self).__init__(parent)
  154. self.global_data = {}
  155. self.ui = ui.mainwindow_main.Ui_MainWindow()
  156. self.ui.setupUi(self)
  157. self.showMaximized()
  158. self.db = DBManager()
  159. # self.doLogin()
  160. self.readExcel()
  161. self.initHandle()
  162. def initHandle(self):
  163. self.ui.action_exit.triggered.connect(qApp.quit)
  164. self.ui.action_import.triggered.connect(self.importData)
  165. self.ui.action_export.triggered.connect(self.exportData)
  166. def exit(self):
  167. self.close()
  168. def doLogin(self):
  169. self.dlg_login = LoginDialog(self)
  170. self.dlg_login.show()
  171. self.dlg_login.ok_signal.connect(self.login)
  172. self.dlg_login.cancel_signal.connect(self.exit)
  173. def login(self, account, password):
  174. self.db.exec( "insert into users values('"+account+ "','"+password+ "')")
  175. def setTableWidgetData(self, qmodeLindex):
  176. name = self.ui.treeWidget.currentItem().text( 0)
  177. item = self.global_data[name]
  178. if not item.finish:
  179. QMessageBox.critical(self, ( '提示'), ( '正在加载中, 请稍后......'), QMessageBox.Ok)
  180. return
  181. data = item.sheets[ 0]
  182. self.ui.tableWidget.setRowCount(data[ 'row'])
  183. self.ui.tableWidget.setColumnCount(data[ 'col'])
  184. self.ui.tableWidget.setHorizontalHeaderLabels(data[ 'title'])
  185. for i in range(data[ 'row']):
  186. for j in range(data[ 'col']):
  187. table = data[ 'record'][(i, j)]
  188. item = QTableWidgetItem(str(table[ 'data']))
  189. item.setTextAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
  190. item.setBackground(self.setItemColor(table[ 'state']))
  191. self.ui.tableWidget.setItem(i, j, item)
  192. pass
  193. def setItemColor(self, state):
  194. if state == 1:
  195. return QtGui.QColor( 255, 255, 0)
  196. elif state == 2:
  197. return QtGui.QColor( 255, 0, 0)
  198. return QtGui.QColor( 255, 255, 255)
  199. def readExcel(self):
  200. self.ui.treeWidget.doubleClicked.connect(self.setTableWidgetData)
  201. self.ui.treeWidget.setColumnCount( 1)
  202. self.ui.treeWidget.setHeaderLabels([ 'Key'])
  203. for root, dirs, files in os.walk( 'excel'):
  204. for file in files:
  205. res = re.findall( '\\~\\$', file)
  206. if len(res) > 0:
  207. continue
  208. name = file.split( '.')[ 0]
  209. root = QTreeWidgetItem(self.ui.treeWidget)
  210. root.setText( 0, name)
  211. self.global_data[name] = ExcelData( 'excel/' + file)
  212. pass
  213. def importData(self):
  214. name = self.ui.treeWidget.currentItem().text( 0)
  215. item = self.global_data[name]
  216. if not item.finish:
  217. QMessageBox.critical(self, ( '提示'), ( '文件未加载完成, 请稍后......'), QMessageBox.Ok)
  218. return
  219. self.global_data[name] = ExcelData(item.filepath)
  220. pass
  221. def exportData(self):
  222. name = self.ui.treeWidget.currentItem().text( 0)
  223. item = self.global_data[name]
  224. if not item.finish:
  225. QMessageBox.critical(self, ( '提示'), ( '正在加载中, 请稍后......'), QMessageBox.Ok)
  226. return
  227. pass
  228. if __name__ == '__main__':
  229. app = QApplication(sys.argv)
  230. main = Application()
  231. main.show()
  232. sys.exit(app.exec_())

运行效果如下:

 


转载:https://blog.csdn.net/ww13055188466/article/details/115003202
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场