我们在上一篇文章里利用Windows Forms Designer做了一个简单的表单设计器,但这个设计器还存在一些问题,比如控件不能自动命名;文档窗口不会自动加入dirty标记;不能undo/redo和copy/paste;不能保存和读取数据等等。这一篇我们来逐一解决这些问题。
控件自动命名
从toolbox里拖入一个控件时,如果想让控件自动命名,我们需要往DesignerHost里加一个INameCreationService的服务,我没有研究过为什么BasicDesignerLoader不默认帮我们加上,不过好在msdn上有一个例子,我稍微简化了一下它,如下:
using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Collections.Generic;
using System.Collections;
namespace Company.WinFormDesigner
{
class NameCreationService : INameCreationService
{
#region INameCreationService 成员
public string CreateName(IContainer container, Type dataType)
{
IList<string> list = new List<string>();
for (int i = 0; i < container.Components.Count; i++)
{
list.Add(container.Components[i].Site.Name);
}
return CreateNameByList(list, dataType.Name);
}
public bool IsValidName(string name)
{
//name is always valid
return true;
}
public void ValidateName(string name)
{
//do nothing
}
#endregion
/// <summary>
/// 创建一个基于baseName并且在array中不存在的名称
/// </summary>
public static string CreateNameByList(IList<string> list, string baseName)
{
int uniqueID = 1;
bool unique = false;
while (!unique)
{
unique = true;
foreach (string s in list)
{
if (s.StartsWith(baseName + uniqueID.ToString()))
{
unique = false;
uniqueID++;
break;
}
}
}
return baseName + uniqueID.ToString();
}
}
}
我们需要把它的实例加到DesignerHost中。在DesignerLoader中重写Initialize方法:
protected override void Initialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService), new NameCreationService());
}
按ctrl + F5运行,启动VS实验室之后,打开一个.form文件,在工具箱中拖入一个Button,看,是不是已经自动有了Name了呢?
支持Undo/Redo
这个问题纠结了我好久,因为实在有太多的方法实现Undo和Redo了,最后通过reflector才找到最佳的解决方案。和控件的自动命名一样,不能Undo/Redo也是由于少了一个Service:ComponentSerializationService。.net framework提供了一个默认的实现:System.ComponentModel.Design.Serialization.CodeDomComponentSerializationService。我一度以为不能直接使用这个类,因为我们并没有CodeDom,不过在尝试了很多方法后,最后在无奈的情况下才去使用这个类,居然成功了,看来绝不能以貌取人。由于已经有实现了,我们只需要把它加到DesignerHost里就行了,依然要修改DesignerLoader的Initialize方法:
protected override void Initialize()
{
base.Initialize();
LoaderHost.AddService(typeof(INameCreationService), new NameCreationService());
LoaderHost.AddService(typeof(ComponentSerializationService), new CodeDomComponentSerializationService(LoaderHost));
}
大家可以测试一下效果,真的可以undo/redo了,呵呵。
支持Copy/Paste
到目前为止,我们的设计器似乎不支持复制和粘贴。选中一个控件后,复制的按钮是可用的,但粘贴却一直是灰色的,这是怎么回事呢?没错,你猜对了,DesignerHost里少了相应的Service。这个Service叫做System.ComponentModel.Design.Serialization.IDesignerSerializationService。这个接口有两个方法,分别处理序列化和反序列化的工作,我没有找到.net framework里公开的实现,所以我们不得不自己实现这个接口了。不过好在我们可以充分利用undo/redo里提到的ComponentSerializationService来帮助我们去序列化和反序列化控件。我的实现类如下:
using System;
using System.ComponentModel.Design.Serialization;
using System.ComponentModel.Design;
using System.Collections;
namespace Company.WinFormDesigner
{
class DesignerSerializationService : IDesignerSerializationService
{
private IServiceProvider serviceProvider;
private ComponentSerializationService serializer;
public DesignerSerializationService(IServiceProvider sp)
{
serviceProvider = sp;
serializer = serviceProvider.GetService(typeof(ComponentSerializationService)) as ComponentSerializationService;
}
#region IDesignerSerializationService 成员
public ICollection Deserialize(object serializationData)
{
if (serializer != null && serializationData is SerializationStore)
{
return serializer.Deserialize((SerializationStore)serializationData);
}
return null;
}
public object Serialize(ICollection objects)
{
if (objects == null)
{
objects = new object[0];
}
if (serializer != null)
{
SerializationStore store = serializer.CreateStore();
using (store)
{
foreach (object obj in objects)
{
serializer.Serialize(store, obj);
}
}
return store;
}
return null;
}
#endregion
}
}
接下来,我们需要把它加到DesignerHost里: