基于python win32com的visio文件基础操作语句简介及案例展示(家族树自动创建)

1 写在前面

最近需要给老师们做家族树,向visio模板中套excel数据,但是在手动套模板的过程中发现间距的控制是一个较为繁琐的事情,于是萌生了用python完成这一过程的想法。
在探索的过程中经验性地总结了一些东西,这里记录分享给大家。
后续文章分为三个部分:首先是现有使用python操作visio文件的方法简介和资源汇总;其次是个人在使用win32com操作visio过程中总结整理得到的一些方法和经验;最后是一个家族树示例的创建案例。
案例相关的程序和文件可以从https://github.com/M73ACat/python-visio-familyTree获取。

2 方法调研

大概了解了一下,使用python控制visio有这么两类方案:
第一类为通过python的win32com操作visio程序;
第二类则通过xml操作并创建visio文件。
相较之下,个人感觉通过win32com操作visio程序的方法在网络上有更多的相关资料且方案较成熟,而第二类方法资料十分少而且需要对xml有一定的了解。在简单尝试两类方案后,还是选择了使用win32com操作visio程序的方案。
这里简单记录一下两类方案的相关介绍、资料等,以供参考。

2.1 win32com操作visio

2.1.1 简单介绍和示例

从使用体验上来讲,使用win32com来操作visio像是通过python打开visio程序和相应文件,通过特定的语句控制visio程序对特定文件进行更改,以满足文件操作需求。
通过使用下列语句,可以打开visio程序并打开“source.vsdx”文件,并显示对应窗口,其效果见图1(窗口可以隐藏)。

import win32com.client as win32
visio = win32.gencache.EnsureDispatch("Visio.Application")
vdoc = visio.Documents.Open("source.vsdx")
图1

2.1.2 一些资料

pywin32/win32com的官方文档应该是这个https://mhammond.github.io/pywin32/html/com/win32com/HTML/docindex.html,但可能是个人水平不足,难以从该文档中获取到什么有帮助性的指引,所以还是转向了零散的资料。

  1. 首先是知乎博主小雨的专栏https://www.zhihu.com/column/c_1362546109030723584,专栏中的几篇文章提供了一些基础语法和简单的示例;
  2. 其次是csdn文章https://blog.csdn.net/q774318039a/article/details/128082172,提供了一个较为完整的案例,当然相关的介绍还是不太丰富;
  3. github资源https://github.com/john-bessire/PythonVisioFlowchart,提供了一个更加丰富和完整的案例。这里还是要挂一个csdn资源(链接:https://download.csdn.net/download/weixin_42157188/18540786),题为“PythonVisioFlowchart:使用Python创建Visio流程图”,该资源照搬了该github资源并设置为收费。不过在一定程度上要感谢这个csdn,让我能够在github上找到该案例;
  4. https://www.coder.work/article/1260877,给出了一些属性设置的示例。

总的来说,网络上的这些资源都较为零散且不太全面。

2.2 visiopy(通过操作xml操作visio)

该方案来自于github https://github.com/thiezn/visiopy,其大概操作过程和原理为:

  1. 更改vsdx文件拓展名为rar等压缩文件格式拓展名;
  2. 解压缩后可以得到一些文件,其中包含了许多xml文件、rels文件以及visio中包含的媒体文件。解压缩后文件夹结构和部分document.xml示例如图2所示(visio文件见图1),个人推测visio通过这样的xml文件控制着每个元素的属性;
  3. 将相应的文件压缩为zip格式(此过程中可以通过更改xml文件实现visio操作);
  4. 将文件拓展名改回vsdx,文件可以正常打开,并实现visio操作。该过程的流程图见图3。(注:在压缩时rar格式更改后缀名为vsdx后无法打开,zip格式可以)
