演练:使用 XAML 创建按钮

本演练的目的是了解如何创建用于 Windows Presentation Foundation (WPF) 应用程序的动画按钮。 本演练使用样式和模板创建一个自定义按钮资源,该资源允许重用代码并将按钮逻辑与按钮声明分离。 本演练完全使用 Extensible Application Markup Language (XAML) 编写。

重要

本演练将指导你通过将 Extensible Application Markup Language (XAML) 键入或复制并粘贴到 Visual Studio 来创建应用程序。 如果你想了解如何使用设计器创建相同的应用程序,请参阅使用 Microsoft Expression Blend 创建按钮

下图显示了完成的按钮。

Custom buttons that were created by using XAML

创建基本按钮

首先创建一个新项目并向窗口中添加几个按钮。

创建新的 WPF 项目并向窗口添加按钮

  1. 启动 Visual Studio。

  2. 创建新的 WPF 项目:在“文件”菜单上,指向“新建”,然后单击“项目”。 找到“Windows 应用程序(WPF)”模板并将项目命名为“AnimatedButton”。 这将为应用程序创建框架。

  3. 添加基本默认按钮:本演练所需的所有文件均由模板提供。 在解决方案资源管理器中双击打开 Window1.xaml 文件。 默认情况下,Window1.xaml 中有一个 Grid 元素。 删除 Grid 元素并向 Extensible Application Markup Language (XAML) 页面添加几个按钮,方法是将以下突出显示的代码键入或复制并粘贴到 Window1.xaml 中:

    <Window x:Class="AnimatedButton.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="AnimatedButton" Height="300" Width="300"
      Background="Black">
      <!-- Buttons arranged vertically inside a StackPanel. -->
      <StackPanel HorizontalAlignment="Left">
          <Button>Button 1</Button>
          <Button>Button 2</Button>
          <Button>Button 3</Button>
      </StackPanel>
    </Window>
    

    按 F5 运行应用程序;你应该会看到一组如下图所示的按钮。

    Three basic buttons

    基本按钮创建完毕,你已经完成了 Window1.xaml 文件中的工作。 本演练的其余部分侧重于 app.xaml 文件、定义样式和按钮模板。

设置基本属性

接下来,让我们在这些按钮上设置一些属性来控制按钮的外观和布局。 你将使用资源为整个应用程序定义按钮属性,而不是在按钮上单独设置属性。 应用程序资源在概念上类似于网页的外部级联样式表 (CSS);但是,如本演练结束时所示,资源比级联样式表 (CSS) 更强大。 若要详细了解资源,请参阅 XAML 资源

使用样式设置按钮的基本属性

  1. 定义 Application.Resources 块:打开 app.xaml 并添加以下突出显示的标记(如果尚不存在):

    <Application x:Class="AnimatedButton.App"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      StartupUri="Window1.xaml"
      >
      <Application.Resources>
        <!-- Resources for the entire application can be defined here. -->
      </Application.Resources>
    </Application>
    

    资源范围取决于定义资源的位置。 通过在 app.xaml 文件的 Application.Resources 中定义资源,可以在应用程序的任何位置使用该资源。 若要详细了解如何定义资源范围,请参阅 XAML 资源

  2. 创建样式并使用它定义基本属性值:将以下标记添加到 Application.Resources 块。 此标记会创建一个适用于应用程序中所有按钮的 Style,并将按钮的 Width 设置为 90,将 Margin 设置为 10:

    <Application.Resources>
      <Style TargetType="Button">
        <Setter Property="Width" Value="90" />
        <Setter Property="Margin" Value="10" />
      </Style>
    </Application.Resources>
    

    TargetType 属性指定样式适用于 Button 类型的所有对象。 每个 SetterStyle 设置一个不同的属性值。 因此,此时应用程序中每个按钮的宽度为 90,边距为 10。 如果按 F5 运行应用程序,你会看到以下窗口。

    Buttons with a width of 90 and a margin of 10

    你可以使用样式执行更多操作,包括各种微调目标对象的方法、指定复杂属性值,甚至使用样式作为其他样式的输入。 有关详细信息,请参阅样式设置和模板化

  3. 将样式属性值设置为资源:资源提供一种简单的方法来重用通常定义的对象和值。 使用资源定义复杂值以使代码更加模块化特别有用。 将以下突出显示的标记添加到 app.xaml。

    <Application.Resources>
      <LinearGradientBrush x:Key="GrayBlueGradientBrush" StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="DarkGray" Offset="0" />
        <GradientStop Color="#CCCCFF" Offset="0.5" />
        <GradientStop Color="DarkGray" Offset="1" />
      </LinearGradientBrush>
      <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="{StaticResource GrayBlueGradientBrush}" />
        <Setter Property="Width" Value="80" />
        <Setter Property="Margin" Value="10" />
      </Style>
    </Application.Resources>
    

    Application.Resources 块的正下方,你创建了一个名为“GrayBlueGradientBrush”的资源。 此资源定义水平渐变。 此资源可用作应用程序中任何位置(包括 Background 属性的按钮样式资源库内部)的属性值。 现在,所有按钮都具有此渐变的 Background 属性值。

    按 F5 运行该应用程序。 该消息应如下所示。

    Buttons with a gradient background

