自定义 Windows Presentation Foundation 的控件---官方文档

       虽然 Windows Presentation Foundation 中的控件模型非常多,但仍不可能提供需要的每一种控件。这时候,控件编写就派上用场了。在本文中,我将向您讲述如何使用 Windows ® Presentation Foundation 自定义现有控件,以及如何为您的项目创建全新的控件(或元素)。
       在开发一个自定义控件之前,应该先问问自己是否真的需要它。在 Windows Presentation Foundation 中,组合、样式和模板化功能使您可以自定义现有控件,这是以前的技术所无法比拟的。在决定创建新控件之前,我们先快速讲述一下上述三种自定义控件的方法。

1. 使用组合
    创建组合控件是常见要求。所谓组合控件是指由一个以上控件组成的控件。假定您有一个用于启动视频播放的 Play 按钮。XAML 和控件如 图 1 所示。
<StackPanel>
  <Button Height=”50” Width=”50” Content=”Play” />
  <Polygon HorizontalAlignment=”Center” 
           Points=”0,0 0,26 17,13”
           Fill=”Black” />
</StackPanel>

     您需要能够得到 play 图标并将它放置在该按钮上。您可以使用组合将 XAML 元素实际嵌入其他 XAML 元素内。例如,您可以通过更改 XAML 来创建标签和图标(作为该按钮的内容)。将这些元素放置在该按钮内的容器(此处为 StackPanel)中,这样可将它们分配给 Button 类的 Content 属性,如图 2 中所示。这样得到的按钮会象任何其他按钮一样正常工作,但它里面却有您自己的内容。

<StackPanel>
  <Button Height=”50” Width=”50”>
    <StackPanel>
      <TextBlock>Play</TextBlock>
      <Polygon Points=”0,0 0,26 17,13”
               Fill=”Black” />
    </StackPanel>
  </Button>
</StackPanel>
     使用组合来创建此类控件非常简单。与 Windows Forms、Visual Basic ® 6.0 和 MFC 等演示技术中的控件不同,大多数组合控件都是其他控件的容器。当您真正需要的只是一个组合控件时,就不必编写自定义控件。


2.使用样式

   如果您需要的只是改变控件的外观,怎么办?使用样式就可以解决问题。通过创建一个类似以下样式的样式,您可以指定一个带有红色边框的按钮样式。  
<StackPanel>
  <StackPanel.Resources>
    <Style TargetType=”Button” x:Key=”RedButton”>
      <Setter Property=”BorderBrush” Value=”Red” />
    </Style>
  </StackPanel.Resources>
  ...
</StackPanel>
     现在,您可以通过为特定的按钮分配样式来更改它们的边框,如 图 3 所示。第一个按钮是标准外观,而第二个按钮则将自己和共享样式绑定在一起。
<Button Height=”50” Width=”50”>
  <StackPanel>
    <TextBlock>Play</TextBlock>
    <Polygon Points=”0,0 0,26 17,13”
             Fill=”Black” />
  </StackPanel>
</Button>
<Button Height=”50” Width=”50” Style=”{StaticResource RedButton}”>
  <StackPanel>
    <TextBlock>Play</TextBlock>
    <Polygon Points=”0,0 0,26 17,13”
             Fill=”Black” />
  </StackPanel>
</Button>
     您甚至可以使用样式来更改某容器内特定类型 XAML 元素的所有实例的外观。例如,您可以创建一个指定所有按钮外观的样式,而不用创建一个可重用样式来更改按钮,如 图 4 中所示。此示例将所有按钮的背景设置为灰色/绿色/渐进灰色。此示例中的样式忽略了样式的 Key 值。因此,这会影响 TargetType 属性中指定的所有元素。
<StackPanel>
  <StackPanel.Resources>

    <Style TargetType=”Button”>
      <Setter Property=”Background”>
        <Setter.Value>
          <LinearGradientBrush>
            <GradientStop Color=”#DDDDDD” Offset=”0” />
            <GradientStop Color=”#88FF88” Offset=”.6” />
            <GradientStop Color=”#EEEEEE” Offset=”1” />
          </LinearGradientBrush>
        </Setter.Value>
      </Setter>
    </Style>

  </StackPanel.Resources>

  <Button Height=”50” Width=”50”>
    <StackPanel>
      <TextBlock>Play</TextBlock>
      <Polygon Points=”0,0 0,26 17,13”
               Fill=”Black” />
    </StackPanel>
  </Button>

  <Button Height=”50” Width=”50”>
    <StackPanel>
      <TextBlock>Play</TextBlock>
      <Polygon Points=”0,0 0,26 17,13”
               Fill=”Black” />
    </StackPanel>
  </Button>

