web-dev-qa-db-ja.com

WPFアニメーション:ストーリーボードアニメーションの "To"属性へのバインド

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

ControlTemplateで行うのと同じように、DoubleAnimationsの "To"属性を実際のテキストの幅にバインドしようとすると、問題が発生します。値をControlTemplateの要素のActualWidthにバインドすると、コントロールは空白のチェックボックス(私の基本クラス)として表示されます。ただし、問題なく、ControlTemplate自体の同じActualWidthsにバインドしています。問題のあるCheckBox.Resourcesのようです。

たとえば、次はそれを壊します:

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

これがレンダーパスが完了するまで存在しない値にバインドしようとしているためなのか、それとも別の原因なのかはわかりません。誰もがこの種のアニメーションバインディングについて何か経験がありますか?

26
user264621

私はControlTemplatesで同様の状況にあり、 "To"属性を値に(ハードコーディングするのではなく)バインドしたかったのですが、最終的にが見つかりましたソリューション

簡単な補足:Webを調べてみると、「From」または「To」プロパティにデータバインディングを使用できる examples が見つかります。ただし、これらの例では、ストーリーボードは StyleまたはControlTemplateにない です。ストーリーボードがStyleまたはControlTemplate内にある場合は、このソリューションなどの別のアプローチを使用する必要があります。

このソリューションは、0から1までのdouble値をアニメーション化するだけなので、フリーズ可能問題を回避します。Tagプロパティと乗算コンバーターの巧妙な使用法で機能します。マルチバインディングを使用して、目的のプロパティと「スケール」(タグ)の両方にバインドします。これらは一緒に乗算されます。基本的には、タグの値はアニメーション化するものであり、その値は「スケール」(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");
   }
}
55
Jason Frank

私が知る限り、アニメーションはフリーズ可能でなければならないため、アニメーションをバインドすることはできません。

8
David Cater

私はこれを正確に実装しました。

<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"  />
1
astronought

@Jason Frankのソリューションが好きです。ただし、タグを使用しない方が簡単でエラーが発生しにくくなります。空のダミーのBorder要素のWidthプロパティ。これはネイティブのdoubleプロパティであるため、<sys:Double>構文は必要なく、次のような変数で行うようにBorderに名前を付けることができます。

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

これにより、バインディングがはるかに読みやすくなります。

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

1
JCH2k