创建一个定义按钮外观的模板

在本部分中,你将创建一个自定义按钮外观(呈现)的模板。 按钮呈现由多个对象组成,包括矩形和其他赋予按钮独特外观的组件。

到目前为止,对应用程序中按钮外观的控制仅限于更改按钮的属性。 如果要对按钮外观进行更彻底的更改,该怎么办? 模板支持对对象的呈现进行强大的控制。 因为模板可用于样式,所以你可以将模板应用于应用了样式的所有对象(在本演练中为按钮)。

使用模板定义按钮的外观

  1. 设置模板:由于像 Button 这样的控件具有 Template 属性,因此可以像在 Style 中使用 Setter 设置的其他属性值一样定义模板属性值。 将以下突出显示的标记添加到按钮样式。

    <Application.Resources>
      <LinearGradientBrush x:Key="GrayBlueGradientBrush"
        StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="DarkGray" Offset="0" />
        <GradientStop Color="#CCCCFF" Offset="0.5" />
        <GradientStop Color="DarkGray" Offset="1" />
      </LinearGradientBrush>
      <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="{StaticResource GrayBlueGradientBrush}" />
        <Setter Property="Width" Value="80" />
        <Setter Property="Margin" Value="10" />
        <Setter Property="Template">
          <Setter.Value>
            <!-- The button template is defined here. -->
          </Setter.Value>
        </Setter>
      </Style>
    </Application.Resources>
    
  2. 更改按钮呈现:此时,你需要定义模板。 添加以下突出显示的标记。 此标记指定两个具有圆角的 Rectangle 元素,后跟一个 DockPanelDockPanel 用于托管按钮的 ContentPresenterContentPresenter 显示按钮的内容。 在本演练中,内容为文本(“Button 1”、“Button 2”、“Button 3”)。 所有模板组件(矩形和 DockPanel)都布置在 Grid 内。

    <Setter.Value>
      <ControlTemplate TargetType="Button">
        <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" ClipToBounds="True">
          <!-- Outer Rectangle with rounded corners. -->
          <Rectangle x:Name="outerRectangle" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="{TemplateBinding Background}" RadiusX="20" RadiusY="20" StrokeThickness="5" Fill="Transparent" />
          <!-- Inner Rectangle with rounded corners. -->
          <Rectangle x:Name="innerRectangle" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="Transparent" StrokeThickness="20" Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20" />
          <!-- Present Content (text) of the button. -->
          <DockPanel Name="myContentPresenterDockPanel">
            <ContentPresenter x:Name="myContentPresenter" Margin="20" Content="{TemplateBinding  Content}" TextBlock.Foreground="Black" />
          </DockPanel>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
    

    按 F5 运行该应用程序。 该消息应如下所示。

    Window with 3 buttons

  3. 向模板添加玻璃效果:接下来添加玻璃效果。 首先,创建一些可呈现玻璃渐变效果的资源。 在 Application.Resources 块中的任意位置添加这些渐变资源:

    <Application.Resources>
      <GradientStopCollection x:Key="MyGlassGradientStopsResource">
        <GradientStop Color="WhiteSmoke" Offset="0.2" />
        <GradientStop Color="Transparent" Offset="0.4" />
        <GradientStop Color="WhiteSmoke" Offset="0.5" />
        <GradientStop Color="Transparent" Offset="0.75" />
        <GradientStop Color="WhiteSmoke" Offset="0.9" />
        <GradientStop Color="Transparent" Offset="1" />
      </GradientStopCollection>
      <LinearGradientBrush x:Key="MyGlassBrushResource"
        StartPoint="0,0" EndPoint="1,1" Opacity="0.75"
        GradientStops="{StaticResource MyGlassGradientStopsResource}" />
    <!-- Styles and other resources below here. -->
    

    这些资源用作插入到按钮模板的 Grid 中的矩形的 Fill。 将以下突出显示的标记添加到模板。

    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Button}">
        <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
          ClipToBounds="True">
    
        <!-- Outer Rectangle with rounded corners. -->
        <Rectangle x:Name="outerRectangle" HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch" Stroke="{TemplateBinding Background}"
          RadiusX="20" RadiusY="20" StrokeThickness="5" Fill="Transparent" />
    
        <!-- Inner Rectangle with rounded corners. -->
        <Rectangle x:Name="innerRectangle" HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch" Stroke="Transparent" StrokeThickness="20"
          Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20" />
    
        <!-- Glass Rectangle -->
        <Rectangle x:Name="glassCube" HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch"
          StrokeThickness="2" RadiusX="10" RadiusY="10" Opacity="0"
          Fill="{StaticResource MyGlassBrushResource}"
          RenderTransformOrigin="0.5,0.5">
          <Rectangle.Stroke>
            <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
              <LinearGradientBrush.GradientStops>
                <GradientStop Offset="0.0" Color="LightBlue" />
                <GradientStop Offset="1.0" Color="Gray" />
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Rectangle.Stroke>
          <!-- These transforms have no effect as they are declared here.
          The reason the transforms are included is to be targets
          for animation (see later). -->
          <Rectangle.RenderTransform>
            <TransformGroup>
              <ScaleTransform />
              <RotateTransform />
            </TransformGroup>
          </Rectangle.RenderTransform>
          <!-- A BevelBitmapEffect is applied to give the button a "Beveled" look. -->
          <Rectangle.BitmapEffect>
            <BevelBitmapEffect />
          </Rectangle.BitmapEffect>
        </Rectangle>
    
        <!-- Present Text of the button. -->
        <DockPanel Name="myContentPresenterDockPanel">
          <ContentPresenter x:Name="myContentPresenter" Margin="20"
            Content="{TemplateBinding  Content}" TextBlock.Foreground="Black" />
        </DockPanel>
      </Grid>
    </ControlTemplate>
    </Setter.Value>
    

    请注意,x:Name 属性为“glassCube”的矩形的 Opacity 为 0,因此当你运行示例时,你看不到覆盖在顶部的玻璃矩形。 这是因为我们稍后会在用户与按钮交互时将触发器添加到模板中。 但是,通过将 Opacity 值更改为 1 并运行应用程序,可以看到按钮现在的外观。 请参阅下图。 在继续下一个步骤之前,请将 Opacity 改回为 0。

    Custom buttons that were created by using XAML