</StackPanel>


3. 使用模板
    样式仅限于设置 XAML 元素上的默认属性。例如,当我在前面的示例中设置 BorderBrush 时,我可以指定画笔,而不能指定边框的宽度。若要完全自由地定义控件的外观,您需要使用模板。为此,可以创建样式并指定 Template 属性(参见 图 5)。Template 属性的 Value 成为指定如何编写控件本身的 ControlTemplate 元素。在本示例中,我指定了一个圆中心有 play 图标的按钮。为此,我将 play 图标覆在了 Ellipse 元素上。新的模板按钮显示在普通按钮旁边。
<StackPanel>
  <StackPanel.Resources>

    <Style TargetType=”{x:Type Button}” x:Key=”PlayButton” >
      <Setter Property=”Template”>
        <Setter.Value>
          <ControlTemplate TargetType=”{x:Type Button}”>
            <Grid>
              <Ellipse Width=”{TemplateBinding Width}”
                       Height=”{TemplateBinding Height}”
                       Stroke=”DarkGray”
                       VerticalAlignment=”Top”
                       HorizontalAlignment=”Left”
                       Fill=”LightGray” />
              <Polygon Points=”18,12 18,38 35,25”
                       Fill=”Black” />
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

  </StackPanel.Resources>

  <Button Height=”50” Width=”50”>Normal Button</Button>
  <Button Height=”50” Width=”50” Style=”{StaticResource PlayButton}” />

</StackPanel>
     总之,样式和模板仍只允许您更改控件的外观。若要为按钮添加行为和其他功能,则需要创建自定义控件。

4. 编写控件
在编写自己的控件之前,应该采取的第一步是决定创建控件所使用的方法。在 Windows Presentation Foundation 中创建控件主要有两种方法:用户控件和自定义控件。两种方法各有各的好处。
使用用户控件,您可以得到类似于 Windows Presentation Foundation 应用程序开发的简单开发模型。如果您需要根据现有组件编写控件,而不必进行复杂的自定义(像使用模板和样式那样),则应使用用户控件。如果希望完全控制外观、需要特殊的显示支持,或希望您的控件成为其他控件的容器,那么最好应使用自定义控件。
如果无法决定选择哪类控件,请使用用户控件。如果您使用用户控件时遇到功能障碍,那么随后也可以比较轻松地切换到自定义控件。
创建用户控件时,要做的第一件事情是将新的项添加到项目中。右键单击您的项目并单击“添加”,您可能希望从上下文菜单中选择“用户控件”选项。可惜,此操作只能创建新的 Windows Forms 用户控件。因此,请选择“添加新项”选项。在“添加新项”对话框中,选择“用户控件 (WPF)”项。
创建新的用户控件会创建新的 XAML 文件和备份代码文件。该 XAML 文件类似于随新的 Windows Presentation Foundation 项目创建的主文件,不同之处在于新的 XAML 文件的根元素是 UserControl 元素。在 UserControl 元素内,您要创建组成您的控件的内容。
对此示例而言,继续使用前面用过的同一 XAML 来为 PlayButton 控件创建模板。此新控件会将自己绑定到 MediaElement,以便对某些数字媒体的播放或暂停进行控制。 图 6 显示 PlayButton 的 XAML。
<!-- PlayButton.xaml --> 
<UserControl x:Class=”CustomWPF.PlayButton”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”>

  <Grid>

    <Ellipse Width=”50” Height=”50” 
             Stroke=”DarkGray”
             VerticalAlignment=”Top”
             HorizontalAlignment=”Left”
             Name=”ButtonBack”                       
             Fill=”LightGray” />
    <Path Name=”PlayIcon” Fill=”Black”
          Data=”M18,12 18,38 35,25”/>
    <Path Name=”PauseIcon” Fill=”Black” Opacity=”0”
          Data=”M15,12 15,38 23,38 23,12z M27,12 27,38 35,38 35,12” />

  </Grid>

</UserControl>

