WPF自定义控件 按钮 (一)

从这里开始,记录WPF自定义控件的制作方法,相比制作传统winform的自定义控件来说,WPF自定义控件做起来更加优雅,效果也更好。

从最常用的控件 Button 开始:

首先从MSDN上查看 Button 控件的默认样式和模板,如下:

<Style TargetType="Button">
      <Setter Property="Background" Value="#FF1F3B53"/>
      <Setter Property="Foreground" Value="#FF000000"/>
      <Setter Property="Padding" Value="3"/>
      <Setter Property="BorderThickness" Value="1"/>
      <Setter Property="BorderBrush">
          <Setter.Value>
              <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                  <GradientStop Color="#FFA3AEB9" Offset="0"/>
                  <GradientStop Color="#FF8399A9" Offset="0.375"/>
                  <GradientStop Color="#FF718597" Offset="0.375"/>
                  <GradientStop Color="#FF617584" Offset="1"/>
              </LinearGradientBrush>
          </Setter.Value>
      </Setter>
      <Setter Property="Template">
          <Setter.Value>
              <ControlTemplate TargetType="Button">
                  <Grid>
                      <vsm:VisualStateManager.VisualStateGroups>
                          <vsm:VisualStateGroup x:Name="CommonStates">
                              <vsm:VisualState x:Name="Normal"/>
                              <vsm:VisualState x:Name="MouseOver">
                                  <Storyboard>
                                      <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
                                  </Storyboard>
                              </vsm:VisualState>
                              <vsm:VisualState x:Name="Pressed">
                                  <Storyboard>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF6DBDD1"/>
                                      <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#D8FFFFFF"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#C6FFFFFF"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#8CFFFFFF"/>
                                      <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#3FFFFFFF"/>
                                  </Storyboard>
                              </vsm:VisualState>
                              <vsm:VisualState x:Name="Disabled">
                                  <Storyboard>
                                      <DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
                                  </Storyboard>
                              </vsm:VisualState>
                          </vsm:VisualStateGroup>
                          <vsm:VisualStateGroup x:Name="FocusStates">
                              <vsm:VisualState x:Name="Focused">
                                  <Storyboard>
                                      <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1"/>
                                  </Storyboard>
                              </vsm:VisualState>
                              <vsm:VisualState x:Name="Unfocused" />
                          </vsm:VisualStateGroup>
                      </vsm:VisualStateManager.VisualStateGroups>
                      <Border x:Name="Background" CornerRadius="3" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                          <Grid Background="{TemplateBinding Background}"  Margin="1">
                              <Border Opacity="0"  x:Name="BackgroundAnimation" Background="#FF448DCA" />
                              <Rectangle x:Name="BackgroundGradient" >
                                  <Rectangle.Fill>
                                      <LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
                                          <GradientStop Color="#FFFFFFFF" Offset="0" />
                                          <GradientStop Color="#F9FFFFFF" Offset="0.375" />
                                          <GradientStop Color="#E5FFFFFF" Offset="0.625" />
                                          <GradientStop Color="#C6FFFFFF" Offset="1" />
                                      </LinearGradientBrush>
                                  </Rectangle.Fill>
                              </Rectangle>
                          </Grid>
                      </Border>
                      <ContentPresenter
                              x:Name="contentPresenter"
                              Content="{TemplateBinding Content}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              Margin="{TemplateBinding Padding}"/>
                      <Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
                      <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Margin="1" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
                  </Grid>
              </ControlTemplate>
          </Setter.Value>
      </Setter>
  </Style>

由于历史遗留原因,现在上面的 vsm 名称空间可以直接删掉即可;

接下来演示,如何一步步制作自己的自定义按钮控件:

准备工具:visual studio 2015 (或VS2010,2012,2013都可以)


1.打开VS,新建 WPF自定义控件库 项目:



2.新建完成后,vs自动生成了一些文件,如 Themes 文件夹,以及文件夹下面的 Genric.xaml 文件;还有CustomControl1.cs文件,这个不用管它,直接删除即可,同时也把Genric.xaml 文件中关于CustomControl1的style节点也删除;

3.在项目下新建一个 Controls 文件夹,在文件夹里新建一个自定义控件(WPF) MyButton1.cs 和资源字典(WPF) MyButton1.xaml, 并在Genric.xaml中引用该资源字典:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfCustomControlLibrary">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/WpfCustomControlLibrary;component/Controls/MyButton1.xaml"/>
    </ResourceDictionary.MergedDictionaries>


</ResourceDictionary>

4.修改 MyButton1.cs 代码, 使 MyButton 类继承 Button (默认继承自 Control ) , 最终代码如下 :

    public class MyButton1 : Button
    {
        static MyButton1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton1), new FrameworkPropertyMetadata(typeof(Button)));
        }
    }

5.到此为止,一个默认原生的按钮就产生了(和系统自带的 Button 一样),接下来新建一个WPF应用程序用来测试这个控件,在项目中引用上面生成的dll, 新建两个Button,一个是系统自带的,一个是刚刚自己制作的:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:controls="clr-namespace:WpfCustomControlLibrary.Controls;assembly=WpfCustomControlLibrary"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Content="Default Button" Width="100" Height="50"></Button>
        <controls:MyButton1  Grid.Row="0" Grid.Column="1" Content="MyButton1" Width="100" Height="50"></controls:MyButton1>
    </Grid>
