您的位置:首页 > 其它

VsxHowTo -- 把Windows Forms Designer作为自己的编辑器(2)

2010-08-06 08:32 791 查看
我们在上一篇文章里利用WindowsFormsDesigner做了一个简单的表单设计器,但这个设计器还存在一些问题,比如控件不能自动命名;文档窗口不会自动加入dirty标记;不能undo/redo和copy/paste;不能保存和读取数据等等。这一篇我们来逐一解决这些问题。

控件自动命名

从toolbox里拖入一个控件时,如果想让控件自动命名,我们需要往DesignerHost里加一个INameCreationService的服务,我没有研究过为什么BasicDesignerLoader不默认帮我们加上,不过好在msdn上有一个例子,我稍微简化了一下它,如下:

usingSystem;
usingSystem.ComponentModel;
usingSystem.ComponentModel.Design.Serialization;
usingSystem.Collections.Generic;
usingSystem.Collections;
namespaceCompany.WinFormDesigner
{
classNameCreationService:INameCreationService
{
#regionINameCreationService成员
publicstringCreateName(IContainercontainer,TypedataType)
{
IList<string>list=newList<string>();
for(inti=0;i<container.Components.Count;i++)
{
list.Add(container.Components[i].Site.Name);
}
returnCreateNameByList(list,dataType.Name);
}
publicboolIsValidName(stringname)
{
//nameisalwaysvalid
returntrue;
}
publicvoidValidateName(stringname)
{
//donothing
}
#endregion
///<summary>
///创建一个基于baseName并且在array中不存在的名称
///</summary>
publicstaticstringCreateNameByList(IList<string>list,stringbaseName)
{
intuniqueID=1;
boolunique=false;
while(!unique)
{
unique=true;
foreach(stringsinlist)
{
if(s.StartsWith(baseName+uniqueID.ToString()))
{
unique=false;
uniqueID++;
break;
}
}
}
returnbaseName+uniqueID.ToString();
}
}
}

我们需要把它的实例加到DesignerHost中。在DesignerLoader中重写Initialize方法:

protectedoverridevoidInitialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService),newNameCreationService());
}

按ctrl+F5运行,启动VS实验室之后,打开一个.form文件,在工具箱中拖入一个Button,看,是不是已经自动有了Name了呢?

支持Undo/Redo

这个问题纠结了我好久,因为实在有太多的方法实现Undo和Redo了,最后通过reflector才找到最佳的解决方案。和控件的自动命名一样,不能Undo/Redo也是由于少了一个Service:ComponentSerializationService。.netframework提供了一个默认的实现:System.ComponentModel.Design.Serialization.CodeDomComponentSerializationService。我一度以为不能直接使用这个类,因为我们并没有CodeDom,不过在尝试了很多方法后,最后在无奈的情况下才去使用这个类,居然成功了,看来绝不能以貌取人。由于已经有实现了,我们只需要把它加到DesignerHost里就行了,依然要修改DesignerLoader的Initialize方法:

protectedoverridevoidInitialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService),newNameCreationService());
LoaderHost.AddService(typeof(ComponentSerializationService),newCodeDomComponentSerializationService(LoaderHost));
}


大家可以测试一下效果,真的可以undo/redo了,呵呵。

支持Copy/Paste

到目前为止,我们的设计器似乎不支持复制和粘贴。选中一个控件后,复制的按钮是可用的,但粘贴却一直是灰色的,这是怎么回事呢?没错,你猜对了,DesignerHost里少了相应的Service。这个Service叫做System.ComponentModel.Design.Serialization.IDesignerSerializationService。这个接口有两个方法,分别处理序列化和反序列化的工作,我没有找到.netframework里公开的实现,所以我们不得不自己实现这个接口了。不过好在我们可以充分利用undo/redo里提到的ComponentSerializationService来帮助我们去序列化和反序列化控件。我的实现类如下:

