wpf data-binding animation user-controls

Animación WPF: enlace al atributo "A" de la animación del guión gráfico



Intento crear un botón que se comporte de manera 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.

Aquí está mi trabajo XAML, hasta ahora:

<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 detrás:

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 surge cuando trato de vincular el atributo "Para" en las Anotaciones Dobladas con el ancho real del texto, de la misma manera que lo estoy haciendo en el 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 lo romperá:

<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á intentando vincularse a un valor que no existe hasta que se realiza un pase de renderizado, o si se trata de otra cosa. Alguien tiene alguna experiencia con este tipo de enlace de animación?


He tenido situaciones similares en ControlTemplate s donde he querido vincular el atributo "Para" a un valor (en lugar de un código de codificación), y finalmente encontré 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 "A". Sin embargo, en esos ejemplos, los Guiones gráficos no están en un Estilo o Plantilla de Control . Si su Storyboard está en un estilo o plantilla de control, tendrá que usar un enfoque diferente, como esta solución.

Esta solución evita el problema de congelación porque simplemente anima un valor doble de 0 a 1. Funciona con un uso inteligente de la propiedad Etiqueta y un convertidor Multiplicar. 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.

Puedes ver esto en acción here . El quid de esto es esto:

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

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


Implementé esto exactamente.

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

uso de ejemplo:

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


Me gusta la solución de @Jason Frank. Sin embargo, es aún más fácil y menos propenso a errores si no utiliza la etiqueta, sino que, por el contrario, la propiedad Ancho de un elemento de borde ficticio vacío. Es una propiedad doble nativa, por lo que no es necesaria la sintaxis <sys:Double> y puede asignarle un nombre al borde tal como lo haría con una variable como esta:

<!-- 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 sean mucho más legibles.

La animación siempre es 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, vincula lo que quieras animar al ancho del borde ficticio. De esta forma, incluso puedes encadenar conversores entre sí de la siguiente manera:

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

Combinado con MathConverter puede hacer casi cualquier cosa en los estilos: https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML


Por lo que yo sé, no puedes vincular la animación a / desde porque la animación tiene que ser congelable.