2023年2月

我们知道,Visio的Shape对象有有3个比较特别的属性,分别是Data1、Data2、Data3,平常我们很少用到它,因为我们如果需要属性的话,可能会通过ShapeSheet的Customed Properties中定义我们所需要的信息,需要各种属性的值都可以拿到,那么Data1、Data2、Data3对我们来说,就用处不大,但有的情况下,我们使用它进行一些操作却是非常方便的。
首先我们介绍下,这几个属性是什么东西,Data1、Data2、Data3是Shape的内置属性,不需要额外定义,而且它据说可以放置64K的字符串(没有试过这么大的),一般来说我们是足够放置内容的了。
言归正题,我们什么情况下会使用到这几个属性呢,大家想想,我们在绘图的时候,是不是总有个图框来列出图纸那些设备有几个的一个设备清单呢,或者是一个比例尺说明什么的,这些场合就可以 用上它们了。
因为这些线条,可能是我们随机画上去的,里面的一些设备的说明以及相关信息一般都是根据图纸变化而变化的,因此每次保存的时候,可能要重新绘制一下,但绘制的时候,为了有别于其他设备图元,我们使用这几个属性来记录相关信息。
01.JPG
如我们抹掉已经画上去的内容的时候,可以如下操作



//
把原有的统计项先抹掉



if
(VisioUtility.HasShapeInWindow(visApp.ActiveWindow))



{

visApp.ActiveWindow.SelectAll();

foreach
(Visio.Shape shape
in
visApp.ActiveWindow.Selection)



{

if
(shape.Data1
==

"
tongji
"
)



{

shape.Delete();

}



}



visApp.ActiveWindow.DeselectAll();

}


如果我们需要添加那些信息的时候,先根据坐标绘制一个形状


Visio.Shape shape
=
VisioUtility.DrawRectangeOnPage(visApp, leftTopX, leftTopY
-
j
*
cellHeight,

leftTopX

+
leftCellWidth, leftTopY
-
(j
+

1
)
*
cellHeight);


然后给这个Shape的属性Data1、Data2、Data3赋值



//
第一个单元格用于放统计项名称


shape.NameU
=

"
某某名称
"
;

shape.Text

=

"
某某名称
"
;

shape.Data1

=

"
tongji
"
;
//
用tongji来标识Data1这个属性只是为了在删除这些框的时候便于查找到


//
放置统计内容


shape
=
VisioUtility.DrawRectangeOnPage(visApp, leftTopX
+
leftCellWidth, leftTopY
-
j
*
cellHeight,

leftTopX

+
leftCellWidth
+
rightCellWidth, leftTopY
-
(j
+

1
)
*
cellHeight);

shape.NameU

=

"
某某名称
"

+

"
v
"
;

shape.Data1

=

"
tongji
"
;


//
Data2放模具组的名称,用来获取相关单位


shape.Data2
=

"
模具组名称
"
;


以上是绘制统计图框,绘制完后,我们可能需要更新图框右边的统计值



foreach
(Visio.Shape shape
in
page.Shapes)



{

if
(shape.Data1.Equals(
"
tongji
"
, StringComparison.OrdinalIgnoreCase)
&&


shape.NameU.Equals(

string
.Format(
"
{0}v
"
, resultInfo.DeviceType)))



{






..

}



}


整个应用就完成了,这里没有用到自定义属性,因为Data1、Data2、Data3的属性更好用,而且方便,你说呢?

明察秋毫,很多情况下是非常需要的,例如,如果我们完善了一套系统,能够将四川赈灾的款项的筹集、采购、分发等步骤都能明察秋毫,相信整个世界会安静很多。
同样,对于一个使用Visio进行二次开发的程序来说,背后你需要知道用户增加了那些设备,删除了那些设备,修改了那些设备,这样你才能对整个系统的数据进行有效的控制,否则“赈灾”款项就可能丢失了,呵呵。
那我们应该如何做才能有效的处理这些事件,达到对设备的变更明察秋毫呢?
我前面介绍了一篇文章《
C#进行Visio开发的事件处理
》,其中也介绍了各种事件侦听,我们要对设备进行跟踪的话,基本上只需要侦听这几个事件,并对之进行处理即可。



