动手试验:创建具有自定义属性的自定义控件

Microsoft Expression Blend 提供了大量不同的系统控件和简单样式,可供您在应用程序中使用。但是,如果这些控件或样式不能满足您的特定需要,则可以通过创建继承自某个 System.Windows.Controls 类的 Microsoft .NET 类来创建自定义控件。

以下操作步骤显示了如何创建自定义按钮控件,该按钮具有一个用于存放图像文件路径以显示图像的新属性。

有关不使用代码即创建用户控件并因而没有自定义属性的示例,请参阅创建空的用户控件

有关自定义控件(包括 XAML 和代码示例)的详细信息,请参阅 MSDN 上的 Windows Presentation Foundation 部分中的控件创作概述DependencyProperty 类

定义自定义控件

  1. 在“项目”面板中,找到“Window1.xaml”文件,将其展开以显示项目的代码隐藏文件(名为“Window1.xaml.cs”)。(如果在创建项目时选择了 Microsoft Visual Basic 作为编程语言,则代码隐藏文件将命名为“Window1.xaml.vb”。)双击该代码隐藏文件,将其打开以进行编辑。

  2. 在 Window1.xaml.cs 文件中,将以下类定义源代码粘贴到文件中的最后一个右大括号 (}) 之前。(对于 Visual Basic,将适当的代码粘贴到“End Namespace”之前或最后一个“End Class”之后。)

    public class ImageButton : Button 
    { 
      public ImageSource Source 
      { 
        get { return base.GetValue(SourceProperty) as ImageSource; } 
        set { base.SetValue(SourceProperty, value); } 
      } 
      public static readonly DependencyProperty SourceProperty = 
        DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImageButton), null); 
    }
    
    Class ImageButton
      Inherits Button
      Public Property Source() As ImageSource
        Get
          Return CType(MyBase.GetValue(SourceProperty), ImageSource)
        End Get
        Set(ByVal value As ImageSource)
          MyBase.SetValue(SourceProperty, value)
        End Set
      End Property
      Public Shared ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register("Source", GetType(ImageSource), GetType(ImageButton), nothing)
    End Class
    
  3. 保存经过修改的代码隐藏文件,然后关闭文件。

    Cc295235.alert_tip(ZH-CN,Expression.30).gif提示:

    “ImageButton”类展示了依赖属性模式。从外部看来,该类公开了一个常规的公共语言运行时(CLR,即 .NET)属性,其名称为“Source”,类型为“ImageSource”。该类还注册并公开了一个名为“SourceProperty”的只读的依赖属性字段,并根据依赖属性实现了“Source”属性的 CLR 访问器。用注册的依赖属性来支持属性,意味着 WPF 或 Silverlight 可以为属性提供大量服务。这些服务包括样式设置、数据绑定、值继承、默认值和动画。其中还有一个称为“模板绑定”的功能,可用于从控件模板内绑定到属性值。本教程将显示模板绑定的实际效果。

    Cc295235.alert_tip(ZH-CN,Expression.30).gif提示:

    用户可以根据自己的喜好,将自定义控件的类定义源代码保存在单独的源代码文件中。如果愿意,可以将前面的两个步骤改为:创建一个名为“ImageButton.cs”的新文件,将源代码以及文档的代码隐藏文件中的命名空间声明和同一组 using 指令粘贴到此文件中,然后将这个新文件添加到项目中(在“项目”菜单上,单击“添加项”)。

  4. 此时,需要生成项目,以使新的“ImageButton”控件显示在“资产”面板中。在 Expression Blend 的“项目”菜单上,单击“生成项目”(或者按 Ctrl+Shift+B)。确定生成过程中没有出现任何错误,成功结束。

  5. 在 Expression Blend 中,重新切换到“Window1.xaml”的编辑模式。

  6. 在 Expression Blend 左侧的“工具”面板中,单击“资产”Cc295235.0d8b8d29-1af9-418f-8741-be3097d76eab(ZH-CN,Expression.30).png

    这将打开“资产”面板,以显示可放入文档中的所有控件、面板和元素。

  7. 在“项目”选项卡上的列表中选择“ImageButton”控件。

    此时,“资产”按钮下方的“工具”面板图标将设置为“ImageButton”控件,而且该控件已被选定,以便您可以在美工板上绘制该控件。

    Cc295235.alert_note(ZH-CN,Expression.30).gif说明:

    除非已将自定义控件的源代码添加到项目中并执行生成操作(按 Ctrl+Shift+B),否则,自定义控件将不会显示在“资产”面板的“项目”类别中。

  8. 在“工具”面板中选择“ImageButton”图标,然后通过在美工板上单击并在文档中绘制范围框来绘制“ImageButton”。

    此时,“ImageButton”将呈现在美工板上,并且在“对象和时间线”面板中,一个名为“[ImageButton] "ImageButton"”的“ImageButton”元素将作为“LayoutRoot”的子元素列出来。

