python 快速实践之:tkinker openpyxl实例分析

先说背景:女朋友做审计工作中需要批量制作大量的统计表,统计表的数据来自于一个整体汇总表,按照一定的规则进行重复工作,为了帮助他们快速交付任务,设计了这个工具。界面设计如下:

本博客主要技术点在于解决以下问题:

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()


  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值