const

string
sink
=

""
;

Event newEvent

=

null
;


EventList applicationEvents

=
eventApplication.EventList;

EventList documentEvents

=
eventDocument.EventList;


newEvent

=
documentEvents.AddAdvise(

(

unchecked
((
short
)VisEventCodes.visEvtAdd)
+
(
short
)VisEventCodes.visEvtShape),

(IVisEventProc)

this
, sink,
"
ShapeAdd
"
);


newEvent

=
documentEvents.AddAdvise(

(

short
)VisEventCodes.visEvtDel
+
(
short
)VisEventCodes.visEvtShape,

(IVisEventProc)

this
, sink,
"
ShapeDelete
"
);


newEvent

=
documentEvents.AddAdvise(

(

short
)VisEventCodes.visEvtMod
+
(
short
)VisEventCodes.visEvtCell,

(IVisEventProc)

this
, sink,
"
CellChanged
"
);




object
IVisEventProc.VisEventProc(
short
eventCode,
object
source,
int
eventId,

int
eventSequenceNumber,
object
subject,
object
moreInfo)



{






.

switch
(eventCode)



{

case
(
short
)VisEventCodes.visEvtShape
+

unchecked
((
short
)VisEventCodes.visEvtAdd):

eventShape

=
(Shape)subject;

handleShapeAdd(eventShape);

break
;


case
(
short
)VisEventCodes.visEvtDel
+
(
short
)VisEventCodes.visEvtShape:

eventShape

=
(Shape)subject;

handleShapeDelete(eventShape);

break
;


case
(
short
)VisEventCodes.visEvtCell
+
(
short
)VisEventCodes.visEvtMod:

Visio.Cell cell

=
(Cell)subject;

if
(cell.Name.IndexOf(
"
Prop
"
)
>=

0
)
//
限制只执行自定义事件一次





{

eventShape

=
cell.Shape;

handleCellModify(eventShape);

}



break
;


default
:

break
;

}




return
result;

}


以上是对几个特别事件的侦听,我们要实现设备的跟踪,需要在这几个事件中处理相关的设备信息。为了跟踪好设备的相关信息,我们需要定义一个实体类ShapeLogInfo类放置相关的设备信息,如Shape的GUID,Shape的ID,设备状态(添加、修改、删除),Shape的Name,还有就是我们自定义的一个属性“设备类型”。





///

<summary>


///
形状的类型,即设备类型

///

</summary>



public

string
ShapeType




///

<summary>


///
形状ID

///

</summary>



public

string
ShapeID




///

<summary>


///
形状的GUID

///

</summary>



public

string
ShapeGuid




///

<summary>


///
形状的状态:新增、编辑、删除

///

</summary>



public
ShapeStatus ShapeStatus




///

<summary>


///
形状名称

///

</summary>



public

string
ShapeName

为了更好的管理ShapeLogInfo的相关信息,我们把相同设备类型(ShapeType)的放到一起管理,于是,我们再创建一个ShapeTypeLogInfo类来装载相关的日志信息,具体如下:





///

<summary>


///
形状类型

///

</summary>



public

string
ShapeType




///

<summary>


///
形状类型对应的数据库表名称

///

</summary>



public

string
ShapeTableName




///

<summary>


///
该形状类型对应的形状日志对象集合

///

</summary>



public
Dictionary
<
string
, ShapeLogInfo
>
ShapeColloction

为了判断是否有相应的ShapeTypeLogInfo和ShapeLogInfo,需要定义几个函数用来维护相关的集合信息,如下所示:





///

<summary>


///
获取形状对应的类型,如果没有则创建

///

</summary>



private
ShapeTypeLogInfo GetShapeType(ShapeLogInfo shape)




///

<summary>


///
如果集合中有,修改状态;否则添加一个新的

///

</summary>



public

void
AddLog(ShapeLogInfo shapeLogInfo)




///

<summary>


///
判断指定的形状是否是在集合中存在

///

</summary>



public

bool
IsNewShape(ShapeLogInfo shape)





///

<summary>


///
取当前设备在集合中存储的ShapeLog对象

///

</summary>



