Skip to content

Latest commit

 

History

History
211 lines (135 loc) · 7.88 KB

File metadata and controls

211 lines (135 loc) · 7.88 KB

English | Chinese

PyAddin

pypi-version license

VBA日渐式微,但 Excel 依旧是表格数据存储、处理和传阅的通用工具。对于一些复杂的业务逻辑,一个带宏的 Excel 文件(xlsm)通常包含数据本身和处理数据的VBA脚本。当需要频繁复用这些脚本时,例如按相同的业务逻辑处理月度数据,建议将其拆分为两部分: Excel 纯数据文件(xlsx),以及一个负责数据处理的 Excel 插件(xlam)。

PyAddin是一个辅助创建上述 Excel 插件的命令行工具,同时支持使用 Python 语言来实现原本 VBA 负责的数据处理流程。两个基本功能如下:

  • 创建一个模板插件,支持自定义菜单功能区(Ribbon)和近“无缝”调用 Python 脚本。
  • 在开发插件过程中,方便根据自定义的CustomUI.xml更新插件菜单功能区。

集成 VBA 和 Python 的主要思路:VBA 通过后台运行控制台程序调用 Python 脚本,Python 脚本执行计算并以写临时文件的方式保存返回值,最后 VBA 读取返回值。此外,借助 Python 第三方库pywin32,可以直接在 Python 脚本中进行与 Excel 的交互,例如获取/设置单元格内容,设置单元格样式等等。

限制

  • 仅支持 Windows 平台
  • 要求 Microsoft Excel 2007 及以上
  • VBA 和 Python 之间传参仅限于 字符串 格式简单数据类型
  • 与 Excel 的交互能力取决于 pywin32/win32com

安装

支持 Pypi 或者本地安装:

# pypi
pip install pyaddin

# local  
python setup.py install

# local in development mode
python setup.py develop

使用 pip 卸载:

pip uninstall pyaddin

命令说明

  • 创建模板插件
pyaddin init --name=xxx --quiet=True|False
  • 更新插件功能区
pyaddin update --name=xxx --quiet=True|False

其中,quiet是可选参数,表明是否以后台模式创建插件(不显式打开 Excel)。默认值True,即后台模式。

使用帮助

1. 初始化模板插件

D:\WorkSpace>pyaddin init --name=sample

在当前目录下新建了文件夹sample,其中包含模板插件sample.xlam,以及实现VBA 与 Python 互联所需的支持文件。目录结构如下:

sample\
|- scripts\
|    |- utils\
|    |    |- __init__.py
|    |    |- context.py
|    |- __init__.py
|    |- sample.py
|- main.cfg
|- main.py
|- CustomUI.xml
|- sample.xlam

其中,

  • main.py是 VBA 调用 Python 脚本的入口文件。
  • main.cfg是基本配置参数文件,例如指定 Python 解释器的路径。
  • scripts存放处理具体业务的 Python 脚本,例如sample.py是其中的一个示意模块,开发者根据需要在此目录下创建其他模块。
  • CustomUI.xml定义了插件的 Ribbon 界面,例如包含的控件及样式。
  • sample.xlam为模板插件,开发者可以在此基础上添加和扩展自定义的功能。

当前模板的 Ribbon 区域参考下图。

add-in.png

2. 自定义 Ribbon 区域

在上一步创建的 CustomUI.xml 的基础上,根据具体需求设计界面和样式,然后运行以下命令将其更新到插件中去。当然, Excel 2007及以上的文件本质上是一个压缩文件,因此可以手动解压后替换相应的CustomUI.xml,或者直接借助其他的 Ribbon XML 编辑器进行修改。

D:\WorkSpace\sample>pyaddin update --name=sample

以此模板为例,定义了两个分组:

  • setting组提供基础功能,请直接保留在你的项目中。例如,运行前需要设置 Python 解释器路径。
  • Your Group 1是一个示例分组,其中设计了两个按钮。实际开发中请替换为需要的控件,或者增加其他更多分组。

关于 Ribbon 界面的具体介绍及格式规范,参考以下链接。

3. 实现 Ribbon 控件响应函数

这一步需要在 VBA 中实现 CustomUI.xml定义的控件事件及其响应函数。

以模板插件为例,下面的 XML 片段表明按钮Sample 1的点击事件由 VBA 过程CB_Sample_1响应。

<button id="sample1" label="Sample 1" 
		imageMso="AppointmentColor3" size="large" 
		onAction="CB_Sample_1"
...>

进一步,查看插件的UserRibbon模块中定义的两个响应过程:

Sub CB_Sample_1(control As IRibbonControl)
    '''onAction for control: Sample 1'''
    Dim res As Object
    Dim x As Integer: x = Range("A1").Value
    Dim y As Integer: y = Range("A2").Value
    
    Set res = RunPython("scripts.sample.run_example_1", x, y)
    Range("A3") = res("value")    
End Sub

Sub CB_Sample_2(control As IRibbonControl)
    '''onAction for control: Sample 2'''
    RunPython "scripts.sample.run_example_2"
End Sub

可以看到,二者都是通过RunPython()函数来调用 Python 脚本并获取返回值,函数签名如下:

Function RunPython(methodName As String, ParamArray args()) As Object
  • 第一个参数methodName表示调用的 Python 方法,具体格式为package.module.method。本例 "scripts.sample.run_example_1" 表示调用脚本sample/scripts/sample.py中的run_example_1方法。

  • 第二个参数args是可变长参数,可以传入任意个数的参数给相应 Python 方法。注意,仅支持字符串、数字等简单数据类型,并且经过控制台参数传递,最终到 Python 端都被转成了字符串格式。

  • 返回值为 VBA 的Dictionary格式,其中包含两个键:

    • status: 调用成功与否,True 或者 False
    • value: 返回值(错误信息如果status为 False)

4. 实现 Python 脚本

根据 VBA 端RunPython()调用路径和参数列表,创建相应的 Python 脚本文件及函数。

以模板插件为例,在项目目录下查看scripts\sample.py文件:

# sample.py
from .utils import context

def run_example_1(x:str, y:str):
    return int(x) + int(y)

def run_example_2():
    # get the workbook calling this method, then do anything with win32com
    wb = context.get_caller()
    sheet = wb.ActiveSheet

    # get cells value
    x = sheet.Range('A1').Value
    y = sheet.Range('A2').Value

    # set cell value
    sheet.Range('A3').Value = x + y

根据上下文可知,上述两个方法实现相同的事情,将单元格 A1、A2 的和写到单元格 A3,进一步可以分为三步:取值,求和,设置值。但实现的思路略有不同:

  • run_example_1的思路是仅将 Excel 无关的流程(求和)交由 Python 实现,所有 Excel 相关的操作(取值、设置值)仍通过 VBA 执行,二者的桥梁是参数传递。

  • run_example_2的思路是在 Python 端获取到调用此脚本的工作簿,然后直接对其做所有需要的操作(取值,求和,设置值)。

对比可以发现,前者按各自强项分工,运行效率较高,但 VBA 和 Python 之间存在强耦合,且容易受限于参数传递的复杂度;后者几乎完全解耦 VBA 和 Python,且避免了参数传递,但操作 Excel 的能力受限于pywin32,某些操作可能无法实现。

5. 交付插件

因为依赖关系,整个工程应作为整体(第一步中所示结构)交付,且需要最终用户配置好 Python 环境及相应第三方库(主要是pywin32)。有时候 Python 环境对用户来说是一个挑战,因此可以考虑可移植的便携式 Python,事先安装好依赖库后随插件工程一起发布,如此便于最终用户开箱即用。

许可

MIT License