在实际应用中,我们还需要将Shape进行移动。将Shape进行移动的方法有两种。首先我们讲最简单的移动方式:选中à移动。
选中移动是指先把被移动的Shape进行选择,然后再进行移动处理。在ApplicationClass中,有一个Window成员,叫做ActiveWindow,当前活跃窗口。通过ActiveWindow可以选择Shape,就像用鼠标在屏幕上选中对象一样。然后在通过ActiveWindow的SelectionMove来进行移动,非常简单!
代码:
/* ------------------------------------------------------------------ */
/* 使用Select的方式进行的移动 */
private void f_part3_move_shape_by_select(string sShape,double DeltaX,double DeltaY)
{
Shape sp;
Window aw;
sp = findshapebyid(sShape);
if (sp == null)
return;
aw = m_App.ActiveWindow;
aw.Select(sp, (short)VisSelectArgs.visSelect);
aw.Selection.Move(DeltaX, DeltaY, (VisUnitCodes.visCentimeters));
}
需要注意的是,移动时有一个参数,叫做单位,这里用的是VisUnitCodes.visCentlmeters,也可以用毫米等等单位。移动的时候,都是以偏移量作为参数的。如果需要定位到某绝对位置,还需要更高深的技巧,比如要知道本形状当前的绝对位置等等。(看起来获得形状的当前绝对位置不难,其实还挺复杂,关键是要理解)。
使用选择à移动的方式是我一开始使用的方法。当我逐渐深入了解visio之后,可以使用另外的方式进行移动。这就需要大家仔细去理解Cell,理解Row,理解Column,理解Visio的组织结构。
下一种移动位置的方法是更改Shape中的位置资料。Shape中的位置资料是保存在Cell中的。要理解如何设置Cell,如何获得Cell,那就需要下边讲述的知识了。
在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);
/* ------------------------------------------------------------------ */
/* 打开D:/TestVisio/TestCase01.vsd,移动CUSTID1的图像,本次实现的移动 */
/* 不是使用Select的方式进行的,尝试通过修改ParentShape的属性实现 */
private void f_part3_move_shape(string sShape,double DeltaX,double DeltaY)
{
Shape sp;
short iRet;
Cell m_Cell;
/* ------------------------------------------------------------------ */
/* <XForm>
<PinX>1.771653543307088</PinX>
<PinY>10.23622047244095</PinY>
...
* PinX是指的形状的中心点。
*/
sp = findshapebyid(sShape);
if (sp == null)
return;
iRet = sp.get_CellsSRCExists((short)VisSectionIndices.visSectionObject, (short)VisRowIndices.visRowXFormOut,
(short)VisCellIndices.visXFormPinX, (short)1);
if (iRet == 0)
{
Console.WriteLine("Internal error? can't find <XForm> <PinX>?");
return;
}
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);
}
当然还免不了有测试代码,动画效果哦。
public void TestCase01Part3()
{
bool m_bRet;
int i;
m_Gvar = new CGvars();
m_bRet = m_Gvar.OpenVisioDocument("D://TestVisio//TestCase01.vsd", (short)Microsoft.Office.Interop.Visio.VisOpenSaveArgs.visOpenCopy);
if (m_bRet == false)
return;
m_App = m_Gvar.GetApplicationClass();
m_Doc = m_Gvar.GetDocument();
try
{
/* 切换到全屏 */
m_App.DoCmd((short)VisUICmds.visCmdFullScreenMode);
for (i = 0; i < 10; i++)
{
f_part3_move_shape("/"CUSTID1/"", 0.2, 0);
Thread.Sleep(20);
}
for (i = 0; i < 10; i++)
{
f_part3_move_shape_by_select("/"CUSTID2/"", 0.2, 0);
Thread.Sleep(20);
}
Thread.Sleep(3000); /* 3秒之后程序退出 */
m_Doc.Saved = true; // 防止关闭弹出保存对话框
}
finally
{
m_Doc.Close();
m_App.Quit();
}
}