WPF 动画:绑定到情节提要动画的“To”属性

Posted

技术标签:

【中文标题】WPF 动画:绑定到情节提要动画的“To”属性【英文标题】:WPF animation: binding to the "To" attribute of storyboard animation 【发布时间】:2011-01-12 07:36:06 【问题描述】:

我正在尝试创建一个行为类似于 iPhone 上的“滑动”按钮的按钮。我有一个调整按钮位置和宽度的动画,但我希望这些值基于控件中使用的文本。目前,它们是硬编码的。

到目前为止,这是我的工作 XAML:

<CheckBox x:Class="Smt.Controls.SlideCheckBox"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:local="clr-namespace:Smt.Controls"
          xmlns:System.Windows="clr-namespace:System.Windows;assembly=PresentationCore"
          Name="SliderCheckBox"
          mc:Ignorable="d">
    <CheckBox.Resources>
        <System.Windows:Duration x:Key="AnimationTime">0:0:0.2</System.Windows:Duration>
        <Storyboard x:Key="OnChecking">
            <DoubleAnimation Storyboard.TargetName="CheckButton"
                             Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
                             Duration="StaticResource AnimationTime"
                             To="40" />
            <DoubleAnimation Storyboard.TargetName="CheckButton"
                             Storyboard.TargetProperty="(Button.Width)"
                             Duration="StaticResource AnimationTime"
                             To="41" />
        </Storyboard>
        <Storyboard x:Key="OnUnchecking">
            <DoubleAnimation Storyboard.TargetName="CheckButton"
                             Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
                             Duration="StaticResource AnimationTime"
                             To="0" />
            <DoubleAnimation Storyboard.TargetName="CheckButton"
                             Storyboard.TargetProperty="(Button.Width)"
                             Duration="StaticResource AnimationTime"
                             To="40" />
        </Storyboard>
        <Style x:Key="SlideCheckBoxStyle"
               TargetType="x:Type local:SlideCheckBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="x:Type local:SlideCheckBox">
                        <Canvas>
                            <ContentPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels"
                                              Content="TemplateBinding Content"
                                              ContentTemplate="TemplateBinding ContentTemplate"
                                              RecognizesAccessKey="True"
                                              VerticalAlignment="Center"
                                              HorizontalAlignment="Center" />
                            <Canvas>
                                <!--Background-->
                                <Rectangle Width="Binding ElementName=ButtonText, Path=ActualWidth"
                                           Height="Binding ElementName=ButtonText, Path=ActualHeight"
                                           Fill="LightBlue" />
                            </Canvas>
                            <Canvas>
                                <!--Button-->
                                <Button Width="Binding ElementName=CheckedText, Path=ActualWidth"
                                        Height="Binding ElementName=ButtonText, Path=ActualHeight"
                                        Name="CheckButton"
                                        Command="x:Static local:SlideCheckBox.SlideCheckBoxClicked">
                                    <Button.RenderTransform>
                                        <TransformGroup>
                                            <TranslateTransform />
                                        </TransformGroup>
                                    </Button.RenderTransform>
                                </Button>
                            </Canvas>
                            <Canvas>
                                <!--Text-->
                                <StackPanel Name="ButtonText"
                                            Orientation="Horizontal"
                                            IsHitTestVisible="False">
                                    <Grid Name="CheckedText">
                                        <Label Margin="7 0"
                                               Content="Binding RelativeSource=RelativeSource AncestorType=x:Type local:SlideCheckBox, Path=CheckedText" />
                                    </Grid>
                                    <Grid Name="UncheckedText"
                                          HorizontalAlignment="Right">
                                        <Label Margin="7 0"
                                               Content="Binding RelativeSource=RelativeSource AncestorType=x:Type local:SlideCheckBox, Path=UncheckedText" />
                                    </Grid>
                                </StackPanel>
                            </Canvas>
                        </Canvas>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked"
                                     Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="StaticResource OnChecking" />
                                </Trigger.EnterActions>
                                <Trigger.ExitActions>
                                    <BeginStoryboard Storyboard="StaticResource OnUnchecking" />
                                </Trigger.ExitActions>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </CheckBox.Resources>
    <CheckBox.CommandBindings>
        <CommandBinding Command="x:Static local:SlideCheckBox.SlideCheckBoxClicked"
                        Executed="OnSlideCheckBoxClicked" />
    </CheckBox.CommandBindings>
