C#进行Visio二次开发
工作总想偷懒,偷懒的前提就是要勤奋,那才有偷懒的资本
---------------------------------------------------------------------------------------------
Visio的编程对象模型
Visio二次开发中,对于其对象模型的了解,就写我们吃饭用筷子一样重要,否则不熟悉筷子的使用场景和方法,是吃不到东西的。
其中主要的是下面几个对象:
Application
Window (Application.ActiveWindow)
Document (Application.ActiveDocument)
Master、Shape、Cell
一个程序中,就只有一个Application对象,类似进程的概念;打开的Visio文件有很多窗口,有一个ActiveWindow的主窗口,选区窗口、形状窗口等;模具文件打开后也是一个Document、打开的Visio文件也是一个Document、ActvieDocument是指当前 Visio窗口对应的文档,每个Visio的Document有一个或者多个Page,如系统中有两个Page、一个为绘图Page,一个为背景 Page。
模具文件里面有很多Master,Master类似于模板的概念,一个Master代表一个设备类型、Shape是定义一个图形的信息,模具里面的 Master有且只有一个Shape,每个Shape又有很多Cell,代表一个ShapeSheet里面的一个格子,每个格子都有一个唯一的引用名称的。
Visio文档里面,和模具文件不同,里面一个大千世界,有很多Master,也很多Shape和Cell,但是由于我们看到图纸可能一个模具的设备会有拷贝多个,因此它们公用一个Master,也就是一个Master有多个Shape引用,我们分析文件,就知道里面有一个MasterID,有点像数据库里面的外键一样。
在VisioSDK中,大量提及使用Cell,CellsSRC等等方式进行操作,但却基本没有提及Visio的形状大小、颜色等等。Cell,Row,Column,比如最开始设置一个形状数据,为啥是取自CellsSRC(VisSectionProperty,0,0)第0行第0列?
把Visio保存成为XML后,发现每一个形状都是使用XML进行描述的。这很重要,也就是说,Cell代表了XML中的一个叶子节点。比如形状数据中的值等等。根据保存的值,一个形状数据有很多属性,一个"CUSTID1"就有这么多内容:
<Prop NameU='Row_1' ID='1'>
<Value Unit='STR'>CUSTID1</Value>
<Prompt F='No Formula'/>
<Label>ID</Label>
<Format F='No Formula'/>
<SortKey F='No Formula'/>
<Type>0</Type>
<Invisible F='No Formula'>0</Invisible>
<Verify F='No Formula'>0</Verify>
<LangID>1033</LangID>
<Calendar F='No Formula'>0</Calendar>
</Prop>
我们取得的Value这个Cell来定位Shape的。我们还可以取这个CUSTID1的Prompt,只需要用CellsSRC(VisSectionProperty,0, visCustPropsPrompt),注意,其实VisCustPropsPrompt的值是1,visCustPropsLabel的值是2。这暗示了一件事,也就是说,Column,列,意味着在XML中的位置,比如我们看到Value 是Prop中的第一行,哪么,它对应的Column就是0,我们数到Type是第6行,对应的Column就是5,这是真的吗?当然,不信去查visCustPropsType,它就是5。
哪么Row又是怎么回事呢?Row,在这里就是第几个Prop的意思。一个形状可以定义多个形状数据,哪么第一个形状数据就是Row 0,第二个就是Row1。这个可以用来枚举的。
哪么第一个参数Section是啥意思呢?因为我们是取形状数据,也就是CustProperty,因此第一个参数我们用的是visSectionProp。
XML里边一个Shape可以有好多好多类的Section,Prop只是其中之一而已。
言归正传,我们要设置Shape的位置,我们根据保存的XML可以很容易看出,Shape的位置信息似乎是在XForm这个Section里边的。
<XForm>
<PinX>1.771653543307088</PinX>
<PinY>10.23622047244095</PinY>
<Width>0.78740157480315</Width>
<Height>0.78740157480315</Height>
<LocPinX F='Inh'>0.393700787401575</LocPinX>
<LocPinY F='Inh'>0.393700787401575</LocPinY>
<Angle F='Inh'>0</Angle>
<FlipX F='Inh'>0</FlipX>
<FlipY F='Inh'>0</FlipY>
<ResizeMode F='Inh'>0</ResizeMode>
</XForm>
按照我们一般的思想,Shape总归有一个左上角的坐标,其实我没找到。PinX、PinY,是旋转的中心点。LocPinX和LocPinY是这个Shape自身的中心点。后来我想啊想啊的就想明白了,Shape(形状)可能是不规则的,虽然可以获得包容Shape的最小矩形框,但是毕竟不是规则的,也就是说,可能没有左上角的含义。所谓Shape中心点,也就是以左上角定义的中心点。(真的是左上角坐标系吗?呵呵)
实际上,Visio采用的坐标系是左下角坐标系,也就是说,以页面左下角为0,0,页面左上端为0,MaxY,页面右上端是MaxX,MaxY。
Ok,移动一个Shape,也就是设置<XForm>的<PinX>和<PinY>就可以了。根据上边我们对Cell的理解,也就是设置PinX,PinY这两个Cell就可以了。
get_CellsSRC的三个参数怎么填写呢?首先是Section节,Visio没有对XForm定义单独的Section,它把它归在了VisSectionIndices.visSectionObject中,也就是说Section是Object,它需要使用Row来确定是取XForm节,我猜了一下,是(short)VisRowIndices.visRowXFormOut节。其实还有一个,叫visRowXFormIn。visRowXFormOut、visRowXFormIn这两个参数定义都是0,为啥呢?因为<XForm>是<Shape>的第一个定义,按照Visio的Cell、Row、Column的参数定义,第一个就是0嘛。
第三个参数Column就很简单,取PinX就填写0,取PinY就填写1,实际上VisioSDK的参数中也是这样定义的,visXFormPinX = 0,visXFormPinY = 1。
看起来似乎一切正常。我们取得了表示位置中心的PinX和PinY Cell,只需要动动小手指,改一下这两个Cell的值,就可以搞定移动了。根据我们前边的知识,只需要设置Formula(汗死…)就可以了。
实际上我第一次也这样干的,傻呗。正确的做法是使用set_Result,get_Result,如下所示:
m_Cell = sp.get_CellsSRC((short)VisSectionIndices.visSectionObject, (short)VisRowIndices.visRowXFormOut,
(short)VisCellIndices.visXFormPinX);
m_Cell.set_Result(VisUnitCodes.visCentimeters, m_Cell.get_Result(VisUnitCodes.visCentimeters) + DeltaX);
m_Cell = sp.get_CellsSRC((short)VisSectionIndices.visSectionObject, (short)VisRowIndices.visRowXFormOut,
(short)VisCellIndices.visXFormPinY);
m_Cell.set_Result(VisUnitCodes.visCentimeters, m_Cell.get_Result(VisUnitCodes.visCentimeters) + DeltaY);