找回密码
 立即注册

QQ登录

只需一步,快速开始

graper

高级会员

45

主题

63

帖子

1348

积分

高级会员

积分
1348

活字格认证

graper
高级会员   /  发表于:2009-12-11 16:17  /   查看:6721  /  回复:0
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

  1.     [Designer("ConstructorInDesignTime.TestControlDesigner")]
  2.     class TestControl : Control
  3.     {
  4.     }

  5.     public class TestControlDesigner : ControlDesigner
  6.     {
  7.     }
复制代码
再来看看要求:
1. 我们需要在RunTime时用户构建控件的时候加入缺省的Button,那就建立如下构造器:

  1.         public TestControl()
  2.         {
  3.             //Add default button
  4.             Button defaultButton = new Button();
  5.             defaultButton.Location = new Point(10, 10);
  6.             this.Controls.Add(defaultButton);
  7.         }
复制代码
2. 为了能够利用.NET标准的生成代码的功能,利用Designer的InitializeNewComponent时机,加入缺省的Button。

  1.      public override void InitializeNewComponent(System.Collections.IDictionary defaultValues)
  2.         {
  3.             base.InitializeNewComponent(defaultValues);
  4.             TestControl component = (TestControl)base.Component;
  5.             IDesignerHost service = (IDesignerHost)this.GetService(typeof(IDesignerHost));
  6.             Button defaultButton = service.CreateComponent(typeof(Button)) as Button;
  7.             defaultButton.Location = new Point(20, 20);
  8.             component.Controls.Add(defaultButton);
  9.         }
复制代码
3. 为了避免DesignTime自动生成的控件构建代码不加入缺省项,我们需要他们自动生成一个有参的构造器,这个构造器中不创建缺省项。
如果能做到以上3点,原则上就可以达到效果。1和2都没有问题,那3有可能吗?
答,yes。通过查看ErrorProvider生成的代码,我们发现他就生成了一个有参的构造器代码:

  1. this.errorProvider1 = new System.Windows.Forms.ErrorProvider(this.components);
复制代码
为什么呢?在反编译了ComponentCodeDomSerializer对象的Serialize方法后,发现创建对象的代码是这样的:

  1.     if ((TypeDescriptor.GetReflectionType(value).GetConstructor(BindingFlags.ExactBinding
  2.         | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly
  3.         , null, _containerConstructor, null) != null) && (container != null))
  4.     {
  5.         right = new CodeObjectCreateExpression(type, new CodeExpression[] {
  6.             base.SerializeToExpression(manager, container) });
  7.     }
  8.     else
  9.     {
  10.         bool isComplete;
  11.         right = base.SerializeCreationExpression(manager, value, out isComplete);
  12.     }
复制代码
其中_containerConstructor的值是:

  1. _containerConstructor = new Type[] { typeof(IContainer) };
复制代码
也就是说CodeDom会优先使用有IContainer类型参数的构造器生成代码。机会来了:-)给控件增加这样一个构造器:

  1.       public TestControl(IContainer Container)
  2.         {
  3.         }
复制代码
果然DesignTime生成的代码就变了:

  1. this.testControl1 = new ConstructorInDesignTime.TestControl(this.components);
复制代码
成功了!
但,问题又出现了,在IDE中编辑的Form关掉,再打开,发现多了一个缺省项。
原来,IDE在Deserialize代码的时候,并没有按照那个有参的构造器去创建控件,而是使用了无参的构造器,没有细看,也许是个bug。
没希望了吗?
不,发现DesignTime控件会创建Designer,而在Designer有一个Initialize方法,在这个时机,其他的缺省项还没有被实例化加入,只有无参构造器创建的一个缺省项,那么将这个多于出来的缺省项删掉,就OK了。

  1.       public override void Initialize(IComponent component)
  2.         {
  3.             base.Initialize(component);
  4.             //Remove default button
  5.             (component as TestControl).Controls.Clear();
  6.         }
复制代码
Attach file is sample code.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部