从模板示例中,我已经添加了一个新的 Path (PauseIcon)。由于用于暂停媒体的图标不能表示为 Polygon,而将 PlayIcon 更改为 Path 会更容易一些,这样您就可以将每个图标作为代码隐藏中的 Path 对象进行处理。我希望能够通过以下操作来控制 MediaElement 元素:在单击该按钮时暂停或播放媒体,以及在单击按钮时图标发生相应变化从而正确表示相应的操作(暂停或播放)。
在添加该逻辑之前,先确保我的按钮显示正确而且可以显示在窗口中。在此情况下,我希望将此新控件显示在窗口上。在 XAML 文档中使用任何自定义元素(用户控件或自定义控件)之前,必须先创建对它的引用。如果该自定义元素与 XAML 的其余自定义元素位于同一项目中,那么只需添加一个 XML 命名空间声明即可引用它。在下列几行代码中,我创建了命名空间声明 (xmlns:cust),该声明用于指定控件所在的公共语言运行库 (CLR) 命名空间。
<!-- MainWindow.xaml -->
<Window x:Class=”Tester.MainWindow”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    xmlns:cust=”clr-namespace:CustomWPF“
    Title=”Control Viewer” 
    Height=”100” Width=”200”>

  <!-- ... -->

</Window>
XML 命名空间声明中指定的 clr-namespace (CustomWPF) 与该控件的实际 CLR 命名空间 (CustomWPF) 相匹配。如果您要使用的控件位于另一个程序集中,则必须在该命名空间声明中注明该程序集的名称。XML 命名空间声明不会自动将该程序集导入您的项目中,您还必须手动将对该程序集的引用添加到您的项目中。
<!-- MainWindow.xaml -->
<Window x:Class=”Tester.MainWindow”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    xmlns:cust=”clr-namespace:CustomWPF;assembly=CustomWPF”
    Title=”Control Viewer” 
    Height=”100” Width=”200”>

  <!-- ... -->

</Window>
一旦建立了对该 XML 命名空间的引用,您就可以使用它来创建新用户控件的实例。为此,您需要使用命名空间声明的名称。这里指的是 XML 命名空间别名,不是 CLR 命名空间。您应在 XML 命名空间别名之后指定控件的实际名称。这样可确保 XAML 文件中使用的名称与类名称相匹配。若要将 PlayButton 类的实例添加到 XAML,您需要将 cust:PlayButton 指定为元素名称。
<StackPanel>
  <TextBlock HorizontalAlignment=”Center”>User Control:</TextBlock>
  <cust:PlayButton />
</StackPanel>

现在,您可以看到 PlayButton 控件已驻留在典型的 Windows Presentation Foundation 窗口中,如 图 7 所示。

5.  自定义属性
在编写控件时,您会发现需要通过完善某些属性来控制控件的外观及其运行时行为。例如,PlayButton 控件需要获得和设置图标颜色的能力。为此,您可以创建一个简单的 CLR 属性,如 图 8 所示(请注意,您可在本文的下载内容中下载 Visual Basic 示例代码)。
// PlayButton.xaml.cs
public partial class PlayButton : System.Windows.Controls.UserControl
{
  // ...

  Brush _iconColor = Brushes.Black;

  public Brush IconColor
  {
    get {return _iconColor; }
    set
    {
      _iconColor = value;
      PlayIcon.Fill = _iconColor;
      PauseIcon.Fill = _iconColor;
    }
  }
}
简单的 CLR 属性(如 IconColor 属性)足够用了。只需使用属性名称即可在 XAML 中对其进行设置。
<cust:PlayButton IconColor=”Black” />

不过,简单的属性对大多数控件而言都不够,因为它们不支持高级功能,如数据绑定或动画支持。例如,如果您希望通过数据绑定将控件的 IconColor 指定为 XAML 中矩形的填充颜色,那么简单的属性无法做到这一点,如 图 9 所示。
<!-- MainWindow.xaml -->
<Window x:Class=”Tester.MainWindow”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    xmlns:cust=”clr-namespace:CustomWPF;assembly=CustomWPF”
    Title=”Control Viewer” 
    Height=”100” Width=”200”>

  <StackPanel>
    <Rectangle Name=”theRect” Fill=”Red” />
    <TextBlock>User Control:</TextBlock>

    <!-- Simple Assignment Works -->
    <cust:PlayButton IconColor=”Blue” />

    <!—Data Binding Does Not -->
    <cust:PlayButton 
      IconColor=”{Binding ElementName=theRect, Path=Fill}” />

  </StackPanel>
</Window>