</Window>

6.运行看看它俩的效果,是一模一样的:


7.接下来就将MSDN上的 Button 默认模板复制下来 ,放到 资源字典 MyButton1.xaml 里:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfCustomControlLibrary.Controls">


    <Style TargetType="{x:Type local:MyButton1}">
        <Setter Property="Background" Value="#FF1F3B53"/>
        <Setter Property="Foreground" Value="#FF000000"/>
        <Setter Property="Padding" Value="3"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFA3AEB9" Offset="0"/>
                    <GradientStop Color="#FF8399A9" Offset="0.375"/>
                    <GradientStop Color="#FF718597" Offset="0.375"/>
                    <GradientStop Color="#FF617584" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF6DBDD1"/>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#D8FFFFFF"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#C6FFFFFF"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#8CFFFFFF"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#3FFFFFFF"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="Background" CornerRadius="3" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                            <Grid Background="{TemplateBinding Background}"  Margin="1">
                                <Border Opacity="0"  x:Name="BackgroundAnimation" Background="#FF448DCA" />
                                <Rectangle x:Name="BackgroundGradient" >
                                    <Rectangle.Fill>
                                        <LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
                                            <GradientStop Color="#FFFFFFFF" Offset="0" />
                                            <GradientStop Color="#F9FFFFFF" Offset="0.375" />
                                            <GradientStop Color="#E5FFFFFF" Offset="0.625" />
                                            <GradientStop Color="#C6FFFFFF" Offset="1" />
                                        </LinearGradientBrush>
                                    </Rectangle.Fill>
                                </Rectangle>
                            </Grid>
                        </Border>
                        <ContentPresenter
                              x:Name="contentPresenter"
                              Content="{TemplateBinding Content}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              Margin="{TemplateBinding Padding}"/>
                        <Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
                        <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Margin="1" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
</ResourceDictionary>

然后再将 MyButton1 类代码修改成:

    public class MyButton1 : Button
    {
        static MyButton1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton1), new FrameworkPropertyMetadata(typeof(MyButton1)));
        }
    }

8.重新编译类库项目,运行测试项目,发现俩按钮样式有点变化了,自定义按钮看上去变搓了,这是因为使用了MSDN默认模板的原因:



9.接下来,只要修改一下默认模板,就可以改变其样式,例如将模板修改成这样:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfCustomControlLibrary.Controls">


    <Style TargetType="{x:Type local:MyButton1}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition From="Normal" GeneratedDuration="0:0:0.3" To="MouseOver"/>
                                    <VisualTransition From="MouseOver" GeneratedDuration="0:0:0.3" To="Normal"/>
                                    <VisualTransition From="Pressed" GeneratedDuration="0:0:0.2" To="Normal"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="ellipse">
                                            <EasingColorKeyFrame KeyTime="0" Value="#FFEFE9F0"/>
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="ellipse">
                                            <EasingColorKeyFrame KeyTime="0" Value="#FF650A6E"/>
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Ellipse x:Name="ellipse" Stroke="Black">
                            <Ellipse.Fill>
                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="Black" Offset="0"/>
                                    <GradientStop Color="#FFF376FF" Offset="0.566"/>
                                </LinearGradientBrush>
                            </Ellipse.Fill>
                        </Ellipse>
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsFocused" Value="True"/>
                        <Trigger Property="IsDefaulted" Value="True"/>
                        <Trigger Property="IsMouseOver" Value="True"/>
                        <Trigger Property="IsPressed" Value="True"/>
                        <Trigger Property="IsEnabled" Value="False"/>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
</ResourceDictionary>

10.运行测试项目,效果如下:



11.如果你觉得,这些模板效果包括渐变,动画等等样式,是手工一行行代码敲出来的?那你就错了,只要用Blend 1分钟就能做出任何你想要的效果(当然了,还是需要点审美的。。。)!


就拿上面的自定义按钮举个例子,只需要打开Blend (我这里用的是Blend for Visual Studio 2015):

1.新建一个WPF项目,再新建一个用户控件,把容器类型改成 Canvas:



2.拖一个椭圆到画布上,高度和宽都设置成一样,这样就呈现一个圆形了,填充颜色设置成渐变,颜色自己随便取:


3.选中椭圆,然后在Blend工具栏上的 工具->构成控件->选择 Button 这样一个Button就算初步完成了, 可以从对象和时间线看到这个模板是由一个 Ellipse+ContentPresenter 构成:



4.但是这样是不太合适的,因为按钮一般有 鼠标经过+鼠标点击 样式,这里在 状态 视图中,为其增加各种状态效果,比如从默认一般情况到鼠标滑过,填充色在0.3秒内从渐变色A到渐变色B;从鼠标点击到恢复默认情况,填充色在0.2秒内从渐变色C到渐变色A:



5.这些设置好之后,可以看到对应的xaml文件自动生成了Template代码,这个代码就是我们在MyButton1.xaml中使用的Template代码,可以直接复制过去使用。所以还是比较简单的。



以上关于自定义控件 Button 样式 ,先记录到此,接下来记录相对复杂的自定义Button控件。




猜你喜欢

转载自blog.csdn.net/sudazf/article/details/78026685