Post by "xaep", 04-05-2007, 14:50
-----------------------------------------------------
控件的Collection类型的属性增加缺省项的问题
WinForm控件中经常有一些Collection类型的属性,比如TabControl,在从ToolBox拖到Form上时,会缺省生成两个TabPage,这样会方便用户使用。原因是在TabControl在他的Designer初始化组件的时候(InitializeNewComponent)创建了两个TabPage。当然还有其他方法比如利用ToolBoxItem等这里就不细讲了。然而我们发现,如果用Code创建一个TabControl,加到Form上是没有TabPage的,这样就给RunTime创建控件带来了一些不便。那如何让不论是DesignTime通过IDE创建,还是RunTime通过代码创建,都可以有缺省项生成呢?
这太简单了,只要在TabControl的构造器里创建两个TabPage不就行了吗。可问题来了,构造器里创建的TabPage用户DesignTime下不能对他们进行设置,如果通过属性的UITypeEditor对他们进行修改,就必然生成代码,在程序运行起来后,会发现有多余的TabPage出现。细节不讲,大家写个简单的控件试一下就行了。
那如何做呢?方法可能有多种,比如将属性改为可读写,通过每次构造一个新的Collection赋值给属性,因为不是通常的方式,而且需要对加入的项目对象提供全参的构造器等,不做细讲。这里介绍一种利用.NET中ControlDesigner以及DesignerHost生成代码的一些技巧,简单绕过问题的方法。
我们先写一个TestControl从Control继承,还有他的Designer
- [Designer("ConstructorInDesignTime.TestControlDesigner")]
- class TestControl : Control
- {
- }
- public class TestControlDesigner : ControlDesigner
- {
- }
复制代码 再来看看要求:
1. 我们需要在RunTime时用户构建控件的时候加入缺省的Button,那就建立如下构造器:
- public TestControl()
- {
- //Add default button
- Button defaultButton = new Button();
- defaultButton.Location = new Point(10, 10);
- this.Controls.Add(defaultButton);
- }
复制代码 2. 为了能够利用.NET标准的生成代码的功能,利用Designer的InitializeNewComponent时机,加入缺省的Button。
- public override void InitializeNewComponent(System.Collections.IDictionary defaultValues)
- {
- base.InitializeNewComponent(defaultValues);
- TestControl component = (TestControl)base.Component;
- IDesignerHost service = (IDesignerHost)this.GetService(typeof(IDesignerHost));
- Button defaultButton = service.CreateComponent(typeof(Button)) as Button;
- defaultButton.Location = new Point(20, 20);
- component.Controls.Add(defaultButton);
- }
复制代码 3. 为了避免DesignTime自动生成的控件构建代码不加入缺省项,我们需要他们自动生成一个有参的构造器,这个构造器中不创建缺省项。
如果能做到以上3点,原则上就可以达到效果。1和2都没有问题,那3有可能吗?
答,yes。通过查看ErrorProvider生成的代码,我们发现他就生成了一个有参的构造器代码:
- this.errorProvider1 = new System.Windows.Forms.ErrorProvider(this.components);
复制代码 为什么呢?在反编译了ComponentCodeDomSerializer对象的Serialize方法后,发现创建对象的代码是这样的:
- if ((TypeDescriptor.GetReflectionType(value).GetConstructor(BindingFlags.ExactBinding
- | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly
- , null, _containerConstructor, null) != null) && (container != null))
- {
- right = new CodeObjectCreateExpression(type, new CodeExpression[] {
- base.SerializeToExpression(manager, container) });
- }
- else
- {
- bool isComplete;
- right = base.SerializeCreationExpression(manager, value, out isComplete);
- }
复制代码 其中_containerConstructor的值是:
- _containerConstructor = new Type[] { typeof(IContainer) };
复制代码 也就是说CodeDom会优先使用有IContainer类型参数的构造器生成代码。机会来了:-)给控件增加这样一个构造器:
- public TestControl(IContainer Container)
- {
- }
复制代码 果然DesignTime生成的代码就变了:
- this.testControl1 = new ConstructorInDesignTime.TestControl(this.components);
复制代码 成功了!
但,问题又出现了,在IDE中编辑的Form关掉,再打开,发现多了一个缺省项。
原来,IDE在Deserialize代码的时候,并没有按照那个有参的构造器去创建控件,而是使用了无参的构造器,没有细看,也许是个bug。
没希望了吗?
不,发现DesignTime控件会创建Designer,而在Designer有一个Initialize方法,在这个时机,其他的缺省项还没有被实例化加入,只有无参构造器创建的一个缺省项,那么将这个多于出来的缺省项删掉,就OK了。
- public override void Initialize(IComponent component)
- {
- base.Initialize(component);
- //Remove default button
- (component as TestControl).Controls.Clear();
- }
复制代码 Attach file is sample code. |