若要利用所有可用的功能,您必须使用 Dependency 属性而不是简单的 CLR 属性。Dependency 属性允许以各种方式设置元素的值,包括动画和数据绑定。若要通过一种方法设置元素的值,可以通过调用 DependencyProperty.Register 方法在控件上创建一个静态(在 Visual Basic 中为共享)DependencyProperty 字段。此方法会注册您的属性,并返回所创建的 DependencyProperty 的实例。
创建 DependencyProperty 字段后,您可以使用它通过调用控件的 GetValue/SetValue 方法来设置和获取该属性。例如,可以将 PlayButton 的 IconColor 属性更改为 DependencyProperty,如 图 10 所示。
请注意,我从该类中删除了 Brush 字段。DependencyProperty 会存储每个实例的值以及该属性的元数据。这意味着 PlayButton 的每个实例不需要有其自己的字段来存储有关属性的数据。
现在,您已有了 DependencyProperty,可以将它用于数据绑定和动画。目前,还没有一种好的方法来判断属性是否已更改,或者是否有默认值。由于属性值的设置没有经过公共属性(CLR 属性是 DependencyProperty 的包装器,而不是相反),所以当该值发生更改时,您不能只使用 set 访问器来更改图标颜色。而需要添加一个在属性更改时调用的事件。为此,在注册 DependencyProperty 时要指定属性更改时回调的静态或共享方法。您可以修正注册,以便使其包括用于指定默认值和更改回调的 FrameworkPropertyMetadata 对象,如下所示:
public static readonly DependencyProperty IconColorProperty =
        DependencyProperty.Register(
            “IconColor”, 
            typeof(Brush), 
    typeof(PlayButton),
    new FrameworkPropertyMetadata(Brushes.Black,
    new PropertyChangedCallback(OnIconColorChanged
    )));

最后,您需要实现该回调。该回调是控件的静态(或共享)方法,控件可以接受发生更改的对象以及用于指定旧值和新值的参数。通常,要在发生更改的对象上调用一个方法来更新对象。例如,如果 IconColor 已更改,则需要设置两个图标的 Fill。回调方法和更新方法如下所示:
private static void OnIconColorChanged(DependencyObject obj,
    DependencyPropertyChangedEventArgs args)
{
  // When the color changes, set the icon color
  PlayButton control = (PlayButton)obj;
  control.PlayIcon.Fill = control.IconColor;
  control.PauseIcon.Fill = control.IconColor;
}

若要完成控件,还需要 DependencyProperty,从而允许将 MediaElement 分配给控件(参见图 11)。
有了该属性后,您可以将 MediaElement 添加到 XAML 中,也可以将该元素数据绑定到新的 MediaPlayer 属性。在将 MediaElement 添加到 XAML 中时,您需要将 LoadedBehavior 设置为 Manual,这样您可以手动控制播放。新的 XAML 如 图 12 所示。
<!-- MainWindow.xaml -->
<Window x:Class=”Tester.MainWindow”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
    xmlns:cust=”clr-namespace:CustomWPF;assembly=CustomWPF”
    Title=”Control Viewer” 
    Height=”100” Width=”200”>
  <StackPanel>

    <MediaElement Width=”150” Height=”100” 
         Name=”theMedia” 
         Source=”http://download.microsoft.com/.../ctorrec9billg.wmv”
         LoadedBehavior=”Manual” />

    <TextBlock>User Control:</TextBlock>

    <cust:PlayButton MediaPlayer=”{Binding ElementName=theMedia}” />

  </StackPanel>
</Window>

接下来,在 PlayButton 用户控件上实现单击事件。首先,在控件的主 Grid 中添加 MouseLeftButtonUp 事件
<!-- PlayButton.xaml --> 
<UserControl x:Class=”CustomWPF.PlayButton”    
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”>
  <Grid MouseLeftButtonUp=”PlayButton_Clicked”>
    <!-- ... -->
  </Grid>
</UserControl>

这样您就可以实现控件的行为以及更改图标。此事件处理程序的实现如图 13 所示。完成的控件和视频如 图 14 所示。