</CheckBox>

以及背后的代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Smt.Controls

    public partial class SlideCheckBox : CheckBox
    
        public SlideCheckBox()
        
            InitializeComponent();
            Loaded += OnLoaded;
        

        public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text"));
        public string CheckedText
        
            get  return (string)GetValue(CheckedTextProperty); 
            set  SetValue(CheckedTextProperty, value); 
        

        public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text"));
        public string UncheckedText
        
            get  return (string)GetValue(UncheckedTextProperty); 
            set  SetValue(UncheckedTextProperty, value); 
        

        public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand();

        void OnLoaded(object sender, RoutedEventArgs e)
        
            Style style = TryFindResource("SlideCheckBoxStyle") as Style;
            if (!ReferenceEquals(style, null))
            
                Style = style;
            
        

        void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e)
        
            IsChecked = !IsChecked;
        
    

当我尝试将 DoubleAnimations 中的“To”属性绑定到文本的实际宽度时,问题就出现了,这与我在 ControlTemplate 中所做的相同。如果我将值绑定到 ControlTemplate 中某个元素的 ActualWidth,则该控件将显示为一个空白复选框(我的基类)。但是,我在 ControlTemplate 本身中绑定到相同的 ActualWidths 没有任何问题。似乎只是 CheckBox.Resources 有问题。

例如,以下内容会破坏它:

        <DoubleAnimation Storyboard.TargetName="CheckButton"
                         Storyboard.TargetProperty="(Button.Width)"
                         Duration="StaticResource AnimationTime"
                         To="Binding ElementName=CheckedText, Path=ActualWidth" />

我不知道这是因为它试图绑定到一个在渲染过程完成之前不存在的值,还是因为它是其他原因。有人对这种动画绑定有任何经验吗?

【问题讨论】:

【参考方案1】:

我在ControlTemplates 遇到过类似的情况,我想将“To”属性绑定到一个值(而不是硬编码),我终于找到了解决方案

快速旁注:如果您在网络上四处搜索,您会发现examples 的人们能够对“From”或“To”属性使用数据绑定。但是,在这些示例中,情节提要是 not in a Style or ControlTemplate。如果您的 Storyboard 在 Style 或 ControlTemplate 中,您将不得不使用不同的方法,例如此解决方案。

此解决方案解决了可冻结问题,因为它只是将双精度值从 0 变为 1。它巧妙地使用了 Tag 属性和乘法转换器。您使用多重绑定来绑定所需的属性和您的“比例”(标签),它们会相乘。基本上这个想法是你的标签值是你的动画,一旦你将标签动画为 1,它的值就像一个“比例”(从 0 到 1)将你想要的属性值变为“全比例”。

您可以在here 中看到这一点。关键是:

<local:MultiplyConverter x:Key="multiplyConverter" />
<ControlTemplate x:Key="RevealExpanderTemp" TargetType="x:Type Expander">
    <!-- (other stuff here...) -->
    <ScrollViewer x:Name="ExpanderContentScrollView">
        <!-- ** BEGIN IMPORTANT PART #1 ...  -->
        <ScrollViewer.Tag>
            <sys:Double>0.0</sys:Double>
        </ScrollViewer.Tag>
        <ScrollViewer.Height>
            <MultiBinding Converter="StaticResource multiplyConverter">
               <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
               <Binding Path="Tag" RelativeSource="RelativeSource Self" />
            </MultiBinding>
        </ScrollViewer.Height>
        <!-- ...end important part #1.  -->
        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>

    </ScrollViewer>

  <ControlTemplate.Triggers>
    <Trigger Property="IsExpanded" Value="True">
        <Trigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                   <!-- ** BEGIN IMPORTANT PART #2 (make TargetProperty 'Tag') ...  -->
                   <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                         Storyboard.TargetProperty="Tag"
                         To="1"
                         Duration="0:0:0.4"/>
                    <!-- ...end important part #2 -->
               </Storyboard>
            </BeginStoryboard>
        </Trigger.EnterActions>
    </Trigger>
  </ControlTemplate.Triggers>