创建按钮交互性

在本部分中,你将创建属性触发器和事件触发器来更改属性值,并运行动画来响应用户操作,例如将鼠标指针移到按钮上并单击。

添加交互性(鼠标悬停、鼠标离开、单击等)的一种简单方法是在模板或样式中定义触发器。 若要创建 Trigger,可定义属性“condition”,例如:按钮 IsMouseOver 属性值等于 true。 然后,定义触发器条件为 true 时发生的资源库(操作)。

创建按钮交互性

  1. 添加模板触发器:将突出显示的标记添加到模板。

    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Button}">
        <Grid Width="{TemplateBinding Width}"
          Height="{TemplateBinding Height}" ClipToBounds="True">
    
          <!-- Outer Rectangle with rounded corners. -->
          <Rectangle x:Name="outerRectangle" HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch" Stroke="{TemplateBinding Background}"
          RadiusX="20" RadiusY="20" StrokeThickness="5" Fill="Transparent" />
    
          <!-- Inner Rectangle with rounded corners. -->
          <Rectangle x:Name="innerRectangle" HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch" Stroke="Transparent"
            StrokeThickness="20"
            Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20"
          />
    
          <!-- Glass Rectangle -->
          <Rectangle x:Name="glassCube" HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            StrokeThickness="2" RadiusX="10" RadiusY="10" Opacity="0"
            Fill="{StaticResource MyGlassBrushResource}"
            RenderTransformOrigin="0.5,0.5">
            <Rectangle.Stroke>
              <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                <LinearGradientBrush.GradientStops>
                  <GradientStop Offset="0.0" Color="LightBlue" />
                  <GradientStop Offset="1.0" Color="Gray" />
                </LinearGradientBrush.GradientStops>
              </LinearGradientBrush>
            </Rectangle.Stroke>
    
            <!-- These transforms have no effect as they
                 are declared here.
                 The reason the transforms are included is to be targets
                 for animation (see later). -->
            <Rectangle.RenderTransform>
              <TransformGroup>
                <ScaleTransform />
                <RotateTransform />
              </TransformGroup>
            </Rectangle.RenderTransform>
    
              <!-- A BevelBitmapEffect is applied to give the button a
                   "Beveled" look. -->
            <Rectangle.BitmapEffect>
              <BevelBitmapEffect />
            </Rectangle.BitmapEffect>
          </Rectangle>
    
          <!-- Present Text of the button. -->
          <DockPanel Name="myContentPresenterDockPanel">
            <ContentPresenter x:Name="myContentPresenter" Margin="20"
              Content="{TemplateBinding  Content}" TextBlock.Foreground="Black" />
          </DockPanel>
        </Grid>
    
        <ControlTemplate.Triggers>       <!-- Set action triggers for the buttons and define            what the button does in response to those triggers. -->     </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
    
  2. 添加属性触发器:将突出显示的标记添加到 ControlTemplate.Triggers 块:

    <ControlTemplate.Triggers>
    
      <!-- Set properties when mouse pointer is over the button. -->   <Trigger Property="IsMouseOver" Value="True">     <!-- Below are three property settings that occur when the           condition is met (user mouses over button).  -->     <!-- Change the color of the outer rectangle when user           mouses over it. -->     <Setter Property ="Rectangle.Stroke" TargetName="outerRectangle"       Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />     <!-- Sets the glass opacity to 1, therefore, the           glass "appears" when user mouses over it. -->     <Setter Property="Rectangle.Opacity" Value="1" TargetName="glassCube" />     <!-- Makes the text slightly blurry as though you           were looking at it through blurry glass. -->     <Setter Property="ContentPresenter.BitmapEffect"        TargetName="myContentPresenter">       <Setter.Value>         <BlurBitmapEffect Radius="1" />       </Setter.Value>     </Setter>   </Trigger>
    
    <ControlTemplate.Triggers/>
    

    按 F5 运行应用程序,并查看在按钮上运行鼠标指针时的效果。

  3. 添加焦点触发器:接下来,添加一些类似的资源库来处理按钮具有焦点的情况(例如,在用户单击它之后)。

    <ControlTemplate.Triggers>
    
      <!-- Set properties when mouse pointer is over the button. -->
      <Trigger Property="IsMouseOver" Value="True">
    
        <!-- Below are three property settings that occur when the
             condition is met (user mouses over button).  -->
        <!-- Change the color of the outer rectangle when user          mouses over it. -->
        <Setter Property ="Rectangle.Stroke" TargetName="outerRectangle"
          Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
    
        <!-- Sets the glass opacity to 1, therefore, the          glass "appears" when user mouses over it. -->
        <Setter Property="Rectangle.Opacity" Value="1"       TargetName="glassCube" />
    
        <!-- Makes the text slightly blurry as though you were          looking at it through blurry glass. -->
        <Setter Property="ContentPresenter.BitmapEffect"       TargetName="myContentPresenter">
          <Setter.Value>
            <BlurBitmapEffect Radius="1" />
          </Setter.Value>
        </Setter>
      </Trigger>
      <!-- Set properties when button has focus. -->   <Trigger Property="IsFocused" Value="true">     <Setter Property="Rectangle.Opacity" Value="1"       TargetName="glassCube" />     <Setter Property="Rectangle.Stroke" TargetName="outerRectangle"       Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />     <Setter Property="Rectangle.Opacity" Value="1" TargetName="glassCube" />   </Trigger>
    
    </ControlTemplate.Triggers>
    

    按 F5 运行应用程序并单击其中一个按钮。 请注意,单击按钮后,按钮保持突出显示状态,因为它仍具有焦点。 如果单击另一个按钮,新按钮将获得焦点,而上一个按钮将失去焦点。

  4. MouseEnterMouseLeave添加动画:接下来为触发器添加一些动画。 在 ControlTemplate.Triggers 块内的任意位置添加以下标记。

    <!-- Animations that start when mouse enters and leaves button. -->
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
      <EventTrigger.Actions>
        <BeginStoryboard Name="mouseEnterBeginStoryboard">
          <Storyboard>
          <!-- This animation makes the glass rectangle shrink in the X direction. -->
            <DoubleAnimation Storyboard.TargetName="glassCube"
              Storyboard.TargetProperty=
              "(Rectangle.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
              By="-0.1" Duration="0:0:0.5" />
            <!-- This animation makes the glass rectangle shrink in the Y direction. -->
            <DoubleAnimation
            Storyboard.TargetName="glassCube"
              Storyboard.TargetProperty=
              "(Rectangle.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
              By="-0.1" Duration="0:0:0.5" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
      <EventTrigger.Actions>
        <!-- Stopping the storyboard sets all animated properties back to default. -->
        <StopStoryboard BeginStoryboardName="mouseEnterBeginStoryboard" />
      </EventTrigger.Actions>
    </EventTrigger>
    

    当鼠标指针移到按钮上时,玻璃矩形会缩小;当指针离开时,玻璃矩形会恢复到正常大小。

    当指针移到按钮上时(引发 MouseEnter 事件),会触发两个动画。 这些动画沿 X 和 Y 轴缩小玻璃矩形。 注意 DoubleAnimation 元素的属性 - DurationByDuration 指定动画效果持续半秒,By 指定玻璃矩形缩小 10%。

    第二个事件触发器 (MouseLeave) 仅用于停止第一个事件触发器。 当你停止 Storyboard 时,所有动画属性都会恢复为默认值。 因此,当用户将指针从按钮上移开时,按钮会回到鼠标指针移到按钮上之前的状态。 有关动画的详细信息,请参阅动画概述

  5. 添加单击按钮时的动画:最后一步是添加用户单击按钮时的触发器。 在 ControlTemplate.Triggers 块内的任意位置添加以下标记:

    <!-- Animation fires when button is clicked, causing glass to spin.  -->
    <EventTrigger RoutedEvent="Button.Click">
      <EventTrigger.Actions>
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimation Storyboard.TargetName="glassCube"
              Storyboard.TargetProperty=
              "(Rectangle.RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"
              By="360" Duration="0:0:0.5" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
    

    按 F5 运行应用程序并单击其中一个按钮。 单击按钮时,玻璃矩形会旋转。

总结

在本演练中,你执行了以下练习:

  • Style 的目标设置为对象类型 (Button)。

  • 使用 Style 控制整个应用程序中按钮的基本属性。

  • 创建渐变等资源,用于 Style 资源库的属性值。

  • 通过将模板应用于按钮来自定义整个应用程序中按钮的外观。

  • 自定义按钮的行为(包括动画效果),以响应用户操作(例如 MouseEnterMouseLeaveClick)。

另请参阅