Addin给元素添加自定义Linkage数据


我们在Mstn中开发工程建模软件时,经常会遇到需要往图形元素上添加自定义属性的需求。举个例子,假如我们创建出来一根管道图形元素以后,我们需要往这个元素上添加一些业务属性,例如管道的工作温度,材质等信息。Mstn SDK发展至今,提供了多种往元素上添加自定义属性的方式,如下阐述了这些方式的原理:

Linkage:添加到元素的自定义属性数据块与Mstn元素原有属性共同保存在MSElement中,MSElement是C/C++接口中的一个union数据类型,Mstn中的一个简单元素加载到内存后就是MSElement的一个实例。对于复杂元素,无论是多么复杂,都是由多个MSElement的实例组合起来构成的。学过C/C++语言的都知道,union数据类型的大小是由其成员变量中最大的那个成员变量决定的。从MSElement的定义中可以看到其最大的成员变量是“Int16 buf[MAX_INTERNAL_ELEM_WORDS];”,所以MSElement类型的大小约为130K字节。由于Linkage的数据块与元素原有属性共同保存在MSElement中,故Linkage方式能添加的自定义属性数据块大小是受MSElement类型大小的限制的。其数据块最大不能超过MSElement扣除元素原有属性后的大小,不同类型的元素其包含的原有属性也是不一样的。所以不同类型的元素能添加的Linkage自定义属性数据块的极限也是不同的。不仅是不同类型的元素可存储的Linkage数据大小有区别,同一类型元素的不同实例之间可存储的Linkage数据大小也可能有区别。例如LineString元素,其元素原有属性大小会随着包含点的增多而增加,因为MSElement大小是固定的,所以其能保存的Linkage数据块大小也会相应的减小。

XAttribute:添加到元素的自定义属性数据块与Mstn元素的原有属性分开存储了,不再是共同保存在MSElement中。自定义属性会由系统自动分配一个ID值,这个ID值会保存在Mstn元素中,后台就是通过这个ID值将自定义属性与元素关联起来的。显然这种方式最大的好处就是自定义属性块不再像Linkage那样,大小受MSElement数据格式的限制了。

ECXAttribute:使用Linkage和XAttribute的方式给元素添加自定义属性时,属性的数据格式是通过在程序代码中声明的结构体类型定义的。我们给元素添加了自定义属性后,如果其他人没有我们的结构体类型定义,是无法解析读取这些自定义属性的,ECXAttribute解决了这个问题。ECXAttribute是基于XAttribute的,即添加到元素的自定义属性跟元素原有属性是分开存储的。使用ECXAttribute添加自定义属性时,属性的数据格式不再是通过在程序代码中声明一个结构体类型来指定,而是由ECSchema中的ECClass定义的,它的作用相当于是自定义属性的格式“说明书”。ECSchema可以导入保存在dgn文件中,也可以以外部“*.xml”文件保存。无论是哪种方式,在读取元素上添加的ECXAttribute自定义属性时,必须要找到对应的ECSchema中对应的ECClass才能够正确解析出这些属性。后台系统是通过ECSchema以及ECClass的名字去定位自定义属性的“说明书”的。通常我们的ECSchema是导入保存在dgn文件中的,这样只要是基于Mstn的产品打开,以及我们通过二次编程接口都可以正确读取解析出元素上通过ECXAttribute添加的自定义属性。

ItemType: EC只能通过编程的方式进行操作,所以在EC的基础上又增加了ItemType添加自定义属性的方式,可以认为ItemType就是简化、界面化后的EC。当然编程也可以操作ItemType,其编程接口也相对于EC编程接口简单一些。以下表格是这几种添加自定义属性方式的对比:

 

C#

C/C++

数据共享

COM框架

CE新框架

Linkage

Bentley.Interop.MicroStationDGN.Element下的 SetXData,GetXData,DeleteXData以及AddUserAttributeData,GetUserAttributeData,DeleteUserAttributeData

 

Bentley.DgnPlatformNET.Elements.Element下的AppendLinkage,GetLinkage,DeleteLinkage