public
ShapeLogInfo GetShape(
string
shapeType,
string
shapeId)

完成这些函数后,最后需要做的就是在添加、删除、修改Shape的侦听事件中加入相关的日志就可以了,如在删除设备的时候,我们是这样记录相关信息的


private

void
visioEventSink_OnShapeDelete(
object
sender, EventArgs e)



{

Shape shape

=
(Shape)sender;

string
strDeviceType
=

string
.Empty;

string
strDeviceName
=

string
.Empty;

string
strDeviceID
=

string
.Empty;


strDeviceType

=
VisioUtility.GetShapeCellValue(shape,
"
设备类型
"
);

strDeviceID

=
VisioUtility.GetShapeCellValue(shape,
"
GUID
"
);



ShapeLogInfo logInfo

=

new
ShapeLogInfo();

logInfo.ShapeType

=
strDeviceType;

logInfo.ShapeID

=
shape.NameID;

logInfo.ShapeName

=
VisioUtility.GetShapeCellValue(shape,
"
名称
"
);


if
(shape.Application.IsUndoingOrRedoing)



{

ShapeLogInfo info

=
Portal.gc.gOperationLog.GetShape(strDeviceType, shape.NameID);

if
(info
!=

null
)



{

strDeviceID

=
info.ShapeGuid;

}



}




logInfo.ShapeGuid

=
strDeviceID;

logInfo.ShapeStatus

=
ShapeStatus.DeleteShape;

Portal.gc.gOperationLog.AddLog(logInfo);

}

其中if (shape.Application.IsUndoingOrRedoing)是为了判断该操作是否为Undo或者Redo导致的操作,如果是,我们获取它之前Shape的GUID即可。

记录了设备的这些修改信息,我们就可以在保存数据的时候,根据这些信息移除相关的关系,添加或者删除相关的设备信息了,而且这些信息,对于我们记录用户的图纸修订记录也是必须要做的事情。

平时使用Path类不多,常用Combine来串联两个路径,其他的很少用,偶然发现Path提供了很多方便实用的函数。

名称

说明

ChangeExtension

更改路径字符串的扩展名。

Combine

合并两个路径字符串。

GetDirectoryName

返回指定路径字符串的目录信息。

GetExtension

返回指定的路径字符串的扩展名。

GetFileName

返回指定路径字符串的文件名和扩展名。

GetFileNameWithoutExtension

返回不具有扩展名的指定路径字符串的文件名。

GetFullPath

返回指定路径字符串的绝对路径。

GetInvalidFileNameChars

获取包含不允许在文件名中使用的字符的数组。

GetInvalidPathChars

获取包含不允许在路径名中使用的字符的数组。

GetPathRoot

获取指定路径的根目录信息。

GetRandomFileName

返回随机文件夹名或文件名。

GetTempFileName

创建磁盘上唯一命名的零字节的临时文件并返回该文件的完整路径。

GetTempPath

返回当前系统的临时文件夹的路径。

HasExtension

确定路径是否包括文件扩展名。

IsPathRooted

获取一个值,该值指示指定的路径字符串是包含绝对路径信息还是包含相对路径信息。

我比较喜欢那个 GetTempPath 函数,一句代码就可以返回临时目录的路径了,很方便,大家可能也注意到,Environment.GetEnvironmentVariable(string variable)也提供了如何获取特别路径的函数,另外Environment.GetFolderPath(Environment.SpecialFolder folder)的函数也提供了很多实用功能,返回相应的目录,枚举包含有:

Environment
.
SpecialFolder
枚举

成员名称

说明

ApplicationData

目录,它用作当前漫游用户的应用程序特定数据的公共储存库。

CommonApplicationData

目录,它用作所有用户使用的应用程序特定数据的公共储存库。

LocalApplicationData

目录,它用作当前非漫游用户使用的应用程序特定数据的公共储存库。

Cookies

用作
Internet Cookie
的公共储存库的目录。

Desktop

逻辑桌面,而不是物理文件系统位置。

Favorites

用作用户收藏夹项的公共储存库的目录。

History

用作
Internet
历史记录项的公共储存库的目录。

InternetCache