图2
图3
就个人感觉而言,该方案是一种较为巧妙的方案,但是相关的资料仅有作者提供的源码和简单示例。而且简单翻阅其源代码发现目前提供的可修改参数较少(见pages文件PageCollection.to_xml)。所以如果采用该方案可能还需要在源程序的基础上进行拓展,这又需要对visio解压后得到的xml文件有一定的了解,加之没有什么参考资料,于是该方案在简单尝试后被暂时放弃了。

3 一些基础操作

上一部分简单介绍并分析了两个方案,最终选择了基于win32com的visio文件操作方案,这里介绍一些探索到的操作和总结得到的经验。

3.1 visio中的两个有用工具

首先需要介绍一下两个很有用的visio中的工具:绘图资源管理器和ShapeSheet。

3.1.1 绘图资源管理器

绘图资源管理器的打开方式见图4,打开之后的界面如图5所示。

图4
图5
如图5的绘图资源管理器所示,前景页-> 页名->形状 栏目下列出了当前页面下所有的元素名称,点击相应形状,visio页面中对应的元素会呈现被选中的状态。 该工具可以帮助定位到目标对象/元素/形状,从而方便在程序中调用。

3.1.2 ShapeSheet

ShapeSheet的打开方式如图6所示,在某个对象/元素/形状上点击鼠标右键,在弹出的列表中选择“显示ShapeSheet”,即可打开该形状的ShapeSheet。打开后的该元素的ShapeSheet如图7所示。

图6
图7
在图7所示的ShapeSheet中,可以观察到包含了该矩形框的形状(Shape Transform)、线条格式(Line Format)、填充格式(Fill Format)、字体格式(Character)、段落(Paragraph)等等所有属性。 我们通过python和win32com在程序中修改某一形状的属性,最终还是会反映到visio中该形状的ShapeSheet中,当然在visio中直接修改ShapeSheet也是可以的。 该工具一方面可以告诉我们一个形状在程序中有哪些可以修改、调用的属性,另一方面也可以供我们参考这些属性对应的值。当然不同属性的修改、调用方法有所不同。

3.2 文件操作

3.2.1 基础文件操作

打开visio程序

import win32com.client as win32
visio = win32.gencache.EnsureDispatch("Visio.Application")

设置窗口是否可见,0不可见,1可见,不设置则默认为1
【注意】设置窗口不可见的话,需要通过程序关闭文件和visio程序,或在任务管理器找到后台的visio程序结束进程

visio.Visible = 0

打开文件

vdoc = visio.Documents.Open("source.vsdx")

选择页面

page = vdoc.Pages.Item(1)

文件另存为(当然也可直接在visio窗口中保存当前文件)

vdoc.SaveAs("familyTree.vsdx")

关闭打开的文件

vdoc.Close()

关闭visio程序

visio.Quit()

3.2.2 模板选择与打开

Visio的模板/模具保存在C:\Program Files\Microsoft Office\root\Office16\Visio Content\2052目录下(2019专业版),通过以下命令,我们可以打开连接符相关的模板:

con = visio.Documents.Open("CONNEC_M.VSSX")

在打开相应模板后,可以通过Drop指令向页面内拖入指定形状,相关程序如下:

con_shp = page.Drop(con.Masters.ItemU('Dynamic connector'),0,0)

其中’Dynamic connector’为指定形状的名称,后续的两个参数为页面内的坐标,其单位默认为英尺,支持数字和字符串,但貌似无法更改单位。该行程序会返回所拖入的图形,后续可以更改con_shp参数以调整其位置、格式等(见3.3)。
可能会遇到的问题是:不知道想拖入形状的名称是什么。这里有一种略微麻烦一些的方法,我们在visio中打开一个形状模板/模具时会显示相应的名称,如图8所示。通过con.Masters.GetNamesU()可以获取con即连接线模板中所有形状的英文名称列表,如果害怕理解的不对的话可以通过con.Masters.GetNames()获取con中形状的中文名称列表,三者相对应着就可以挑到想要的形状了。
当然也可以通过con.Masters(1)来得到该模板中的第一个shape。