usingSystem;
usingSystem.ComponentModel.Design.Serialization;
usingSystem.ComponentModel.Design;
usingSystem.Collections;
namespaceCompany.WinFormDesigner
{
classDesignerSerializationService:IDesignerSerializationService
{
privateIServiceProviderserviceProvider;
privateComponentSerializationServiceserializer;
publicDesignerSerializationService(IServiceProvidersp)
{
serviceProvider=sp;
serializer=serviceProvider.GetService(typeof(ComponentSerializationService))asComponentSerializationService;
}
#regionIDesignerSerializationService成员
publicICollectionDeserialize(objectserializationData)
{
if(serializer!=null&&serializationDataisSerializationStore)
{
returnserializer.Deserialize((SerializationStore)serializationData);
}
returnnull;
}
publicobjectSerialize(ICollectionobjects)
{
if(objects==null)
{
objects=newobject[0];
}
if(serializer!=null)
{
SerializationStorestore=serializer.CreateStore();
using(store)
{
foreach(objectobjinobjects)
{
serializer.Serialize(store,obj);
}
}
returnstore;
}
returnnull;
}
#endregion
}
}

接下来,我们需要把它加到DesignerHost里:

protectedoverridevoidInitialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService),newNameCreationService());
LoaderHost.AddService(typeof(ComponentSerializationService),newCodeDomComponentSerializationService(LoaderHost));
LoaderHost.AddService(typeof(IDesignerSerializationService),newDesignerSerializationService(LoaderHost));
}

SetDirty

设计器里的控件改变之后,我们希望在documentwindow那里能出现dirty的标记。为了实现这个功能,我们需要修改在上一篇中创建的DocumentData类,这个类实现了IVsPersistDocData接口,其中IsDocDataDirty就是用来判断当前的文档是否是dirty的。让我们修改一些这个类:

namespaceCompany.WinFormDesigner
{
classDocumentData:IVsPersistDocData
{
....
publicboolDirty{get;set;}
intIVsPersistDocData.IsDocDataDirty(outintpfDirty)
{
pfDirty=Dirty?1:0;
returnVSConstants.S_OK;
}
....
}
}

我们在DocumentData里添加了一个Dirty的bool属性,并且修改了IVsPersistDocData.IsDocDataDirty,根据Dirty的属性值来确定pfDirty的值。

下面我们需要在控件修改的时候给这个Dirty属性赋值。怎样捕获控件修改的事件呢?可以通过IComponentChangeService服务的ComponentChanged事件来捕获。在DesignerLoader的初始化方法里,取得这个服务,并添加ComponentChanged的事件处理:

protectedoverridevoidInitialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService),newNameCreationService());
LoaderHost.AddService(typeof(ComponentSerializationService),newCodeDomComponentSerializationService(LoaderHost));
LoaderHost.AddService(typeof(IDesignerSerializationService),newDesignerSerializationService(LoaderHost));
IComponentChangeServiceservice=LoaderHost.GetService(typeof(IComponentChangeService))asIComponentChangeService;
service.ComponentChanged+=newComponentChangedEventHandler(service_ComponentChanged);
}


然后就可以在service_ComponentChanged方法里给DocumentData的Dirty属性赋值了,当然,在这之前,我们需要修改一下DesignerLoader的构造函数,以便我们可以取得DocumentData的引用:

classDesignerLoader:BasicDesignerLoader
{
privateDocumentData_data;
publicDesignerLoader(DocumentDatadata)
{
_data=data;
}
protectedoverridevoidInitialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService),newNameCreationService());
LoaderHost.AddService(typeof(ComponentSerializationService),newCodeDomComponentSerializationService(LoaderHost));
LoaderHost.AddService(typeof(IDesignerSerializationService),newDesignerSerializationService(LoaderHost));
IComponentChangeServiceservice=LoaderHost.GetService(typeof(IComponentChangeService))asIComponentChangeService;
service.ComponentChanged+=newComponentChangedEventHandler(service_ComponentChanged);
}
voidservice_ComponentChanged(objectsender,ComponentChangedEventArgse)
{
_data.Dirty=true;
}
protectedoverridevoidPerformFlush(IDesignerSerializationManagerserializationManager)
{
}
protectedoverridevoidPerformLoad(IDesignerSerializationManagerserializationManager)
{
LoaderHost.Container.Add(newUserControl());
}
}