用作
Internet
临时文件的公共储存库的目录。

Programs

包含用户程序组的目录。

MyComputer


我的电脑

文件夹。

MyMusic

“My Music”
文件夹。

MyPictures

“My Pictures”
文件夹。

Recent

包含用户最近使用过的文档的目录。

SendTo

包含

发送

菜单项的目录。

StartMenu

包含

开始

菜单项的目录。

Startup

对应于用户的

启动

程序组的目录。

System

“System”
目录。

Templates

用作文档模板的公共储存库的目录。

DesktopDirectory

用于物理上存储桌面上的文件对象的目录。

Personal

用作文档的公共储存库的目录。

MyDocuments


我的电脑

文件夹。

ProgramFiles

“Program files”
目录。

CommonProgramFiles

用于应用程序间共享的组件的目录


最后我顺便提供一个递归删除文件夹和文件的操作函数,方便大家





///

<summary>


///
删除文件夹及其下面的子文件和文件夹

///

</summary>


///

<param name="filePath"></param>



public

static

void
DeleteSubFileAndFolder(
string
filePath)



{

if
(Directory.Exists(filePath))



{

foreach
(
string
path
in
Directory.GetFileSystemEntries(filePath))



{

if
(File.Exists(path))



{

File.Delete(path);

}



else




{

DeleteSubFileAndFolder(path);

}



}



Directory.Delete(filePath);

}



}

每个
Shape
有很多属性,这里我是指自定义属性,每个属性都对应一种类型,就像我们在
SqlServer
创建一个字段的时候,需要指定其类型一样。
Visio
的属性类型有以下几种:



说明

自动常量

0

字符串。此为默认值。

visPropTypeString

1

固定列表。在

形状数据

对话框的下拉组合框中显示列表项。在
Format
单元格中指定列表项。用户只能从该列表中选择一项。

visPropTypeListFix

2

数字。包括日期、时间、持续时间和货币值,以及标量、尺寸和角度。在
Format
单元格中指定格式图片。

visPropTypeNumber

3

布尔值。显示
FALSE

TRUE
项,这两项正是用户可以从

形状数据

对话框中的下拉列表框选择的项。

visPropTypeBool

4

变量列表。在

形状数据

对话框的下拉组合框中显示列表项。在
Format
单元格中指定列表项。用户可以选择列表项或在
Format
单元格中输入添加到当前列表中的新项。

visPropTypeListVar

5

日期或时间值。显示日、月和年,或者秒、分钟和小时,或者日期和时间的组合值。在
Format
单元格中指定格式图片。

visPropTypeDate

6

持续时间值。显示已经过去的时间。在
Format
单元格中指定格式图片。

visPropTypeDuration

7

货币值。使用系统的当前

区域设置

。在
Format
单元格中指定格式图片。

visPropTypeCurrency



本文着重介绍下两种类型:
visPropTypeListFix

visPropTypeListVar
,分别为固定列表和可变列表,其实这两种类型对应的
.NET
编程控件模型就是
ComboBox
,这个控件有个属性
DropDownStyle
,可以指定为
DropDownList

DropDown
这两种类型,本质上
visPropTypeListFix
对应
DropDownList
类型(只能选择列表的值),
visPropTypeListVar
对应
DropDown
类型(除了可选列表值,还可以输入新的值)。



Visio
的这两种列表的值,都是通过对应的
ShapeSheet
行中的
Format
的值来展示的,如有一个列表的
Format
的值为“
专用
;
公用
”,那么就有两项可以选择,如下图所示。


VisioDropdownList.jpg

那我们在
Visio
的二次开发程序中如何管理这些下拉列表的呢?

对于固定列表,我们在添加或者修改自定义属性的时候,就给它固定的值那就可以了,很简单,如下面的代码:


targetShape.get_CellsU(
"
Prop.
"

+
source.Name
+

"
.Format
"
).FormulaU
=

"
专用;公用
"


对于可变列表,我们就不希望把内容写死,否则妄为其名。如“生产厂商”属性,我们就希望它能够随着我们的数据库内容的变化而变化,那么如何实现呢?