mdlLinkage_XXX

需要有数据格式定义才能读取自定义数据

XAttribute

ITxnManager,XAttributeHandlerId,XAttributeHandle

同Linkage

ECXAttribute

Bentley.Interop.MicroStationDGN. PropertyHandler

Bentley.Interop.MicroStationDGN. CreatePropertyHandler

DgnECManager,ECSchema,ECClass,ECProperty,IECInstance等

DgnECManager,ECSchema,ECClass,ECProperty,IECInstance等

通过Mstn SDK接口就可以读取

ItemType

ItemTypeLibrary,ItemType,CustomProperty,CustomItemHost等

ItemTypeLibrary,ItemType,CustomProperty,CustomItemHost等

同ECXAttribute

下图以图示方式说明了这几种给元素添加自定义属性方式的原理:

(注:ItemType原理与ECXAttribute基本一致,故在图中没有再单独示意出来。此外本图意在说明自定义属性的工作原理,有关Dgn文件的一些细节图中未完全体现)

接下来我们用代码去看一下Addin中使用CE新框架如何给元素添加自定义Linkage属性:

using Bentley.DgnPlatformNET;
using Bentley.DgnPlatformNET.Elements;
using Bentley.MstnPlatformNET;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LinkageCRDUDemo
{
    class PipeInformation
    {
        public double Diameter;
        public double Temperature;
        public string Material;
        public PipeInformation()
        {
            Diameter = double.MinValue;
            Temperature = double.MinValue;
            Material = null;
        }

        public PipeInformation(double diam,double temp,string mate)
        {
            Diameter = diam;
            Temperature = temp;
            Material = mate;
        }
    }

    class KeyinCommands
    {
        public static ushort LinkageId = 123;
        public static Element Ele1
        {
            get
            {
                ElementAgenda eleAgen = new ElementAgenda();
                SelectionSetManager.BuildAgenda(ref eleAgen);
                if (eleAgen.GetCount() < 1)
                    return null;
                Element ele1 = eleAgen.GetEntry(0);
                return ele1;
            }
        }
        public static void CreateLinkage(PipeInformation pipeInfo)
        {
            Element oldEle = Element.GetFromElementRef(Ele1.GetNativeElementRef());
            WriteDataBlock wrDataBlk = new WriteDataBlock();
            wrDataBlk.WriteDouble(pipeInfo.Diameter);
            wrDataBlk.WriteDouble(pipeInfo.Temperature);
            wrDataBlk.WriteString(pipeInfo.Material);
            oldEle.AppendLinkage(LinkageId, wrDataBlk);
            oldEle.ReplaceInModel(oldEle);
        }
        public static PipeInformation ReadLinkage()
        {
            PipeInformation rtnPipeInfo = null;
            ReadDataBlock reDataBlk = Ele1.GetLinkage(LinkageId);
            if (null != reDataBlk)
            {
                rtnPipeInfo = new PipeInformation();
                rtnPipeInfo.Diameter = reDataBlk.ReadDouble();
                rtnPipeInfo.Temperature = reDataBlk.ReadDouble();
                rtnPipeInfo.Material = reDataBlk.ReadString();
            }
            return rtnPipeInfo;
        } 

        public static void DeleteLinkage()
        {
            Element oldEle = Element.GetFromElementRef(Ele1.GetNativeElementRef());
            oldEle.DeleteLinkage(LinkageId);
            oldEle.ReplaceInModel(oldEle);
        }

        public static void UpdateLinkage(PipeInformation pipeInfo)
        {
            DeleteLinkage();
            CreateLinkage(pipeInfo);
        }

        public static void TestFun(string unparsed)
        {
            unparsed = unparsed.Trim().ToLower();
            if (unparsed == "create")
            {
                PipeInformation pipeInfo = new PipeInformation(0.5, 80, "Q235A");
                CreateLinkage(pipeInfo);
            }
            else if (unparsed == "read")
            {
                PipeInformation pipeInfo = ReadLinkage();
                if(null!=pipeInfo)
                {
                    string msg = string.Format("Diameter={0},Temperature={1},Material={2}", pipeInfo.Diameter, pipeInfo.Temperature, pipeInfo.Material);
                    MessageCenter.Instance.ShowInfoMessage(msg, "", false);
                }
            }
            else if(unparsed == "update")
            {
                PipeInformation pipeInfo = new PipeInformation(1, 90, "Q235B");
                UpdateLinkage(pipeInfo);
            }
            else if (unparsed == "delete")
            {
                DeleteLinkage();
            }
        }
    }
}

