WPF动画:绑定到故事板动画的“To”属性

时间:2010-02-02 19:20:13

标签: wpf data-binding animation user-controls

我正在尝试创建一个与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}" />

我不知道这是否是因为它试图绑定到一个不存在的值,直到渲染通道完成,或者它是否是其他东西。任何人都有这种动画绑定的经验吗?

5 个答案:

答案 0 :(得分:52)

我在ControlTemplate中遇到类似情况,我想将“To”属性绑定到一个值(而不是硬编码),最后我找到了一个解决方案< / strong>即可。

快速注意事项:如果您在网上挖掘,您会发现examples人可以使用数据绑定“From”或“To”属性。但是,在这些示例中,故事板是not in a Style or ControlTemplate。如果您的Storyboard位于Style或ControlTemplate中,则必须使用其他方法,例如此解决方案。

这个解决方案解决了freezable问题,因为它只是简单地设置了从0到1的double值。它可以巧妙地使用Tag属性和Multiply转换器。您使用多绑定绑定到所需的属性和“缩放”(标记),它们相乘。基本上这个想法是你的标签值是你的动画,它的值就像一个“比例”(从0到1),一旦你将标签设置为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");
   }
}

答案 1 :(得分:8)

据我所知,你无法将动画绑定到/从中,因为动画必须是freezable。

答案 2 :(得分:1)

答案 3 :(得分:1)

我实现了这一点。

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

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

示例用法:

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

答案 4 :(得分:1)

我喜欢@Jason Frank的解决方案。但是,如果您不使用标签,它会更容易且不易出错,而是例如空虚拟边框元素的宽度属性。它是一个原生的双重属性,所以不需要<sys:Double>语法,你可以像使用这样的变量一样命名边框:

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

这使得绑定更具可读性。

动画总是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