先说背景:女朋友做审计工作中需要批量制作大量的统计表,统计表的数据来自于一个整体汇总表,按照一定的规则进行重复工作,为了帮助他们快速交付任务,设计了这个工具。界面设计如下:
1. openpyxl操作Excel的方法调用
2. tkinker界面的布置和command方法的实现规则
3. 业务层:识别有效Excel数据并获取有效Excel表格名称
结合代码总结用法:
1. 用openpyxl操作Excel:
这个模块这一在这里下载然后pip 安装
也可以试试这个共享链接:
首先引入模块:
import openpyxl as pl
读取方法:
wb = pl.load_workbook(self.fileName) # 载入文件
names = wb.get_sheet_names() # 得到SHeet名称列表
table = wb.get_sheet_by_name(names[0]) # 得到根据sheet名得到表格
#按列数找到需要匹配的列
for rowNo in range(2, table.max_row):
tampList = []
for colNo in range(2, table.max_column):
tampList.append(table.cell(rowNo + 1, colNo + 1).value) # .cell(int, int).value 读取单元格的值
newList = isValidRow(tampList)
if len(newList) > 0:
self.buildSingleItem(table.cell(rowNo, 1).value, newList)
还可以通过table['A2']的方法直接得到单元格的值
和xlrd与xlwt不同,使用这个模块可以直接在load模式下修改内容,不再是只读模式只能读,只写模式只能写的尴尬情况了。写的方法如下:
wb = pl.load_workbook('DataModel.xlsx')
# copydata
names = wb.get_sheet_names()
table = wb.get_sheet_by_name(names[0])
tempTitle = tripStr(title.strip()) + u'统计表'
table['A1'] = tempTitle # 直接给单元格赋值就可以写入
也可以通过访问Cell写入:
for row_cell in table['D5':'E35']: # 遍历一个单元格范围得到的是一行的单元格List
count += 1
if count < len(valueList) + 1:
row_cell[0].value = self.CopsName[count-1] # 拿到单元格,直接赋值其value即可
row_cell[1].value = valueList[count-1]
最后一步:保存
wb.save(u'Store/'+ tempTitle + '.xlsx')
2. tkinker界面和函数访问方法
from tkinker import *
root = Tk() # 主事件循环
root.title(u'快速生成分项统计表--Jessica') # 主循环上设置标题
root.resizable(False, False) # 设置大小
mainFrame = Frame(root) # 增加Frame
# 提示信息先出现
frmH1 = Frame(mainFrame)
lbTip = Label(frmH1, text=u'*****提示:首次使用软件请务必了解使用方法***** ') # Label控件
btnTip = Button(frmH1, text=u'点击了解', fg='red', relief=GROOVE) # Button控件
lbTip.pack(side=LEFT) # 部署控件
btnTip.pack(side=RIGHT)
frmH1.pack() # 部署frame
mainFrame.pack()
root.mainloop() # 进入时间循环
tkinker是自带的库,直接导入就行。要注意所有的事件都要发布才能执行。下面就是添加函数:
paraList = [varAcc, varAccDate, varreAcc, varreAccData, varCop, varDate]
btnOK.bind('<Button-1>', doCopyTask)
def doCopyTask(args):
newTask = lineCopyTask(buildParaList(paraList))
newTask.clearStoreFloder()
if newTask.doCopyToSheets():
messagebox.showinfo(title="Succeed!", message=u'成功生成所有文件,请在Store文件夹下查看结果!')
由于平时主要是C++开发,这里给btn控件挂接函数陷入了谜团:没有指针,我如何在其作用域之外访问界面上的控件或者给控件传递参数呢?这里才见识到python作为动态语言的强大之处!直接把参数构造好放在UI建立的函数里,其绑定的函数就可以直接访问参数了!
按照C++的套路,可以理解为所有被绑定的回调函数都是这个界面的Friend函数,所有界面的参数对这个函数来说都是可以访问的!如此就顺利完成了界面的逻辑添加和布局。
3. 业务实现:如果对具体业务不敢兴趣,以下内容可以不看了。
汇总表是下面的格式:
需要实现两个判断:
(1)绿色部分的数据是不是全为空或者为0,只要有数据就需要统计此行;
(2)统计此行需要生产新的EXCLE,文件名为左侧的文字,需要删除多余的空格和特殊符号
空行判断:
def isValidRow(valueList):
# 判断是不是有效行
newList = []
isValue = False
for item in valueList:
try:
newList.append(float(item))
if float(item) > 0:
isValue = True
except:
return []
if isValue:
return newList
else:
return []
名称过滤:(如:“其他:分布分析费用”则显示为:“分布分析费用”等,需要去除多余信息)
思路:多余信息或者为特殊字符,或者以特殊字符分割了整个名称,而有效信息在去除空格后是长度最长的,以此为特征,实现了这个算法设计。
def tripStr(raw_str):
listBrokePoints = []
position = -1
raw_str_copy = raw_str
hasslash = False
for s in raw_str:
# 不是中文则为切割点
position += 1
if s >= u'\u4e00' and s <= u'\u9fa6':
if s == u':' or s == u'、' or s == u'*' or s == u'△':
listBrokePoints.append(position)
elif s == '(' or s == u'(' or s == u')' or s == ')' or s == u' ' or s == '-' or s == '-' or s == u'“' or s == u'”':
continue
elif s == '/':
hasslash = True
continue
else:
listBrokePoints.append(position)
# 得到每个切割结果
listSubStr = []
lastP = 0
for p in listBrokePoints:
p = p - lastP
listSubStr.append(raw_str_copy[:p])
raw_str_copy = raw_str_copy[p+1:]
lastP += p
listSubStr.append(raw_str_copy)
# 求最长
lenth = 0
reStr = ''
for subStr in listSubStr:
tmpLen = len(subStr)
if tmpLen > lenth:
lenth = tmpLen
reStr = subStr
if hasslash:
reStr = reStr.replace('/', '&')
return reStr.strip()
解决以上问题后,加入Log模块,然后加上异常处理,基本上就可以用了,下面是全部代码
from tkinter import *
from tkinter import messagebox
import openpyxl as pl
from mylog import MyLog
from tkinter.filedialog import *
import UI
import os
def isValidRow(valueList):
# 判断是不是有效行
newList = []
isValue = False
for item in valueList:
try:
newList.append(float(item))
if float(item) > 0:
isValue = True
except:
return []
if isValue:
return newList
else:
return []
def tripStr(raw_str):
listBrokePoints = []
position = -1
raw_str_copy = raw_str
hasslash = False
for s in raw_str:
# 不是中文则为切割点
position += 1
if s >= u'\u4e00' and s <= u'\u9fa6':
if s == u':' or s == u'、' or s == u'*' or s == u'△':
listBrokePoints.append(position)
elif s == '(' or s == u'(' or s == u')' or s == ')' or s == u' ' or s == '-' or s == '-' or s == u'“' or s == u'”':
continue
elif s == '/':
hasslash = True
continue
else:
listBrokePoints.append(position)
# 得到每个切割结果
listSubStr = []
lastP = 0
for p in listBrokePoints:
p = p - lastP
listSubStr.append(raw_str_copy[:p])
raw_str_copy = raw_str_copy[p+1:]
lastP += p
listSubStr.append(raw_str_copy)
# 求最长
lenth = 0
reStr = ''
for subStr in listSubStr:
tmpLen = len(subStr)
if tmpLen > lenth:
lenth = tmpLen
reStr = subStr
if hasslash:
reStr = reStr.replace('/', '&')
return reStr.strip()
# for Controller
def showTip(args):
ui = UI.TiplUI()
def buildParaList(widgetList):
strList = []
for item in widgetList:
strList.append(item.get())
return strList
def doCopyTask(args):
newTask = lineCopyTask(buildParaList(paraList))
newTask.clearStoreFloder()
if newTask.doCopyToSheets():
messagebox.showinfo(title="Succeed!", message=u'成功生成所有文件,请在Store文件夹下查看结果!')
class lineCopyTask():
def __init__(self, paraList):
self.xlsLeage = False
self.lineName = ''
self.log = MyLog()
self.fileName = self.getFileName()
self.CopsName = []
self.NameCount = 0
self.getCopNames()
self.Acc = paraList[0]
self.reAcc = paraList[2]
self.date = paraList[5]
self.coName = paraList[4]
self.accDate = paraList[1]
self.reaccDate = paraList[3]
def getFileName(self):
file = askopenfilename(initialdir='D:/')
return file
def getCopNames(self):
self.log.info('Enter get Cops....')
if len(self.fileName) < 0:
self.xlsLeage = False
return
try:
wb = pl.load_workbook(self.fileName)
self.NameCount = 0
names = wb.get_sheet_names()
table = wb.get_sheet_by_name(names[0])
colCount = table.max_column
for i in range(2, colCount):
tempName = table.cell(1, i + 1).value
self.CopsName.append(tempName)
self.log.info(u'\n获取:'+ tempName)
self.NameCount += 1
self.log.info(u'\n获取分公司名称完成')
self.xlsLeage = True
except:
self.xlsLeage = False
messagebox.showinfo('ERROR', u"汇总表格式非法!")
def buildSingleItem(self, title, valueList):
# build new xls file
wb = pl.load_workbook('DataModel.xlsx')
# copydata
names = wb.get_sheet_names()
table = wb.get_sheet_by_name(names[0])
tempTitle = tripStr(title.strip()) + u'统计表'
table['A1'] = tempTitle
table['E2'] = self.Acc
table['E3'] = self.reAcc
table['G2'] = self.accDate
table['G3'] = self.reaccDate
table['A2'] = self.coName
table['B3'] = self.date
count = 0
for row_cell in table['D5':'E35']:
count += 1
if count < len(valueList) + 1:
row_cell[0].value = self.CopsName[count-1]
row_cell[1].value = valueList[count-1]
# build infos
wb.save(u'Store/'+ tempTitle + '.xlsx')
self.log.info(tempTitle + " done!")
def doCopyToSheets(self):
if not self.xlsLeage:
return False
wb = pl.load_workbook(self.fileName)
names = wb.get_sheet_names()
table = wb.get_sheet_by_name(names[0])
#按列数找到需要匹配的列
for rowNo in range(2, table.max_row):
tampList = []
for colNo in range(2, table.max_column):
tampList.append(table.cell(rowNo + 1, colNo + 1).value)
newList = isValidRow(tampList)
if len(newList) > 0:
print(newList)
self.buildSingleItem(table.cell(rowNo, 1).value, newList)
return True
def clearStoreFloder(self):
if os.path.exists('Store'):
# clear
for item in os.listdir('Store'):
file = os.path.join('Store', item)
os.remove(file)
else:
# new floder
os.mkdir('Store')
if __name__ == '__main__':
root = Tk()
root.title(u'快速生成分项统计表--Jessica')
root.resizable(False, False)
mainFrame = Frame(root)
# 提示信息先出现
frmH1 = Frame(mainFrame)
lbTip = Label(frmH1, text=u'*****提示:首次使用软件请务必了解使用方法***** ')
btnTip = Button(frmH1, text=u'点击了解', fg='red', relief=GROOVE)
lbTip.pack(side=LEFT)
btnTip.pack(side=RIGHT)
frmH1.pack()
# 审核人:
frmH2 = Frame(mainFrame)
lbAcc = Label(frmH2, text=u'审核人:')
varAcc = StringVar()
etAcc = Entry(frmH2, textvariable=varAcc)
lbAcc.pack(side=LEFT)
etAcc.pack(side=LEFT)
# 审核时间
lbAccData = Label(frmH2, text=u'审核时间:')
varAccDate = StringVar()
etAcc = Entry(frmH2, textvariable= varAccDate)
lbAccData.pack(side=LEFT)
etAcc.pack(side=RIGHT)
frmH2.pack()
# 复审人:
frmH3 = Frame(mainFrame)
lbreAcc = Label(frmH3, text=u'复审人:')
varreAcc = StringVar()
etreAcc = Entry(frmH3, textvariable = varreAcc)
lbreAcc.pack(side=LEFT)
etreAcc.pack(side=LEFT)
# 复审时间:
lbreAccData = Label(frmH3, text=u'复审时间:')
varreAccData = StringVar()
etreAcc = Entry(frmH3, textvariable = varreAccData)
lbreAccData.pack(side=LEFT)
etreAcc.pack(side=RIGHT)
frmH3.pack()
# 被审单位
frmH4 = Frame(mainFrame)
lbCorp = Label(frmH4, text=u'被审核单位: ')
varCop = StringVar()
etCorp = Entry(frmH4, width=46, textvariable = varCop)
lbCorp.pack(side=LEFT)
etCorp.pack(side=LEFT)
frmH4.pack()
# 被审时间
frmH5 = Frame(mainFrame)
lbCorpDate = Label(frmH5, text=u'被审核时间: ')
varDate = StringVar()
etCorpDate = Entry(frmH5, width=46, textvariable = varDate)
lbCorpDate.pack(side=LEFT)
etCorpDate.pack(side=LEFT)
frmH5.pack()
# 确认执行
frmH6 = Frame(mainFrame)
btnOK = Button(frmH6, text=u'数据无误,立即生成', relief=RIDGE, bg='green', font='黑体', fg='white')
btnOK.pack()
frmH6.pack()
# 添加Controll
paraList = [varAcc, varAccDate, varreAcc, varreAccData, varCop, varDate]
btnTip.bind('<Button-1>', showTip)
btnOK.bind('<Button-1>', doCopyTask)
mainFrame.pack()
root.mainloop()