</ControlTemplate>

使用此值转换器:

public class MultiplyConverter : IMultiValueConverter

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    
       double result = 1.0;
       for (int i = 0; i < values.Length; i++)
       
           if (values[i] is double)
               result *= (double)values[i];
       

       return result;
    

   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
   
       throw new Exception("Not implemented");
   

【讨论】:

除了绑定到我的视图模型中的高度,我该怎么做? 你能解释一下为什么这与简单的绑定不同吗? @Jason Frank 我找到了解决冻结问题的好方法,并查看我的解决方案 我收到此异常:无法使用“System.Windows.Media.Animation.DoubleAnimationUsingKeyFrames”为“System.Windows.Controls.Border”上的“Tag”属性设置动画。【参考方案2】:

据我所知,您不能将动画绑定到/从,因为动画必须是可冻结的。

【讨论】:

【参考方案3】:

我喜欢@Jason Frank 的解决方案。但是,如果您不使用标签,它会更容易且不易出错,而是使用例如空虚拟边框元素的 Width 属性。它是本机双重属性,因此不需要&lt;sys:Double&gt; 语法,您可以像使用这样的变量一样命名 Border:

<!-- THIS IS JUST USED FOR SLIDING ANIMATION MATH -->
<!-- animated Border.Width    From 0 to 1 -->
<Border x:Name="Var_Animation_0to1" Width="0"/>
<!-- animated Border.Width    From 0 to (TotalWidth-SliderWidth) -->
<Border x:Name="Var_Slide_Length">
    <Border.Width>
        <MultiBinding Converter="mvvm:MathConverter" ConverterParameter="a * (b-c)">
            <Binding ElementName="Var_Animation_0to1" Path="Width"/>
            <Binding ElementName="BackBorder" Path="ActualWidth"/>
            <Binding ElementName="Slider" Path="ActualWidth"/>
        </MultiBinding>
    </Border.Width>
</Border>

这使得绑定更具可读性。

正如 Jason 指出的那样,动画始终为 0..1:

<BeginStoryboard Name="checkedSB">
    <Storyboard Storyboard.TargetProperty="Width" Storyboard.TargetName="Var_Animation_0to1">
        <DoubleAnimation To="1" Duration="00:00:00.2"/>
    </Storyboard>
</BeginStoryboard>

然后将您想要动画的任何内容绑定到虚拟边框的宽度。这样,您甚至可以像这样将转换器相互链接:

<Border x:Name="Slider" HorizontalAlignment="Left"
        Margin="Binding ElementName=Var_Slide_Length, Path=Width, Converter=StaticResource DoubleToThickness, ConverterParameter=x 0 0 0"/>

结合 MathConverter,您几乎可以在样式中做任何事情: https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML

【讨论】:

【参考方案4】:

我实现了这个确切的东西。