测试上面的代码可在Mstn中通过选择集工具选中一个元素以后,依次调用KeyinCommands.TestFunc("create"), KeyinCommands.TestFunc("read"), KeyinCommands.TestFunc("update"), KeyinCommands.TestFunc("delete"),分别演示了在元素上添加、读取、更新以及删除自定义Linkage属性。KeyinCommands类中定义了一个static类型的成员属性Ele1,通过此属性可以获取选中的元素。我们在代码中定义了PipeInformation类,用来表示添加到元素上的自定义属性。添加自定义属性时,我们new了一个PipeInformation的实例,传递给我们编写的另外一个CreateLinkage函数。在CreateLinkage函数中,我们将PipeInformation实例的数据添加到WriteDataBlock的实例中去,添加方式是通过调用WriteDataBlock下的各种WriteXXX函数来完成的。接下来我们调用了Bentley.DgnPlatformNET.Elements.Element的AppendLinkage函数。AppendLinkage函数的第一个参数类型是“ushort”,这个参数指定了一个ID值,用以标识我们填加的自定义属性,此ID值不可与其他Linkage属性的ID值重复。此时我们的自定义属性仅仅是添加到了加载到内存中的元素上,在CreateLinkage函数的最后,调用了Bentley.DgnPlatformNET.Elements.Element的ReplaceInModel函数才真正将自定义属性保存到了dgn文件中。读取自定义属性时,我们调用了ReadLinkage函数,函数中通过Bentley.DgnPlatformNET.Elements.Element的GetLinkage获取到了我们在元素上添加的自定义属性。GetLinkage需要一个ID值作为参数,这个ID值跟我们添加自定义属性时传递给AppendLinkage的第一个参数一致。GetLinkage返回一个ReadDataBlock的实例,通过此实例我们调用其下的各ReadXXX函数可以读取我们添加的自定义属性。读取属性时需要注意的一点是,读取属性的顺序跟我们添加自定义属性时添加的属性要一致。我们在添加自定义属性时,是按照PipeInformation的Diameter、Temperature、Material成员变量顺序依次调用WriteDouble、WriteDouble、WriteString添加的,读取时我们也是依次调用ReadDouble、ReadDouble、ReadString按照Diameter、Temperature、Material的顺序读取的,这个顺序一定要前后对应的,否则就会读取到错误的数据。最后我们在Mstn的消息中心中输出了我们读取到的自定义属性。如果您在VS中编写读取自定义Linkage属性时,可能会注意到GetLinkage函数两个重载形式,如下所示:

public ReadDataBlock GetLinkage(ushort linkageId);
public ReadDataBlock GetLinkage(ushort linkageId, int index);

我们上面的例子代码中调用的是第一个,第二个重载函数多了一个“int index”的参数。我们在添加Linkage属性时,同一ID可以添加多份数据。在获取时,就可以通过这个“index”参数来指定获取同一ID的哪一份属性。删除自定义属性调用的是Bentley.DgnPlatformNET.Elements.Element的DeleteLinkage函数,与添加自定义属性一样,最后需要调用ReplaceInModel函数,将删除的操作写回保存到dgn文件中。DeleteLinkage函数与GetLinkage函数类似,也有两个重载形式,主要区别就是多了“index”参数。更新自定义属性比较特殊,Bentley.DgnPlatformNET.Elements.Element下没有直接更新的函数,所以我们是先删除后添加来实现与更新相同的效果。如下链接可以下载到例子代码的VS完整项目文件。

communities.bentley.com/.../LinkageCRUDDemo.7z