在前两篇里,我向大家介绍了如何把vs的windows forms designer作为自己的自定义编辑器,这这篇文章里我再介绍一些大家可能关心的和设计器相关的其他问题。
给toolbox添加自己的控件
首先我们要开发自己的控件。我们在WinFormsDesigner项目里添加一个Controls文件夹,用于放置自己的控件。然后添加一个MyTextBox的控件,继承自TextBox:
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
namespace Company.WinFormDesigner.Controls
{
[ToolboxBitmap(typeof(TextBox))]
[DisplayName("我的文本框")]
[Description("我的文本框控件")]
[ToolboxItem(true)]
public class MyTextBox : TextBox
{
public string MyProperty { get; set; }
}
}
我们需要一段代码来把这个控件自动加道toolbox里。这里需要用到System.Drawing.Design.IToolboxService和Microsoft.VisualStudio.Shell.Interop.IVsToolbox这两个服务以及Package.ToolboxInitialized和Package.ToolboxUpgraded这两个事件。目的是在初始化工具箱和重置工具箱的时候调用我们的逻辑。我们在Package的Initialize方法中来注册这两个事件:
protected override void Initialize()
{
Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
base.Initialize();
//注册Editor Factory
RegisterEditorFactory(new DocumentEditorFactory());
//注册toolbox事件
ToolboxInitialized += OnRefreshToolbox;
ToolboxUpgraded += OnRefreshToolbox;
}
void OnRefreshToolbox(object sender, EventArgs e)
{
IToolboxService tbService =
GetService(typeof(IToolboxService)) as IToolboxService;
IVsToolbox toolbox = GetService(typeof(IVsToolbox)) as IVsToolbox;
var assembly = typeof(WinFormDesignerPackage).Assembly;
Type[] types = assembly.GetTypes();
List<ToolboxItem> tools = new List<ToolboxItem>();
foreach (var type in types)
{
try
{
//要使用ToolboxService,需要添加System.Drawing.Design引用
ToolboxItem tool = ToolboxService.GetToolboxItem(type);
if (tool == null) continue;
AttributeCollection attributes = TypeDescriptor.GetAttributes(type);
//DisplayNameAttribute不能够自动附加到ToolboxItem的DisplayName上,所以我们在这里要给它赋一下值
var displayName = attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
if (displayName != null)
{
tool.DisplayName = displayName.DisplayName;
}
tools.Add(tool);
}
catch
{
}
}
var category = "我的控件";
toolbox.RemoveTab(category);
foreach (var tool in tools)
{
tbService.AddToolboxItem(tool, category);
}
tbService.Refresh();
}
OnRefreshToolbox方法负责读取当前程序集里所有附有ToolboxItemAttribute(true)的组件,并利用ToolboxService的GetToolboxItem方法取得一个type对应的ToolboxItem。我们在MyTextBox上添加了DisplayName和Description两个Attribute,目的是想自定义ToolboxItem在显示的时候的名称和描述,但ToolboxService.GetToolboxItem似乎忽略了DisplayNameAttribute,所以我们不得不在多写两句话来给ToolboxItem的DisplayName属性赋值。
要想让toolbox里显示我们的toolboxitem,还需要在Package注册的时候告诉vs,我们的Package是会提供ToolboxItem的,所以我们要给Package加上ProvideToolboxItemsAttribute,如下:
[PackageRegistration(UseManagedResourcesOnly = true)]
[DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
[InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
[ProvideLoadKey("Standard", "1.0", "WinFormDesigner", "Company", 1)]
[Guid(GuidList.guidWinFormDesignerPkgString)]
//将EditorFactory和文件扩展名关联起来
[ProvideEditorExtension(typeof(DocumentEditorFactory), ".form", 100)]
//添加一条注册表项,告诉vs我们的Package会提供ToolboxItem
[ProvideToolboxItems(1, true)]
public sealed class WinFormDesignerPackage : Package
{
...
}
ProvideToolboxItems的第一个参数1是干嘛的呢?它表示ToolboxItem的版本号:当我们改变了MyTextBox的DisplayName属性,或者新增加了一个控件的时候,工具窗里很有可能出现的还是旧的ToolboxItem,当遇到这个情况的时候,我们就可以把这个参数1改成大一点的数字,比如2。vs在初始化toolbox的时候发现这个数字变大了,就会重新调用OnRefreshToolbox方法,这样toolbox里面的内容就更新了。当然,我们也可以不改变这个数字,而是在toolbox那里点鼠标右键,选择“重置工具箱”,也可以更新我们的toolbox。
编译我们的Package之后,用vs实验室打开一个.form文件,我们的toolboxitem就出现在toolbox中了:
让toolbox只显示我们的控件
现在toolbox可以显示我们的控件了,但它同时也显示了很多vs内置的其它控件。我们有时候想要的效果是:当打开.form文件后,toolbox里只显示我们自己的控件,隐藏掉其他的控件。可以用ToolboxItemFilterAttribute来实现过滤。
简单的说一下ToolboxItemFilterAttribute,不过我对它的理解的不是很透彻,只知道大概的意思。toolbox会根据当前的DesignerHost里的RootDesigner的ToolboxItemFilter,和所有的ToolboxItem的ToolboxItemFilter相匹配,匹配通过的就显示,匹配不通过的不显示。
所以我们需要同时给RootDesigner和希望显示的控件添加相匹配的ToolboxItemFilter。先给控件加吧,稍后再给RootDesigner加:
[ToolboxBitmap(typeof(TextBox))]
[DisplayName("我的文本框")]
[Description("我的文本框控件")]
[ToolboxItem(true)]
[ToolboxItemFilter("MyControls")]
public class MyTextBox : TextBox
{
public string MyProperty { get; set; }
}
这样就给MyTextBox这个控件指定了Filter为“MyControls”,下面我们要给RootDesigner加。
RootDesigner是当前DesignerHost里的RootComponent的Designer。所谓RootComponent,其实就是第一个被加到DesignerHost里的组件。所以在我们这个例子里,RootComponent是一个UserControl。怎样才能给UserControl对应的RootDesigner添加ToolboxItemFilterAttribute呢?这里有两个方法:
- 利用TypeDescriptor.AddAttribute方法来动态的为RootDesigner添加ToolboxItemFilterAttribute。
- 做一个控件,继承UserControl,把它作为RootComponent,给这个控件指定自己的Designer,然后就可以在这个Designer上添加ToolboxItemFilterAttribute了。
我们先来看一下第一种方法:用TypeDescriptor.AddAttribute方法为RootDesigner动态的添加Attribute。这个方法很简单,在DesignerLoader的PerformLoad里:
class DesignerLoader : BasicDesignerLoader
{
protected override void PerformLoad(IDesignerSerializationManager serializationManager)
{
ControlSerializer serializer = new ControlSerializer(_data.DocumentMoniker);
Control control = serializer.Deserialize();
//把控件的引用传给DocumentData,这样它保存的时候就可以序列化这个控件了
_data.Control = control;
AddControl(control);
var rootDesigner = LoaderHost.GetDesigner(LoaderHost.RootComponent);
TypeDescriptor.AddAttributes(rootDesigner, new ToolboxItemFilterAttribute("MyControls", ToolboxItemFilterType.Require) );
}
}
编译并运行项目,打开.form文件后,可以看到toolbox里只剩下我们的MyTextBox了。如果工具箱里什么都没有显示或显示的不对,那就把ProvideToolboxItems的值改大一点,或者重置一下工具箱。
现在我们再来看一下第二种方法,这种方法比第一种要复杂一些,不过很多情况下需要采用这种方法,因为我们可能需要自定义RootComponent和RootDesigner。添加一个控件,继承自UserControl,并为它做一个RootDesigner,继承自DocumentDesigner:
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace Company.WinFormDesigner.Controls
{
[Designer(typeof(MyRootControlDesigner), typeof(IRootDesigner))]
class MyRootControl : UserControl
{
}
[ToolboxItemFilter("MyControls", ToolboxItemFilterType.Require)]