<UserControl x:Class="YOURNAMESPACE.UserControls.SliderControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:YOURNAMESPACE.UserControls"
             xmlns:converter="clr-namespace:YOURNAMESPACE.ValueConverters"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             SizeChanged="UserControl_SizeChanged">
    <UserControl.Resources>

        <converter:MathConverter x:Key="mathConverter" />

        <LinearGradientBrush x:Key="CheckedBlue" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Color="#e4f5fc" Offset="0" />
            <GradientStop Color="#e4f5fc" Offset="0.1" />
            <GradientStop Color="#e4f5fc" Offset="0.1" />
            <GradientStop Color="#9fd8ef" Offset="0.5" />
            <GradientStop Color="#9fd8ef" Offset="0.5" />
            <GradientStop Color="#bfe8f9" Offset="1" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="CheckedOrange" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Color="#FFCA6A13" Offset="0" />
            <GradientStop Color="#FFF67D0C" Offset="0.1" />
            <GradientStop Color="#FFFE7F0C" Offset="0.1" />
            <GradientStop Color="#FFFA8E12" Offset="0.5" />
            <GradientStop Color="#FFFF981D" Offset="0.5" />
            <GradientStop Color="#FFFCBC5A" Offset="1" />
        </LinearGradientBrush>

        <SolidColorBrush x:Key="CheckedOrangeBorder" Color="#FF8E4A1B" />
        <SolidColorBrush x:Key="CheckedBlueBorder" Color="#FF143874" />

        <Style x:Key="CheckBoxSlider" TargetType="x:Type CheckBox">
            <Setter Property="Foreground" Value="DynamicResource x:Static SystemColors.WindowTextBrushKey" />
            <Setter Property="Background" Value="DynamicResource x:Static SystemColors.WindowBrushKey" />

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="x:Type CheckBox" >

                        <DockPanel x:Name="dockPanel" 
                               Width="TemplateBinding ActualWidth" 
                               Height="TemplateBinding Height" >
                            <DockPanel.Resources>

                                <Storyboard x:Key="ShowRightStoryboard">
                                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
                                        <SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>

                                <Storyboard x:Key="ShowLeftStoryboard" >
                                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                           Storyboard.TargetName="slider" 
                                                           Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"
                                                           >
                                        <SplineDoubleKeyFrame x:Name="RightHalfKeyFrame" KeyTime="00:00:00.1000000" Value="300" />
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </DockPanel.Resources>

                            <ContentPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels" 
                                          Content="TemplateBinding Content" 
                                          ContentStringFormat="TemplateBinding ContentStringFormat" 
                                          ContentTemplate="TemplateBinding ContentTemplate" 
                                          RecognizesAccessKey="True" 
                                          VerticalAlignment="Center" />
                            <Grid>

                                <Border x:Name="BackgroundBorder" BorderBrush="#FF939393" BorderThickness="1" CornerRadius="3" 

                                Width="TemplateBinding ActualWidth" 
                                Height="TemplateBinding Height" >

                                    <Border.Background>
                                        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                            <GradientStop Color="#FFB5B5B5" Offset="0" />
                                            <GradientStop Color="#FFDEDEDE" Offset="0.1" />
                                            <GradientStop Color="#FFEEEEEE" Offset="0.5" />
                                            <GradientStop Color="#FFFAFAFA" Offset="0.5" />
                                            <GradientStop Color="#FFFEFEFE" Offset="1" />
                                        </LinearGradientBrush>
                                    </Border.Background>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition />
                                            <ColumnDefinition />
                                        </Grid.ColumnDefinitions>
                                        <TextBlock x:Name="LeftTextBlock"  Text="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:SliderControl, Path=LeftText, Mode=TwoWay" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center"  />
                                        <TextBlock x:Name="RightTextBlock"  Text="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:SliderControl, Path=RightText, Mode=TwoWay" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"  />
                                    </Grid>
                                </Border>

                                <Border x:Name="slider" 
                                    BorderBrush="#FF939393" 
                                    HorizontalAlignment="Left" 
                                    Width="TemplateBinding ActualWidth, Converter=StaticResource mathConverter, ConverterParameter=/2" 
                                    Height="TemplateBinding Height"
                                    BorderThickness="1" 
                                    CornerRadius="3" 
                                    RenderTransformOrigin="0.5,0.5" Margin="0"
                                    >
                                    <Border.RenderTransform>
                                        <TransformGroup>
                                            <ScaleTransform ScaleX="1" ScaleY="1" />
                                            <SkewTransform AngleX="0" AngleY="0" />
                                            <RotateTransform Angle="0" />
                                            <TranslateTransform X="TemplateBinding ActualWidth, Converter=StaticResource mathConverter, ConverterParameter=/2" Y="0" />
                                        </TransformGroup>
                                    </Border.RenderTransform>
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                                            <GradientStop Color="#FFF0F0F0" Offset="0" />
                                            <GradientStop Color="#FFCDCDCD" Offset="0.1" />
                                            <GradientStop Color="#FFFBFBFB" Offset="1" />
                                        </LinearGradientBrush>
                                    </Border.Background>
                                    <DockPanel Background="Transparent" LastChildFill="False">
                                        <Viewbox x:Name="SlideRight" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Right" Margin="0,0,50,0" >
                                            <Path  Stretch="Fill"  Fill="DynamicResource TextBrush">
                                                <Path.Data>
                                                    <PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
                                                </Path.Data>
                                            </Path>
                                        </Viewbox>
                                        <Viewbox x:Name="SlideLeft" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Left" Margin="50,0,0,0" >
                                            <Path  Stretch="Fill"  Fill="DynamicResource TextBrush">
                                                <Path.LayoutTransform>
                                                    <TransformGroup>
                                                        <ScaleTransform ScaleX="-1"/>
                                                    </TransformGroup>
                                                </Path.LayoutTransform>
                                                <Path.Data>
                                                    <PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
                                                </Path.Data>
                                            </Path>
                                        </Viewbox>
                                    </DockPanel>
                                </Border>
                            </Grid>
                        </DockPanel>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="BackgroundBorder" Property="Background" Value="StaticResource CheckedOrange" />
                                <Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="StaticResource CheckedOrangeBorder" />
                                <Setter TargetName="SlideRight" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="SlideLeft" Property="Visibility" Value="Visible" />
                            </Trigger>
                            <Trigger Property="IsChecked" Value="False">
                                <Setter TargetName="BackgroundBorder" Property="Background" Value="StaticResource CheckedBlue" />
                                <Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="StaticResource CheckedBlueBorder" />
                                <Setter TargetName="SlideRight" Property="Visibility" Value="Visible" />
                                <Setter TargetName="SlideLeft" Property="Visibility" Value="Collapsed" />
                            </Trigger>
                        </ControlTemplate.Triggers>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>


    </UserControl.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition  Width="*"/>
        </Grid.ColumnDefinitions>

        <CheckBox   x:Name="checkBox" 
                    Style="StaticResource CheckBoxSlider" 
                    HorizontalAlignment="Stretch"
                    DockPanel.Dock="Top" 
                    Height="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:SliderControl, Path=Height, Mode=TwoWay"
                    IsChecked="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:SliderControl, Path=IsLeftVisible, Mode=TwoWay"
                    Checked="CheckBox_Checked" 
                    Unchecked="CheckBox_Unchecked"
                    />
    </Grid>
</UserControl>

后面的代码。

namespace YOURNAMESPACE.UserControls

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    /// <summary>
    /// Interaction logic for SliderControl.xaml
    /// </summary>
    public partial class SliderControl : UserControl
    
        public static readonly DependencyProperty IsLeftVisibleProperty =
         DependencyProperty.RegisterAttached(
             "IsLeftVisible",
             typeof(bool),
             typeof(SliderControl),
             new UIPropertyMetadata(true, IsLeftVisibleChanged));

        public static readonly DependencyProperty LeftTextProperty =
            DependencyProperty.RegisterAttached(
                "LeftText",
                typeof(string),
                typeof(SliderControl),
                new UIPropertyMetadata(null, LeftTextChanged));

        public static readonly DependencyProperty RightTextProperty =
        DependencyProperty.RegisterAttached(
            "RightText",
            typeof(string),
            typeof(SliderControl),
            new UIPropertyMetadata(null, RightTextChanged));

         /// <summary>
        /// Initializes a new instance of the <see cref="SliderControl"/> class.
        /// </summary>
        public SliderControl()
        
            this.InitializeComponent();
        

        public string LeftText  get; set; 

        public string RightText  get; set; 

        [AttachedPropertyBrowsableForType(typeof(SliderControl))]
        public static bool GetIsLeftVisible(SliderControl sliderControl)
        
            return (bool)sliderControl.GetValue(IsLeftVisibleProperty);
        

        [AttachedPropertyBrowsableForType(typeof(SliderControl))]
        public static string GetLeftText(SliderControl sliderControl)
        
            return (string)sliderControl.GetValue(LeftTextProperty);
        

        public static void SetIsLeftVisible(SliderControl sliderControl, bool value)
        
            sliderControl.SetValue(IsLeftVisibleProperty, value);
        

        public static void IsLeftVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            SliderControl slider = d as SliderControl;

            if ((bool)e.NewValue == true)
            
                slider.RunAnimation("ShowLeftStoryboard");
            
            else
            
                slider.RunAnimation("ShowRightStoryboard");
            
        

        [AttachedPropertyBrowsableForType(typeof(SliderControl))]
        public static string GetRightText(SliderControl sliderControl)
        
            return (string)sliderControl.GetValue(RightTextProperty);
        

        public static void SetLeftText(SliderControl sliderControl, string value)
        
            sliderControl.SetValue(LeftTextProperty, value);
        

        public static void SetRightText(SliderControl sliderControl, string value)
        
            sliderControl.SetValue(RightTextProperty, value);
        

        private static void LeftTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            SliderControl slider = d as SliderControl;
            slider.LeftText = e.NewValue as string;
        

        private static void RightTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        
            SliderControl slider = d as SliderControl;
            slider.RightText = e.NewValue as string;
        

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        
            this.checkBox.Width = e.NewSize.Width;

            CheckBox cb = this.checkBox;
            var controlTemplate = cb.Template;

            DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
            Storyboard story = dockPanel.Resources["ShowLeftStoryboard"] as Storyboard;

            DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
            SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;

            // must manipulate this in code behind, binding does not work, 
            // also it cannot be inside of a control template 
            // because storyboards in control templates become frozen and cannot be modified
            sk.Value = cb.Width / 2;

            if (cb.IsChecked == true)
            
                story.Begin(cb, cb.Template);
            
        

        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        
            this.RunAnimation("ShowLeftStoryboard");
        

        private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
        
            this.RunAnimation("ShowRightStoryboard");
        

        private void RunAnimation(string storyboard)
        
            CheckBox cb = this.checkBox;
            var controlTemplate = cb.Template;

            DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;

            if (dockPanel != null)
            
                Storyboard story = dockPanel.Resources[storyboard] as Storyboard;

                DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
                SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
                story.Begin(cb, cb.Template);
            
        
    