首先,我们在数据库中创建两个表,一个表命名为
DropdownListField
,用来放置下拉列表的字段;一个表命名为
DropdownListValue
,用来放置可变列表的选项值。
VisioDropDownField.jpg
VisioDropDownValue.jpg


接着,我们在添加或者修改模具属性的时候,对每个设备的字段,为可变列表类型

visPropTypeListVar
,而
没有加入列表字段表
DropdownListField
的,添加到数据库。这样就相当于在数据库中注册了该字段为可变列表,可以给它赋值了(下面介绍)。

然后,就是给可变列表赋值的步骤了,我们从数据库中找出相关的可变列表字段,给它添加或者删除相关的列表项就可以了,如下图所示。
SetDropdownList.jpg


最后,我们需要查看某个设备的时候,把它的下拉列表属性值更新显示即可(注意:上个步骤只是修改了数据库的列表值,而没有改变图纸的)。

由于我们查看图纸设备的时候,我们一定是选定了某个设备,那么我们在选定操作里面(响应
Visio DrawingControl
控件的
SelectionChanged
事件)
更新图纸设备的列表字段就可以了,更新下拉列表的操作其实就是遍历所有的属性,对是可变列表的属性修改其列表值即可。



if
(VisWindow.Selection.Count
>

0
)



{

Shape shapeSelected

=
VisWindow.Selection.get_Item16(
1
);

//
只更新选定的第一个设备的下拉列表,这样速度不会很慢


dropdownBLL.UpdateDropdownListValue(shapeSelected);

}






///

<summary>


///
更新Shape下拉列表的值

///

</summary>



public

void
UpdateDropdownListValue(Visio.Shape targetShape)



{

string
fieldId
=

string
.Empty;

string
strFormatVlaue
=

string
.Empty;

List

<
StencilPropertyInfo
>
properties
=
propertyVOL.GetPropertyCollection(targetShape);


foreach
(StencilPropertyInfo entity
in
properties)



{

if
(entity.PropType
==
PropType.visPropTypeListVar)



{

strFormatVlaue

=
dropdownDAL.GetDrpFieldVlaue(entity.DeviceName, entity.Name);

if
(
!
string
.IsNullOrEmpty(strFormatVlaue))



{

targetShape.get_CellsU(

"
Prop.
"

+
entity.Name
+
"
.Format
"
).FormulaU
=


VisioUtility.StringToFormulaForString(strFormatVlaue);

}



}



}



}



我们看到最后的结果如下
VisioDropDownValue2.jpg

好久没有更新代码生成工具了,因为很多功能基本上能够应付日常的开发工作了,如C#代码生成、数据库文档导出、Sql脚本生成,但用户在使用过程中也发现了一些需要完善的地方,因此继续完善,以求更加实用方便。
现更新了下面几个问题,希望给大家做软件开发的时候有一个启示。
1. 所有的模板支持C#2.0的架构,方便转换为高版本的VS格式。
虽然现在很多时候用VS2008来做开发了,但是我们一般来说还是基于C#2.0来进行开发;本次更新考虑有些用户可能还是使用VS2005的开发工具,因此只做了基于VS2005的项目工程文件,VS2008也可直接打开,转换正常。
Database2SharpVS2005.jpg

2. 完善修改表的别名和字段别名功能。
这个功能原来就有的,只是对于表很多,字段很多,超出屏幕的范围没有进行很好的控制,因此导致一些显示的问题,本版本采用了TableLayoutPanel来进行动态的伸缩,再多的内容,都可以通过滚动条进行控制了,呵呵。
Database2SharpSetTable.jpg
Database2SharpSetField.jpg

3.  软件界面修改成了XP样式的了
其实VS2005及后续版本进行开发的软件本来就支持XP样式的,我人为的去掉了,只保留VS2003的那种样式。设置成XP样式也很简单的,Main函数启动的时候两行代码就搞定。


Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(

false
);


Database2Sharp080610.jpg

4。 增加数据库文档的格式设置
默认导出的数据库表包括内容有:字段描述、字段列名、数据类型、约束类型、可空、默认值,上一版本不能选择性进行导出,这个版本完善之,选择性地导出你认为重要的信息吧。
Database2SharpTableSchema.jpg