今天你写控件了吗?----ASP.net控件开发系列(七)

ComponentEditor与Designer开发详解
本文围绕ComponentEditor和Designer展开。介绍了ComponentEditor是用于操作整个控件的组件编辑器,通过实例说明实现它的步骤。还阐述了Designer用于管理设计时控件呈现行为,以WebForm控件为例,讲述定制设计器动词实现右键打开组件编辑器等功能,以及设计期“所见即所得”的相关注意点。

ComponentEditor

 “第二选择”

 上篇中,关于Editor说了那么多,完了吗?没有,上篇仅仅介绍了操作属性的UITypeEditor而已。还记得DataGrid的属性窗口的下方的“属性生成器...”吗?


DataGrid.gif


当我们点击“属生生成器...”后,IDE弹出一个窗体,提供我们全方位的操作DataGrid属性的交互界面,这个界面比PropertyGrid提供更方便易用的,更符合DataGrid“国情”。所以,用户有了属性窗格之外的第二个选择。
 那么这个“属性生成器...”是什么呢?它也是一个Editor,只是它不是一个UITypeEditor,而是一个ComponentEditor,对,它是一个组件编辑器,它不是用来编辑某个属性的,而是用来操作整个控件的。

下面我们就以实例来看看要实现组件编辑器,要做哪些工作?

控件主体类

None.gif    [
None.gif    Designer(
typeof(MyDesigner)),
None.gif    Editor(
typeof(MyComponentEditor), typeof(ComponentEditor))
None.gif    ]
ExpandedBlockStart.gifContractedBlock.gif    
public class MyControl :WebControl dot.gif{
InBlock.gif dot.gifdot.gif
ExpandedBlockEnd.gif    }

None.gif

在这里我们用到了两个Attribute来描述我们的控件主体类:Designer和Editor,第一个为控件主体类关联一个设计器,这里之所以要用到设计器,因为要方便的调用组件编辑器要借助Designer类。第二个Attribute为控件主体类关联了一个编辑器,大家可以看到它的第二个参数变成了ComponentEditor而不是UITypeEditor。

编辑器窗体类