修改自定义控件的样式

  1. 若要创建一个适用于所有“ImageButton”元素的控件模板,请右键单击新的“ImageButton”,然后依次单击“编辑模板”和“编辑副本”。

    此时,将显示“创建样式”对话框。

  2. 在“创建样式”对话框的“资源名(关键字)”字段中,在第一个单选按钮旁边的文本框中键入“ImageButtonStyle”。

    这会在资源字典中设置模板的名称,以便您可以将其设置为任何“ImageButton”元素的模板。

    Cc295235.alert_note(ZH-CN,Expression.30).gif说明:

    控件模板封装在样式中,以便应用于控件的样式同时包含控件的外观(部件)和行为。

  3. 在“定义位置”字段中,选择“本文档”单选按钮,并确保在下拉列表中选择了“Window:Window”。

    “定义位置”字段用于指明定义新模板的资源字典。选择“Window:Window”表示该模板将对窗口中的所有“ImageButton”控件可见。

  4. 单击“确定”。

    此时,已复制所有“ImageButton”元素的默认控件模板,并且已将该副本另存为新的“ControlTemplate”(名为“ImageButtonStyle”)。新的模板已放入资源字典中,可以在“资源”面板的“Window”节点中查看该模板。

    创建新模板后,Expression Blend 将进入您可在其中查看和编辑新模板的模式。在“对象和时间线”面板中,新元素树上方的“Template”一词指明了当前的编辑范围。该模板包含一个“ContentPresenter”元素,该元素将自动显示使用此模板的“ImageButton”的“Content”属性值。

    Cc295235.alert_tip(ZH-CN,Expression.30).gif提示:

    若要退出模板编辑模式并返回到文档范围,请单击位于元素树上方的“ImageButtonStyle”一词旁边的“范围上移”Cc295235.55844eb3-ed98-4f20-aa66-a6f5b23eeb2b(ZH-CN,Expression.30).png

    若要返回到现有模板的模板编辑模式,请在“对象和时间线”面板中右键单击要编辑其模板的元素(本例中为“[ImageButton] "ImageButton"”元素),单击“编辑模板”,然后单击“编辑当前模板”。

  5. 在“对象和时间线”面板中,右键单击“ContentPresenter”对象,指向“分组”,然后单击“Grid”。

    此时,“ContentPresenter”对象将成为新的“Grid”对象的子元素。没有网格对象,就不能向模板中添加第二个子元素,这是因为镶边对象只能包含一个子元素。

  6. 在“对象和时间线”面板中选择“Grid”对象,然后在“属性”面板的“布局”类别中找到“Width”和“Height”属性。单击每个属性旁边的“高级属性选项”Cc295235.12e06962-5d8a-480d-a837-e06b84c545bb(ZH-CN,Expression.30).png,然后单击“重置”。

  7. 若要将网格划分为两列,请在“对象和时间线”面板中双击网格对象以激活网格,再在“工具”面板中选择“选择”工具 Cc295235.2ff91340-477e-4efa-a0f7-af20851e4daa(ZH-CN,Expression.30).png,然后在美工板上将鼠标指针移动到“Grid”顶部的蓝色宽标尺区域的上方。鼠标指针后将跟随一个橙色的列标尺,指明在单击时将放置一个新的列分隔线。

    单击以在“Grid”中间创建一个新的列分隔线。

    “Grid”中将显示一条蓝色的列分隔线。

  8. 从“资产”面板的“控件”类别中(单击“全部”子类别),选择“图像”Cc295235.adb61060-da5f-4279-8c0d-3681bfeb145c(ZH-CN,Expression.30).png。在“对象和时间线”面板中的 Grid 元素仍处于活动状态的情况下,在“Grid”右侧的列中绘制新的“Image”。

  9. 在“对象和时间线”面板中选择新的“Image”元素,然后在“属性”面板的“公共属性”下查看其属性。单击“Source”属性右侧的“高级属性选项”Cc295235.12e06962-5d8a-480d-a837-e06b84c545bb(ZH-CN,Expression.30).png,指向“模板绑定”,然后单击“Source”。

    刚才已在“Image”的“Source”属性与使用模板的“ImageButton”的“Source”属性之间建立了模板绑定。

  10. 此时,模板就已编辑完毕。若要退出根元素的范围,请单击“对象和时间线”面板中的“范围上移”。

  11. 在“对象和时间线”面板中选择“ImageButton”,然后在“属性”面板的“杂项”类别中找到“Source”属性,并将其设置为计算机上某个图像文件的路径。

    该图像将显示在“ImageButton”控件的右侧。

