WPF 动画:绑定到“To"故事板动画的属性 [英] WPF animation: binding to the "To" attribute of storyboard animation

查看:27
本文介绍了WPF 动画:绑定到“To"故事板动画的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是我目前使用的 XAML:

<CheckBox.Resources><System.Windows:Duration x:Key="AnimationTime">0:0:0.2</System.Windows:Duration><故事板 x:Key="OnChecking"><DoubleAnimation Storyboard.TargetName="CheckButton"Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"Duration="{StaticResource AnimationTime}"到=40"/><DoubleAnimation Storyboard.TargetName="CheckButton"Storyboard.TargetProperty="(Button.Width)"Duration="{StaticResource AnimationTime}"到=41"/></故事板><故事板 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}"到=40"/></故事板><Style x:Key="SlideCheckBoxStyle"TargetType="{x:Type local:SlideCheckBox}"><Setter 属性="模板"><Setter.Value><ControlTemplate TargetType="{x:Type local:SlideCheckBox}"><画布><ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"内容="{模板绑定内容}"ContentTemplate="{TemplateBinding ContentTemplate}"识别AccessKey="True"垂直对齐=中心"水平对齐=中心"/><画布><!--背景--><矩形宽度="{Binding ElementName=ButtonText, Path=ActualWidth}"Height="{Binding ElementName=ButtonText, Path=ActualHeight}"填充=浅蓝色"/></画布><画布><!--按钮--><Button Width="{Binding ElementName=CheckedText, Path=ActualWidth}"Height="{Binding ElementName=ButtonText, Path=ActualHeight}"名称="检查按钮"Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}"><Button.RenderTransform><变换组><TranslateTransform/></TransformGroup></Button.RenderTransform></按钮></画布><画布><!--文本--><StackPanel Name="ButtonText"方向="水平"IsHitTestVisible="假"><网格名称="CheckedText"><标签边距="7 0"Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=CheckedText}"/></网格><网格名称="UncheckedText"Horizo​​ntalAlignment="右"><标签边距="7 0"Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=UncheckedText}"/></网格></StackPanel></画布></画布><ControlTemplate.Triggers><触发器属性="IsChecked"值="真"><Trigger.EnterActions><BeginStoryboard Storyboard="{StaticResource OnChecking}"/></Trigger.EnterActions><Trigger.ExitActions><BeginStoryboard Storyboard="{StaticResource OnUnchecking}"/></Trigger.ExitActions></触发器></ControlTemplate.Triggers></控制模板></Setter.Value></Setter></风格></CheckBox.Resources><CheckBox.CommandBindings><CommandBinding Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}"已执行=OnSlideCheckBoxClicked"/></CheckBox.CommandBindings></复选框>

以及背后的代码:

使用 System.Windows;使用 System.Windows.Controls;使用 System.Windows.Input;命名空间 Smt.Controls{公共部分类 SlideCheckBox : CheckBox{公共 SlideCheckBox(){初始化组件();已加载 += 已加载;}public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text"));公共字符串 CheckedText{获取{返回(字符串)GetValue(CheckedTextProperty);}set { SetValue(CheckedTextProperty, value);}}public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text"));公共字符串 UncheckedText{获取{返回(字符串)GetValue(UncheckedTextProperty);}set { SetValue(UncheckedTextProperty, value);}}public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand();void OnLoaded(对象发送者,RoutedEventArgs e){Style style = TryFindResource("SlideCheckBoxStyle") as Style;if (!ReferenceEquals(style, null)){风格=风格;}}void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e){IsChecked = !IsChecked;}}}

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

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

 

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

解决方案

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

快速旁注:如果您在网上搜索,您会发现 人们能够将数据绑定用于From"或To"属性的示例.但是,在这些示例中,故事板 不是在 Style 或 ControlTemplate 中.如果您的 Storyboard 是 Style 或 ControlTemplate,则您必须使用不同的方法,例如此解决方案.

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

您可以在此处看到这一点.关键在于:

<ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}"><!--(这里还有其他东西...)--><ScrollViewer x:Name="ExpanderContentScrollView"><!-- ** 开始重要的部分 #1 ... --><ScrollViewer.Tag><sys:Double>0.0</sys:Double></ScrollViewer.Tag><ScrollViewer.Height><MultiBinding Converter="{StaticResource multiplyConverter}"><绑定路径="ActualHeight" ElementName="ExpanderContent"/><绑定路径="标签"RelativeSource="{RelativeSource Self}"/></多重绑定></ScrollViewer.Height><!-- ...结束重要部分#1.--><ContentPresenter x:Name="ExpanderContent" ContentSource="内容"/></ScrollViewer><ControlTemplate.Triggers><Trigger Property="IsExpanded" Value="True"><Trigger.EnterActions><开始故事板><故事板><!-- ** 开始重要的第 2 部分(制作 TargetProperty 'Tag')... --><DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"Storyboard.TargetProperty="标签"到=1"持续时间="0:0:0.4"/><!-- ...结束重要部分#2 --></故事板></BeginStoryboard></Trigger.EnterActions></触发器></ControlTemplate.Triggers></控制模板>

使用此值转换器:

公共类 MultiplyConverter : IMultiValueConverter{公共对象转换(对象 [] 值,类型目标类型,对象参数,文化信息文化){双倍结果 = 1.0;for (int i = 0; i < values.Length; i++){如果(值 [i] 是双倍)结果 *= (double)values[i];}返回结果;}public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture){throw new Exception("未实现");}}

I'm trying to create a button that behaves similarly to the "slide" button on the iPhone. I have an animation that adjusts the position and width of the button, but I want these values to be based on the text used in the control. Currently, they're hardcoded.

Here's my working XAML, so far:

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

And the code behind:

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

The problem comes when I try to bind the "To" attribute in the DoubleAnimations to the actual width of the text, the same as I'm doing in the ControlTemplate. If I bind the values to an ActualWidth of an element in the ControlTemplate, the control comes up as a blank checkbox (my base class). However, I'm binding to the same ActualWidths in the ControlTemplate itself without any problems. Just seems to be the CheckBox.Resources that have a problem with it.

For instance, the following will break it:

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

I don't know whether this is because it's trying to bind to a value that doesn't exist until a render pass is done, or if it's something else. Anyone have any experience with this sort of animation binding?

解决方案

I've had similar situations in ControlTemplates where I've wanted to bind the "To" attribute to a value (rather than hard-coding it), and I finally found a solution.

Quick side note: If you dig around on the web you'll find examples of people being able to use data binding for the "From" or "To" properties. However, in those examples the Storyboards are not in a Style or ControlTemplate. If your Storyboard is in a Style or ControlTemplate, you'll have to use a different approach, such as this solution.

This solution gets around the freezable issue because it simply animates a double value from 0 to 1. It works with a clever use of the Tag property and a Multiply converter. You use a multibinding to bind to both a desired property and your "scale" (the Tag), which get multiplied together. Basically the idea is that your Tag value is what you animate, and its value acts like a "scale" (from 0 to 1) bringing your desired attribute value to "full scale" once you've animated the Tag to 1.

You can see this in action here. The crux of it is this:

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

With this value converter:

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

这篇关于WPF 动画:绑定到“To"故事板动画的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