图8

3.3 内容操作

这部分具体介绍一下如何对每个形状进行操作,包括形状信息的查看、特定形状的选中、复制、粘贴、线型、字体、添加连接线、连接线各拐点的修改等
以下程序需要前置程序如下:

import win32com.client as win32
visio = win32.gencache.EnsureDispatch("Visio.Application")
vdoc = visio.Documents.Open("source.vsdx")
page = vdoc.Pages.Item(1)

3.3.1 形状的查看与选择

查看页面元素数量:

page.Shapes.Count 
# 或
page.Shapes.Count16

page.Shapes()中存储着page页面中的每个形状,可以通过以下程序查看每个shape的信息:

for num, shp in enumerate(page.Shapes):
    print('num: %s, name: %s, nameID: %s'%(num, shp.Name, shp.NameID))

其中,shp.NameU/Name均为查看形状名称的操作,所得到的名称与3.1.1节中介绍的绘图资源管理器中的名称相对应
shp.NameU = ‘1’ 可直接更改名称(Name和NameU修改一个即可),有些时候因为在visio中创建并删除了很多图形,所以最终图形名称的编号较为混乱,可以自行修改名称,例:

for num, shp in enumerate(page.Shapes):
    shp.Name = num + 1
    print('num: %s, name: %s, nameID: %s'%(num, shp.Name, shp.NameID))

NameID为shape的唯一ID,只能查看不能修改
借由绘图资源管理器中相应的名称(当然也可以辅以其他手段比方说shape.Text查看该shape中的文字来确定的shape),针对特定的shape,可以通过该shape在page.Shapes中的顺序或NameID来进行选择:

header_shape = page.Shapes(1) # 选择page.Shapes中的第一个shape 【注意,Shapes中的第一个shape从1开始】
header_shape = page.Shapes.ItemU('Sheet.10') # 选择page.Shapes中NameID为'Sheet.10'的shape

3.3.2 复制与粘贴

我们首先获取一个shape:shp = page.Shapes(1),shp的复制与粘贴有两种方法:
方法一,通过调用系统的剪贴板实现粘贴复制:

shp.Copy()
page.Paste() # 参数只支持0、1、2,貌似为粘贴得到图形的初始位置,1为左下角

这种方法无法直接返回粘贴得到的新shape,可以通过shp_copy = page.Shapes(-1)来得到粘贴后的新shape
2. 方法二,不通过剪贴板,可以直接获得粘贴后的新shape,有些类似于在visio中直接进行ctrl+D:

shp_copy = shp.Duplicate()

3.3.3 参数的查看和修改

我们首先获取一个新的shape,该shape如图9所示:

time_shp = page.Shapes(2)
图9
一个较为基本的操作是,获取该shape上的文字,对其进行修改:
time_shp.Text
time_shp.Text = '2020级'

除此之外,对time_shp参数的大部分操作都需要使用到time_shp.CellsU()来实现,我们以该shape的位置参数(x方向中心点PinX和y方向中心点PinY)为例,展示如何进行参数的查看和修改:

pinx = time_shp.CellsU('pinx').ResultIU # 返回pinx值,数字;“pinx“为x方向中心点的参数名称
piny = time_shp.CellsU('piny').ResultIU # 返回piny值,数字;“piny“为y方向中心点的参数名称
time_shp.CellsU('pinx').FormulaU = '10 mm' # 修改time_shp的中心点x坐标为10 mm
time_shp.CellsU('piny').FormulaU = '250 mm' # 修改time_shp的中心点y坐标为250 mm,从而实现对time_shp的移动

一些注意事项:

  1. 返回值的默认单位为英尺;
  2. CellsU()括号中的参数名称对大小写不敏感;
  3. 在赋值时可以指定单位。