ExpandedBlockStart.gifContractedBlock.gif    public class MyComponentEditorForm : System.Windows.Forms.Form dot.gif{
InBlock.gif dot.gifdot.gif
InBlock.gif        
private MyControl _myControl;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public myControlComponentEditorForm(myControl component) dot.gif{
InBlock.gif            InitializeComponent();
InBlock.gif
InBlock.gif            _myControl 
= component;
InBlock.gif  
//用_myControl的属性初始化本窗体上的操作控件(如一些TextBox,还以我以前讲到的PropertyGrid的值)。
ExpandedSubBlockEnd.gif
        }

InBlock.gif 
//以下是用户点击确定完成编辑的逻辑纲要
ExpandedSubBlockStart.gifContractedSubBlock.gif
        private void okButton_Click(object sender, System.EventArgs e) dot.gif
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
这里使用PropertyDescriptor来为Component赋值与直接用 _myControl.Property_1 = textBox1.Text 这样的逻辑来赋值有一个好处,就是支持操作的Undo功能#region 这里使用PropertyDescriptor来为Component赋值与直接用 _myControl.Property_1 = textBox1.Text 这样的逻辑来赋值有一个好处,就是支持操作的Undo功能
InBlock.gif            PropertyDescriptorCollection props 
= TypeDescriptor.GetProperties(_myControl);
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif            
try dot.gif{
InBlock.gif                PropertyDescriptor property_1 
= props["Property_1"];
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if (textProperty != nulldot.gif{
InBlock.gif                    textProperty.SetValue(_myControl, textBox1.Text);
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockStart.gifContractedSubBlock.gif            
catch dot.gif{
ExpandedSubBlockEnd.gif            }

InBlock.gif  dot.gifdot.gif
InBlock.gif
InBlock.gif            DialogResult 
= DialogResult.OK;
InBlock.gif            Close();
ExpandedSubBlockEnd.gif 
#endregion

ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif dot.gifdot.gif
ExpandedBlockEnd.gif    }

None.gif
None.gif

编辑器类

ExpandedBlockStart.gifContractedBlock.gif    public class MyComponentEditor : WindowsFormsComponentEditor dot.gif{
InBlock.gif 
//操作控件主逻辑
ExpandedSubBlockStart.gifContractedSubBlock.gif
        public override bool EditComponent(ITypeDescriptorContext context, object component, IWin32Window owner) dot.gif{
InBlock.gif            MyControl control 
= component as MyControl;
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (Control == nulldot.gif{
InBlock.gif                
throw new ArgumentException("操作对象检查,只能是特定的类""component");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            IServiceProvider site 
= control.Site;//每个控件都有一个Site属性,使用它的GetService方法使得在设计环境下,控件能得到各种设计期服务。如IComponentChangeService、IDesignerEventService、IDesignerHost、IDesignerOptionService等等
InBlock.gif
            
InBlock.gif     IComponentChangeService changeService 
= null;//IComponentChangeService规定当组件被增删改时设计界面的修改,并提供方法来引发 ComponentChanged 或 ComponentChanging 事件。不由.net fw实现,由VS.net实现。
InBlock.gif

InBlock.gif            DesignerTransaction transaction 
= null;//DesignerTransaction提供一种方法来对一系列的设计时操作进行分组,从而提高性能并使得大多数类型的更改都能撤消。
InBlock.gif

InBlock.gif            
bool changed = false;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif            
try dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if (site != nulldot.gif{
InBlock.gif                    IDesignerHost designerHost 
= (IDesignerHost)site.GetService(typeof(IDesignerHost));//规定了支持设计器事务、管理组件和设计器、得到设计器信息。由VS.net实现。
InBlock.gif
                    transaction = designerHost.CreateTransaction("事务分组名");//DesignerTransaction由IDesignerHost得到
InBlock.gif

InBlock.gif                    changeService 
= (IComponentChangeService)site.GetService(typeof(IComponentChangeService));
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (changeService != nulldot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                        
try dot.gif{
InBlock.gif                            changeService.OnComponentChanging(control, 
null);//第二个参数为MemberDescriptor它是EventDescriptor和PropertyDescriptor类的基类表示正在更改的成员。如果此更改与单个成员无关,则它将为空引用。
ExpandedSubBlockEnd.gif
                        }

ExpandedSubBlockStart.gifContractedSubBlock.gif                        
catch (CheckoutException ex) dot.gif{//此处的CheckoutException是签入VSS失败
InBlock.gif
                            if (ex == CheckoutException.Canceled)
InBlock.gif                                
return false;
InBlock.gif                            
throw ex;
ExpandedSubBlockEnd.gif                        }

ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif                
try dot.gif{
InBlock.gif    
//以下代码实现调用一个编写好的编辑器窗口
InBlock.gif
                    MyComponentEditorForm form = new MyComponentEditorForm(control);
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (form.ShowDialog(owner) == DialogResult.OK) dot.gif{//from.ShowDialog(owner)指定from为owner的一个模态窗口。
InBlock.gif
                        changed = true;
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

ExpandedSubBlockStart.gifContractedSubBlock.gif                
finally dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (changed && changeService != nulldot.gif{//如果点击了编辑器窗口的确定,引发已经更改事件
InBlock.gif
                        changeService.OnComponentChanged(Control, nullnullnull);//由于更改不与单个属性有关,所以后面的三个参数都为null
ExpandedSubBlockEnd.gif
                    }

ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockStart.gifContractedSubBlock.gif            
finally dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if (transaction != nulldot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (changed) dot.gif{//一切正常的话,提交设计器事件
InBlock.gif
                        transaction.Commit();
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockStart.gifContractedSubBlock.gif                    
else dot.gif{
InBlock.gif                        transaction.Cancel();
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
return changed;
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif
None.gif

当我们做了这些事后,已经可以使用属性编辑器了,我们可以看到在属性窗格的最后面的属性页面按钮已经可用,我们可以点击这个按钮打开属性编辑器
PropertyPage.gif

Designer

“WYSWYG”


 也许你会说:记得DataGrid的属性编辑器可以用属性窗格下方的超链接和右键菜单打开啊,为什么这里还不行?
要回答这个问题,我们就得用到Designer

 设计器是用来管理设计时控件呈现行为的类。WebForm和WinForm的核心设计器都来自System.ComponentModel.Design.ComponentDesigner,所以两者都有相同的架设,不过两者的引擎却是完全不同的,WebForm使用IE做为引擎,WinForm使用GDI+做为引擎。由于我对WinForm接触不多,所以以下的论述以WebForm控件的Designer开发为内容。
 书接上文,我们还是还是先来看看怎么完成上面的问题。要使用右键打开组件编辑器等功能可以通过定制设设计器动词来实现。
设计器动词
 设计器动词是设计界面中的命命,设计器都提供了一个设计器动词集合DesignerVerbCollection Verbs{get;}
下面来看我们的设计器类如何定制Verbs:
  

ExpandedBlockStart.gifContractedBlock.gif  public class MyControlDesigner : ControlDesigner dot.gif{
InBlock.gif
InBlock.gif        
private DesignerVerbCollection designerVerbs;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public override DesignerVerbCollection Verbs dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if (designerVerbs == nulldot.gif{
InBlock.gif                    designerVerbs 
= new DesignerVerbCollection();
InBlock.gif                    designerVerbs.Add(
new DesignerVerb("属生编辑dot.gif"new EventHandler(this.OnControlPropertyBuilder)));//增加一个动词,关联一个方法
ExpandedSubBlockEnd.gif
                }

InBlock.gif
InBlock.gif                
return designerVerbs;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
private void OnControlPropertyBuilder(object sender, EventArgs e) dot.gif{
InBlock.gif            MyComponentEditor compEditor 
= new MyComponentEditor();
InBlock.gif            compEditor.EditComponent(Component);
ExpandedSubBlockEnd.gif        }

InBlock.gif dot.gifdot.gif
ExpandedBlockEnd.gif    }

None.gif
None.gif

 现在你可以看到,我们在属性窗格的下方和上下文菜单中看到打开编辑器的命令了。
Designer的主要功能其实是实现控件在设计期能“所见即所得”,所以我们对设计期还要有更多的了解,让我们来看以下代码:
 

ExpandedBlockStart.gifContractedBlock.gif       public override void Initialize(IComponent component) dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (!(component is MyControl)) dot.gif{
InBlock.gif                
throw new ArgumentException("Component must be a MyControl control.""component");
ExpandedSubBlockEnd.gif            }

InBlock.gif            
base.Initialize(component);
ExpandedBlockEnd.gif        }

ExpandedBlockStart.gifContractedBlock.gif  
public override string GetDesignTimeHtml() dot.gif{
InBlock.gif            MyControl control 
= (MyControl)Component;
InBlock.gif
InBlock.gif            
string designTimeHtml = String.Empty;
ExpandedSubBlockStart.gifContractedSubBlock.gif            
try dot.gif{
InBlock.gif                designTimeHtml 
= base.GetDesignTimeHtml();
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockStart.gifContractedSubBlock.gif            
catch (Exception e) dot.gif{
InBlock.gif                designTimeHtml 
= GetErrorDesignTimeHtml(e);
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
return designTimeHtml;
ExpandedBlockEnd.gif        }

None.gif
ExpandedBlockStart.gifContractedBlock.gif        
protected override string GetEmptyDesignTimeHtml() dot.gif{
InBlock.gif            
return CreatePlaceHolderDesignTimeHtml("右键点击设置控件的属性. ");
ExpandedBlockEnd.gif        }

None.gif
ExpandedBlockStart.gifContractedBlock.gif        
protected override string GetErrorDesignTimeHtml(Exception e) dot.gif{
InBlock.gif            
return CreatePlaceHolderDesignTimeHtml("生成错误.");
ExpandedBlockEnd.gif        }

None.gif
None.gif

对于这些覆写方法,我们都可以望文生义得知它的意义。
不过有几点是要注意的:
1、如果控件是复合控件(就是多个基本控件合成的控件。),你最好让设它的设计器在GetDesignerTimeHtml方法中先调用一下它的含有确保子控件不为null的方法。
2、ControlDesigner基类的GetDesignTimeHtml方法会调用控件的RenderControl方法返回HTML字符串,所以默认情况下,控件在设计期的样子和运行期的样子会差不多。那么我们有没有办法让设计期和运行期不同呢?当然可以,最简单的办法是overrideGetDesignTimeHtml方法实现自己的呈现逻辑,不过你也可以使用另外一个技巧,那就是运行期是会调用OnPerRender方法再调用Render方法,而默认实现下设计期是没有调用OnPerRender方法的,所以,你可以利用这种差别,方便的使两者同中有异。
另外模板控件的设计类我们留待模板控件专门的文章中再讲述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值