6.  自定义控件
您刚创建的 PlayButton 控件工作得很好,但缺乏完全的模板和主题支持。如果您的控件需要支持此功能,则需要将它构建为自定义控件。自定义控件由控件层次结构中的其他类派生而来。例如,可以将 PlayButton 控件重构为可重用程度更高的 MediaButton 控件。
若要创建 MediaButton 控件,首先要在 Visual Studio 的“添加新项”对话框中选择“自定义控件 (WPF)”。这会在项目中添加一个新的自定义控件类 (MediaButton.cs) 文件,还会新增一个包含 generic.xaml 文件的主题文件夹。该 generic.xaml 文件中包含用于新控件类的模板。它使用此 XAML 文件来允许该控件有不同主题。generic.xaml 文件用作备用主题;对大多数控件而言,这是您要创建的唯一主题文件。如果您想编写的控件要根据当前主题来更改其外观,您可以在此目录中创建主题文件。 图 15 显示标准主题文件以及它们的使用时间。
通过指定多个主题,您可以根据用户的主题选择来更改控件的外观。为了给我的简单控件开发一个通用主题,我可以使用用户控件的 XAML 文件,并将其移入 ControlTemplate 标记中。将 XAML 放入模板后,您需要使用模板绑定来根据该控件的属性设置 XAML 的属性。到目前为止,PlayButton 的宽度和高度一直被设置为五十个逻辑单位。对于简单的控件,这完全可以,但如果您真的希望可重用该控件,那么应该使模板的大小可以调整。为此,通过在模板中将高度和宽度分别标记为 {TemplateBinding Width} 和 {TemplateBinding Height},即可用控件的高度和宽度来替换模版的高度和宽度。
在 PlayButton 中,只需根据图标需要显示播放还是暂停来更改两个图标的 Opacity。虽然可以进入模板去更改 Opacity,但这么做太不聪明了。更好的方法是利用单个图标 Path 对象,并更改绘制数据来绘制合适的图标。这样,可视树的大小就会直接影响 XAML 文档的性能。为此,需要引入一个新的 DependencyProperty 来存储当前要使用的图标。创建一个枚举指定要使用哪个图标,然后将它作为 DependencyProperty 公开。使用新的 Icon 属性,您可以对模板进行修改,使之包括用于触发 Icon 属性更改的触发器。
该图标使用 Path 元素来定义该按钮使用的每个图标的外观。如果该按钮的大小始终固定,则一切正常,但由于情况并非如此,所以您需要找到一种方法来使图标的大小随该按钮而调整。方法之一是在背景圆上创建一个覆盖椭圆,并使用 VisualBrush 给该图标绘色。VisualBrush 允许背景的大小随控件而调整。 图 16 中显示了完成的 generic.xaml 模板。
此 MediaButton 应该控制 MediaElement,就像 PlayButton 控制其元素一样。将 MediaPlayer DependencyProperty 从 PlayButton 复制到 MediaButton。请确保将所复制的代码中的任何引用都从 PlayButton 更改为 MediaButton(尤其是在 DependencyProperty 注册中)。
与 PlayButton 不同,您不需要在模板中处理鼠标单击事件。而是可以重写 OnMouseLeftButtonUp 事件来响应单击。在此方法中,您可以更改图标以及播放或暂停媒体。最终的自定义控件代码如 图 17 所示。现在新控件可以调整图标的大小和设置图标的属性,您可以赋予用户更大的灵活性,让他们能够更改控件的大小和图标。 图 18 显示了该控件在大小和图标不同时的情况。

在此控件示例中,我是直接从 System.Windows.Controls.Control 类中进行派生的,但自定义控件的特性允许从类层次结构中的任何位置进行派生。您可以使用自定义控件来重写和更改内置控件的行为,也可以完全从头开始构建自己的控件。例如,从 FrameworkElement 派生让您能够以非常少的内置布局结构来创建控件。从 Panel 派生让您能够为其他对象创建自己专用的容器。决定要从中派生的正确类并不容易,具体取决于您的控件的要求。

我们说到哪儿了?
当您需要专门的控件功能时,您有多个选项,其中包括组合、样式和模板。通过使用组合,您通常可以创建组合控件,不必编写新控件。当您需要更改的只是控件的外观时,可以使用样式。最后,使用模板可以完全控制现有控件的编写。有关使用模板来自定义控件的详细信息,请参阅 Charles Petzold 的 Foundations 专栏
当您确实决定编写新的控件时,仍可以使用简化的编程模型,就如同编写您自己的窗口或页面一样。能够为外观、甚至行为随不同操作系统主题而变化的控件创建模板是自定义控件的独特优势。
将现有的用户控件迁移到自定义控件并不是特别困难,但由于您在使用模板,而不是直接访问 XAML 对象,所以需要改变自定义控件的构造方法。
使用 Windows Presentation Foundation 时,只是偶尔需要编写自定义控件,不是必须编写自定义控件。仅当您真的要创建自定义行为时,才需要钻研控件编写。有关 Windows

原文地址: http://msdn.microsoft.com/zh-cn/magazine/cc163421.aspx



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值