编写CodeSmith自定义属性的编辑器(Writing Custom Property Editors)

CodeSmith模板时,很可能对于使用它的strings或integers属性很满意,但有时你会发现需要创建一个不同类型的属性,可能是一个自定义的类型或者是.NET framework中但是在属性面板中没有提供的类型。在模板中去作这些很简单,但是怎样指定一个类型在运行模板时显示在属性面板中呢?例如创建了一个Person类并且具有很多不同的属性,但是却没有办法让用户去组装这个类……除非创建一个自定义属性编辑器。

NUnit测试基础。(这句翻译的不好,原文:As an example we are going to build a template which accepts an assembly as a property and then using reflection loops through all of the classes, and the methods of those classes, to build NUnit test stubs.)

        首先,我们来关注一下模板的组件属性,暂且不看自定义编写的代码。模板的第一部分是一些声明定义和属性。将属性放在脚本标签中代替使用属性声明,在下一部分将看到这样做的必要。

1
<%@ CodeTemplate Language="C#" TargetLanguage="C#" Descriptinotallow="Builds a class for each class in the assembly, and a test stub for every method." %> 
   
 
    2 
   

 3
<%@ Import NameSpace="System.Reflection" %> 
   
 
    4 
   

 5
<script runat="template"> 
   
 
    6 
   

 7
private 
    Assembly assembly;
 8 
   

 9
public 
    Assembly AssemblyToLoad
10
 
   {
11
      get
{return assembly;}
12
      set
{assembly = value;}
13
} 
   
 
   14 
   

15
</script>


 

assembly中的每一个类创建一个类,为每一个类创建他的方法。然后直接将模板的输出内容放入Visual Studio.NET,然后在编写组件的单元测试时使用向导。

 /Files/Bear-Study-Hard/AssemblyHelper.zip

1
using       System;
 2
using       NUnit.Framework;
 3      

 4
<%      
       5
      foreach(Type T in       AssemblyToLoad.GetTypes())
 6
      
      {
 7
            if(T.IsClass)
 8
            
{
 9
                  %>
10

11
                  [TestFixture]
12
                  public class <%=T.Name%>Tests
13
                  
{
14
                  <%
15
                              MethodInfo[] methods = T.GetMethods ( BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static  );
16
                              foreach(MethodInfo M in methods)
17
                              
{
18
                                    %>
19

20
                                    [Test]
21
                                    public void <%=M.Name%>Test
22
                                    
{
23
                                          //TODO Write this test
24
                                    }                 
25
                                    <%
26
                              }
27

28
                  %>}<%
29
            }
30
      }      
      31
%>

UITypeEditor的类。

1
public class     AssemblyFilePicker : UITypeEditor
2
    {
3
      public AssemblyFilePicker(): base()
4
      
{
5
      }
6
}


UITypeEditor的说明请大家参看M SD N或Visual Studio.NET自带帮助中的说明,其中有详细的例子。

UITypeEditor类的两个不同的方法。第一个需要重载点的方法是GetEditStyle,这个方法是告诉属性面板对于当前类型是用什么类型的编辑器,在这个例子中我们设置编辑类型为Modal。这样大家可以在该属性格子的右边看到一个小按钮,它将引发一个对话框等模式的对话(trigger a modal dialog)。这是我们的GetEditStyle方法:

1
public override       UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) 
2
      {
3
      return UITypeEditorEditStyle.Modal;
4
}


Modal为显示一个省略号按钮。

 

EditValue方法,当用户电击属性时会调用这个方法。按照我们需要加载的组件类型需要创建一个打开文件对话框(open file dialog)然后捕获这个对话框,在属性格子中返回对话框的结果。

1
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object       value)
2
      {
3

4
if (provider != null) 
5
{


ShowDialog方法。(原文:First we need to get a reference to the current service and control, we need the reference to the control so we can pass it to the ShowDialog method.)

 

1
IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof       (IWindowsFormsEditorService));
2
Control editorControl = editorService as        Control;
3       

4
if (editorControl != null       ) 
5
       { 
 
      1
OpenFileDialog openFileDialog = new        OpenFileDialog();                         
2       

3
openFileDialog.CheckFileExists = true       ;
4
openFileDialog.DefaultExt = ".dll"       ;
5
openFileDialog.Multiselect = false       ;
6
openFileDialog.Title = "Select an Assembly:"       ;
7
openFileDialog.Filter = "Assembly Files | *.dll"; 
 
      1
DialogResult result = openFileDialog.ShowDialog(editorControl); 
 
       1
if (result ==        DialogResult.OK)
 2
            
       {
 3
Assembly assembly = Assembly.LoadFrom( openFileDialog.FileName ) ;
 4
                  value = assembly; 
 5
            }       
        6
            else       
        7
            
       {
 8
                  value = null;
 9
            }       
       10       
      }
11       
}
12       

13
return        value;
14
}

assembly与模板放在同一目录下,然后再模板中加入下面两行代码。

1
<%@ Assembly Name="AssemblyHelper" %>       
       2

_CodeSignature codesignature翻译__CodeSignature

<%@ Import NameSpace="AssemblyHelper" %>openFileDialog类并填入适合的属性。reference)将对话框显示给用户。OK按钮,如果点击了,通过文件选择对话框选择文件后使用LoadForm方法加载这个组件,最后返回这个值。import引入到模板中,并在模板中用一对属性声明。


Editor属性,并且指定为UITypeEditor编辑器。

 

1
[Editor(typeof(AssemblyHelper.AssemblyFilePicker), typeof        (System.Drawing.Design.UITypeEditor))]
2
public         Assembly AssemblyToLoad
3
        {
4
      get
{return assembly;}
5
      set
{assembly = value;}
6
}


        当属性面板读取到这些属性时,它将使用我们自定义的UITypeEditor编辑器并允许用户从打开文件对话框中选择一个组件。 

        这个模板仅仅可以编译通过,但是由于我们编写显示了一个类型属性面板并不知道如何去操作它,所以我们没有办法自定义指定组件在加载时想要加载的组件。

UITypeEditor,这是一个建立属性面板是用的特殊属性的类。UITypeEditor需要创建在一个和模板分离的组件中,我们是用Visual Studio创建这个类。