向其他自定义控件应用样式

  1. 从“资产”面板的“项目”类别中,选择“ImageButton”控件。在美工板上绘制一个新的“ImageButton”控件。

  2. 右键单击新的“ImageButton”,再依次单击“编辑模板”和“应用资源”,然后单击样式的名称“ImageButtonStyle”。

    此时,“ImageButtonStyle”将应用于新的“ImageButton”控件。在“属性”面板的“杂项”类别中找到“Source”属性,并将其设置为计算机上某个图像文件的路径。

    Cc295235.alert_note(ZH-CN,Expression.30).gif说明:

    或者,可以向美工板上添加已使用模板设置样式的“ImageButton”。从“资产”面板的“样式”类别中,选择“ImageButtonStyle”,然后在美工板上绘制带样式的“ImageButton”。

高级:应用自定义属性的说明

  1. 在代码隐藏文件 (Window1.xaml.cs) 的顶部,添加对 System.ComponentModel 命名空间的引用。

    下面使用的“Description”和“Category”属性均定义在该命名空间中。

  2. 将以下代码行(粗体)粘贴到类定义的前面:

    [Description("Represents a custom button control that responds to a Click event. Displays an image using a custom Source property if the Source property is bound to an Image in the template.")]
    public class ImageButton : Button
    
    <Description("Represents a custom button control that responds to a Click event. Displays an image using a custom Source property if the Source property is bound to an Image in the template.")> _
    Class ImageButton
    
  3. 将以下代码行(粗体)粘贴到自定义属性定义的前面:

    [Description("The image displayed in the button if there is an Image control in the template whose Source property is template-bound to the ImageButton Source property."), Category("Common Properties")] 
        public ImageSource Source
    
    <Description("The image displayed in the button if there is an Image control in the template whose Source property is template-bound to the ImageButton Source property."), Category("Common Properties")> _ 
        Public Property Source() As ImageSource
    

    “Category”属性用于配置属性将在“属性”面板中显示的位置。

  4. 重新生成项目 (Ctrl+Shift+B)。

    此时,“ImageButton”的“Source”属性将出现在“属性”面板的“公共属性”类别中,并且在将鼠标指针移动到该属性上和“资产”面板中的该控件上时,会在工具提示中显示相关说明。

高级:配置控件以显示默认图像

可以向任何自定义控件的构造函数中添加代码,以便在美工板上绘制控件时(也就是在设计模式下)将属性设置为默认值。按照以下操作步骤设置属性,只会影响美工板上的显示效果,而不会影响应用程序运行时的效果。如果在设计模式下内容不能供控件使用,但在应用程序运行时可以供控件使用(例如,当内容来自数据库或 Web 服务时),这一点将非常有用。在此情况下,美工板上的“ImageButton”的“Source”属性将设置为项目中的一个图像文件的名称,直到显式设置此属性为止。

  1. 在代码隐藏文件 (Window1.xaml.cs) 中,将下列代码行(粗体)粘贴到属性定义的后面:

    Cc295235.alert_note(ZH-CN,Expression.30).gif说明:

    请将以下代码中的文件名 (Sunset.jpg) 更改为项目中的一个图像文件的名称。若要向项目中添加图像文件,请在“项目”面板中右键单击项目名称,然后单击“添加现有项”。

    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImageButton));
    
    // Constructor:  
    public ImageButton()  
    {  
       if (DesignerProperties.GetIsInDesignMode(this))  
       {  
          this.Source = new BitmapImage(new Uri("Sunset.jpg", UriKind.Relative));  
       }  
    }  
    Public Shared ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register("Source", GetType(ImageSource), GetType(ImageButton))
    
    Public Sub New()  
        If DesignerProperties.GetIsInDesignMode(Me) Then  
            Me.Source = New BitmapImage(New Uri("Sunset.jpg", UriKind.Relative))  
        End If  
    End Sub  
    
  2. 重新生成项目 (Ctrl+Shift+B)。

    此时,当您将使用所创建的样式的“ImageButton”添加到美工板上时,该按钮中将显示默认图像。

    Cc295235.alert_note(ZH-CN,Expression.30).gif说明:

    在设计模式下,不能将“Source”属性设置为其他任何值。