最后,记得要修改EditorFactory里构造DesignerLoader时的代码:

...
vardata=newDocumentData();
vardesignerLoader=newDesignerLoader(data);
...


编译并运行项目,可以然后拖一个控件到设计器,Dirty的标记出来了。

保存/加载文档

要实现文档的保存与加载,需要让DocumentData实现IVsPersistDocData和IPersistFileFormat接口。在上次我们已经简单实现了IVsPersistDocData了,现在我们需要再让他实现IPersistFileFormat接口。用于保存的方法是IVsPersistDocData.SaveDocData和IPersistFileFormat.Save。

保存文档,无非就是把DesignerHost中正在设计的UserControl以及它的子控件用某种方式序列化到文件里,而加载文档则相反:读取文件,并反序列化成控件,并把控件加到DesignerHost里。

我们定义一个类用于处理控件的序列化和反序列化:

usingSystem.Windows.Forms;
namespaceCompany.WinFormDesigner
{
classControlSerializer
{
publicstringDocumentMoniker{get;set;}
publicControlSerializer(stringdocumentMoniker)
{
DocumentMoniker=documentMoniker;
}
publicControlDeserialize()
{
/*
*读取文件DocumentMoniker的内容,并把它反序列化成Control。
*下面的代码只是模拟这个过程,并没有真正读取文件并反序列化
*注意控件有可能是复合控件,这种控件的子控件是不需要加到DesignerHost里的,
*所以我给控件的Tag属性设了一个Designable的字符串,目的是在后面
*区分出哪些控件是需要设计的,哪些控件是属于不需要设计的
**/
conststringdesignable="Designable";
UserControluc=newUserControl();
uc.Controls.Add(newLabel{Text="Label1",Tag=designable});
uc.Controls.Add(newTextBox{Tag=designable,Location=newSystem.Drawing.Point(200,500)});
returnuc;
}
publicvoidSerialize(Controlcontrol)
{
/*
*序列化control,并保存到文件DocumentMoniker中。有些属性比如Site之类的,最好不要序列化
*下面的代码只是模拟这个过程,没有真正的序列化控件
**/
MessageBox.Show("序列化。。。");
}
}
}

注意到我并没有实现序列化和反序列化的真正逻辑,因为这和vsx无关,大家可以自己去实现。不过要注意的是Control的部分属性是没有必要序列化到文件里的,所以在序列化的时候要过滤些属性,例如根据BrowsableAttribute来决定哪些属性可以被序列化。

我们需要把文件的路径传给DocumentData,并且在DocumentData里定义一个Control类型的属性:

classDocumentData:IVsPersistDocData,IPersistFileFormat
{
...
publicControlControl{get;set;}
publicstringDocumentMoniker{get;set;}
publicDocumentData(stringdocPath)
{
DocumentMoniker=docPath;
_fileName=docPath;
}
...
}

在DesignerLoader的PerformLoad方法里,调用反序列化方法,并把反序列化出来的控件加到DesignerHost里:

classDesignerLoader:BasicDesignerLoader
{
...
protectedoverridevoidPerformLoad(IDesignerSerializationManagerserializationManager)
{
ControlSerializerserializer=newControlSerializer(_data.DocumentMoniker);
Controlcontrol=serializer.Deserialize();
//把控件的引用传给DocumentData,这样它保存的时候就可以序列化这个控件了
_data.Control=control;
AddControl(control);
}
privatevoidAddControl(Controlparent)
{
LoaderHost.Container.Add(parent);
foreach(Controlchildinparent.Controls)
{
if(child.Tag=="Designable")
{
AddControl(child);
}
}
}
}

在DocumentData的Save方法里,调用如下的代码:

classDocumentData:IVsPersistDocData,IPersistFileFormat
{
....
privatevoidSaveFile(stringfileName)
{
ControlSerializerserializer=newControlSerializer(fileName);
serializer.Serialize(Control);
Dirty=false;
}
....
}

这样就可以保存和加载文档了。

源码下载:WinFormDesigner_V2
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: