2010-02-02 13 views
24

Estoy intentando crear un botón que se comporte de forma similar al botón "deslizar" en el iPhone. Tengo una animación que ajusta la posición y el ancho del botón, pero quiero que estos valores se basen en el texto utilizado en el control. Actualmente, están codificados.Animación WPF: vinculando al atributo "A" de la animación del guión gráfico

Aquí es mi XAML de trabajo, hasta el momento:

<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> 

Y el código subyacente:

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; 
     } 
    } 
} 

El problema viene cuando intento para unir el atributo "A" en los DoubleAnimations a la actual ancho del texto, lo mismo que estoy haciendo en ControlTemplate. Si enlace los valores a un Ancho real de un elemento en ControlTemplate, el control aparece como una casilla en blanco (mi clase base). Sin embargo, estoy vinculando a los mismos Anchos reales en el ControlTemplate mismo sin ningún problema. Simplemente parece ser el CheckBox. Recursos que tienen un problema con él.

Por ejemplo, lo siguiente será romperlo:

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

No sé si esto se debe a que está tratando de unirse a un valor que no existe hasta que un pase rendir está hecho, o si es otra cosa Alguien tiene alguna experiencia con este tipo de enlace de animación?

Respuesta

8

Hasta donde yo sé, no se puede vincular la animación a/desde porque la animación tiene que ser congelable.

43

que he tenido situaciones similares en ControlTemplate s donde yo he querido obligar a la "A" a atribuir un valor (en lugar de la codificación dura de ella), y finalmente encontrado una solución.

Nota rápida: si busca en la web encontrará examples de personas que pueden usar el enlace de datos para las propiedades "De" o "Para". Sin embargo, en esos ejemplos, los guiones gráficos son not in a Style or ControlTemplate. Si su Storyboard está en un estilo o plantilla de control, tendrá que usar un enfoque diferente, como esta solución.

Esta solución soluciona el problema de freezable porque simplemente anima un valor doble de 0 a 1. Funciona con un uso inteligente de la propiedad Tag y un convertidor Multiply. Utiliza una multiburación para vincular tanto una propiedad deseada como su "escala" (la Etiqueta), que se multiplican juntas. Básicamente, la idea es que su valor de etiqueta es lo que anima, y ​​su valor actúa como una "escala" (de 0 a 1) llevando el valor de atributo deseado a "escala completa" una vez que haya animado la etiqueta a 1.

Puede ver esto en acción here. El quid de la cuestión es la siguiente:

<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> 

Con este convertidor de valores:

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"); 
    } 
} 
+0

¿Cómo hacer esto, pero se unen a una altura en mi opinión ¿modelo? – claudekennilol

+2

¿Puede explicarme por qué esta encuadernación diferente de simple? –

+0

@Jason Frank Encontré una buena manera de vencer el problema de la congelación, así que echa un vistazo a mi solución – 0x4f3759df

1

que implementan esta cosa exacta.

<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> 

código detrás.

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); 
      } 
     } 
    } 
} 

IValueConverter

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; 
      } 
     } 
} 

ejemplo de uso:

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

me gusta la solución de @ Jason Frank. Sin embargo, es aún más fácil y menos propenso a errores si no usa la etiqueta, sino que, por ejemplo, la propiedad Ancho de un elemento de borde ficticio vacío. Es una propiedad nativa doble, así que no hay necesidad de y se puede nombrar la frontera al igual que lo haría con una variable de este modo:

<!-- THIS IS JUST THE SLIDING AMIMATION 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> 

Eso hace que los enlaces mucho más legible.

La animación es siempre 0..1, como Jason señaló:

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

A continuación, se unen lo que desea animar a la anchura de la frontera ficticia. De esta manera puede incluso convertidores cadena entre sí de esta manera:

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

En combinación con el MathConverter que puede hacer casi cualquier cosa en los estilos: https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML

Cuestiones relacionadas