上述程序以PinX和PinY两个参数为例介绍了shape的获取和修改操作,但一个显而易见的问题是,我们不知道一个shape有多少个参数,我们想修改的参数对应的名称是什么。
这里需要引入3.1.2节介绍的工具ShapeSheet。该工具中列出了某一shape的所有参数和名称。其中大部分常用参数的名称都是对应的,可以直接复制粘贴到CellsU()中进行操作,如:

CellsU('width') # 宽度
CellsU('height') # 高度
CellsU('rounding') # 圆角
CellsU('lineweight') # 线宽
CellsU('linecolor') # 线颜色

除了借鉴参数的名称之外,ShapeSheet中所列出的参数值也是可以借鉴的。比方说我们可以参照visio参数的样例以确定参数的赋值方法,或者通过在visio中修改相应的参数值来定位ShapeSheet中的变化值,从而确定相应的参数意义及相应参数值的意义,例:

  1. 在修改填充颜色时,参考ShapeSheet中相应区域的取值(见图10),我们可以直接搬进程序中:
图10
time_shp.CellsU('FillForegnd').FormulaU = 'RGB(0,0,0)' # 调整shape填充颜色为黑色
  1. 在修改线型时,参考相应区域(见图11),我们可以尝试修改其取值,或在 开始中修改其线型,便可以知道特定参数值的意义。
图11

然而并非所有参数名称和ShapeSheet中列出的名称是一一对应的。笔者在使用过程中发现“Character”(图12)和连接线的“Geometry”(图13)这两部分参数的操作略有不同。这里将连接线的“Geometry”放在3.3.4进行介绍,本节只介绍“Character”的操作。

图12
图13

一部分修改字体的程序示例如下:

time_shp.CellsU('char.size').FormulaU = '10 pt' # 修改文字大小
time_shp.CellsU('char.font').FormulaU = '20' # 修改字体

需要注意的是,通过char.size/font等参数会统一修改shape中的所有文字,不支持部分修改。目前尚不知道如何直接在程序中进行部分修改。当然,还是有一些间接一些的方法:

  1. 方法一:https://www.coder.work/article/126087中给出了获取shape.Characters并部分调整文字格式的方法和相应程序,但貌似有些复杂;
  2. 方法二:笔者在测试中,发现char支持x1……xn、y1……yn、z等等调用方法,其中x1-n为不同部分的字体,从而可以实现分部分的字体调控【注意1,尚无法实现通过程序将而文字分为不同部分,只能先在visio中完成划分后进行调整;注意2,仅发现x对应font,其他字母间的对应关系尚未发现】。例:
time_shp.CellsU('char.x1').FormulaU = '15'
time_shp.CellsU('char.x2').FormulaU = '30'
图14

另外,个人推测可能这类以表格形式存储的属性(类似的还有段落属性)具有类似的操作方法。
经过上述介绍,相信大家也能发现shape参数的调整是一个比较繁琐的工作,我个人更倾向于在文件中建立并设置好相应的所需图形,在程序中使用一些复制粘贴操作,只需调整位置从而减少查找和调整参数的时间和难度。

3.3.4 连接线操作

3.3.4.1 连接线创建

创建连接线并连接两个形状有许多种方式,笔者在此将连接线连接的方法简单分为三类(以上文中得到的header_shp和time_shp为例):

  1. 使用shape.AutoConnect自动将两个shape通过连接线相连。例:
time_shp.AutoConnect(header_shp, constants.visAutoConnectDirRight, con.Masters.ItemU("Dynamic connector"))