IValue 转换器

namespace YOURNAMESPACE.ValueConverters

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Data;
      /// <summary>
        /// Does basic math operations, eg. value = 60 parameter = "*2 + 1", result = 121
        /// </summary>
        /// <seealso cref="System.Windows.Data.IValueConverter" />
        [ValueConversion(typeof(double), typeof(double))]
        public class MathConverter : IValueConverter
        
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            
                double d = (double)value;
                string mathExpression = d.ToString() + parameter;

                if (mathExpression.Contains("^"))
                
                    throw new Exception("Doesn't handle powers or square roots");
                

                DataTable table = new DataTable();
                table.Columns.Add("expression", typeof(string), mathExpression);
                DataRow row = table.NewRow();
                table.Rows.Add(row);
                double ret = double.Parse((string)row["expression"]);

                if (ret <= 0)
                
                    return 1d;
                

                return ret;
            

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            
                // not implemented
                return null;
            
        

示例用法:

<chart:SliderControl x:Name="sliderControl" 
                                                         LeftText="Binding Path=LeftTextProperty"
                                                         RightText="Binding Path=RightTextProperty"
                                                         Grid.Row="0" 
                                                         IsLeftVisible="Binding Path=IsLeftVisible, Mode=TwoWay" 
                                                         Height="35"  />

【讨论】:

以上是关于WPF 动画:绑定到情节提要动画的“To”属性的主要内容,如果未能解决你的问题,请参考以下文章

代码中的 WPF 动画 Grid.VisibilityProperty

WPF - 将滑块值数据绑定到 StoryBoard?

WPF 用Binding绑定一个属性,能否带上一个动画?比如我绑定了控件的高度。 但是我希望改变高度时呈现动画

如何为 WPF 故事板中的静态对象设置动画

Wpf动画如何移动grid?to属性好像只能为数字,但Grid定位有四个数据,并且执行动画方法参数

WPF(Windows10通用应用程序开发) 线性插值动画 or 帧动画学习笔记及实例