其中,constants.visAutoConnectDirRight控制了连接线的方向,已知的四个方向分别为Right、Left、Up和Down,但该方法会自动调整两个相连形状的位置,其结果如图15所示。更多的资料可以访问博主小雨的专栏(https://zhuanlan.zhihu.com/c_1362546109030723584)。

图15
  1. 通过GlueTo设置连接线的起始点。例:
conn_shp = page.Drop(visio.Application.ConnectorToolDataObject, 0, 0)
conn_shp.CellsU("BeginX").GlueTo(header_shp.CellsU("PinX"))
conn_shp.CellsU("EndY").GlueTo(time_shp.CellsU("PinY"))

该写法参考自https://github.com/john-bessire/PythonVisioFlowchart,其中conn_shp同样可以采用从模板中选择并Drop的方式(所得到的结果见图16):

conn_shp = page.Drop(con.Masters(1), 0, 0)
图16
  1. 通过手动设置起始点等属性调整其在页面上的样式。例:
conn_shp = page.Shapes(5).Duplicate() # 复制现有连接线
conn_shp.CellsU("BeginX").FormulaU = header_shp.CellsU("PinX").ResultIU # 设置连接线起点x为header_shp的PinX
conn_shp.CellsU("EndX").FormulaU = time_shp.CellsU("PinX").ResultIU # 设置连接线终点x为time_shp的PinX
conn_shp.CellsU("BeginY").FormulaU = header_shp.CellsU("PinY").ResultIU - header_shp.CellsU("Height").ResultIU / 2 # 设置连接线起点y为header_shp底边的中点
conn_shp.CellsU("EndY").FormulaU = time_shp.CellsU("PinY").ResultIU + time_shp.CellsU("Height").ResultIU / 2 # 设置连接线终点y为time_shp顶边的中点

上述程序复制了现有的连接线并手动将其起始点定位到相应的位置。同时,复制还保证了连接线之间具备相同的样式,其中最主要的一条就是控制了连接线的走向,在图17所示的结果图中可以看到,复制之后的连接线依然保持着两次转折的样式。在所使用的家族树案例中,这可以保证绘图整体的样式统一。

图17

连接线的一般创建方式与其他shape的创建相通,Drop或者复制均可。当然上面三个例子中也给出了此外的两类连接线创建方法,可供参考。

3.3.4.2 连接线调整

通过设定起始点/连接对象,连接线的位置便不需要再手动调整了。此外的颜色、线型、线宽等常规属性与其他shape的属性设置相通,见3.3.3节。然而连接线还有一个较为重要且较不常规的属性“Geometry”,3.3.4.1节例3(图17)新得到的连接线的ShapeSheet如图18所示。其中“Geometry 1”一栏包含着连接线每个点的相对位置。我们可以通过调整每个点的相对位置来进一步修正连接线的样式。

图18

一个简单的例子是(接3.3.4.1节例3):

conn_shp.CellsU("Geometry1.Y2").FormulaU = '1 mm'
conn_shp.CellsU("Geometry1.Y3").FormulaU = '1 mm'
图19

图19为调整第2、3行y值前后的对比图,通过这种方式,我们就可以统一所有连接线的样式。需要说明的是,暂未明晰如何在程序中新增或删去连接线上的点,仍然需要手动在visio中建立特定样式的连接线。

4 案例

到此为止,关于一些基本操作的介绍就完成了。下面就回到正题,完成家族树自动创建的程序。完整案例(程序和文件)可以从github获取(https://github.com/M73ACat/python-visio-familyTree),这里简单分享一下笔者的实现思路和最终的效果。

  1. 首先在“source.vsdx”中创建并手动调整各基础形状的样式,其结果见图1;
  2. 查看并调整各形状的名称,对照绘图资源管理器定位各基础形状;
  3. 根据所确定的间距,通过复制粘贴的方式保证形状的样式,通过PinX和PinY调整形状的位置,通过BeginX、BeginY、EndX、EndY、Geometry调整连接线的起始点和中间点的位置;
  4. 从“信息.xlsx”中获取信息并执行第3步;
  5. 另存为新文件。
    其过程效果图如图20所示。
图20

5 总结

受限于笔者水平,文章可能存在不合理、疏漏乃至错误之处,文章中一些名词的使用也较为混乱,大家可以共同探讨、改进。
另外,需要强调的一点是,本文为在粗略的试错上建立起来的经验之谈,相关的理论、技术基础等几乎为零,请各位看官酌情参考。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值