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

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

问题描述

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



这是我工作的XAML,到目前为止:

 <复选框x:Class =Smt.Controls.SlideCheckBox
xmlns =http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x =http:// schemas $ m
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)
持续时间= {StaticResource AnimationTime}
To =40/>
< DoubleAnimation Storyboard.TargetName =CheckButton
Storyboard.TargetProperty =(Button.Width)
持续时间={StaticResource AnimationTime}
To =41 />
< / Storyboard>
< Storyboard x:Key =OnUnchecking>
< DoubleAnimation Storyboard.TargetName =CheckButton
Storyboard.TargetProperty =(UIElement.RenderTransform)。(TransformGroup.Children)[0](TranslateTransform.X)
持续时间= {StaticResource AnimationTime}
To =0/>
< DoubleAnimation Storyboard.TargetName =CheckButton
Storyboard.TargetProperty =(Button.Width)
持续时间={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
Horizo​​ntalAlignment =Center/>
< Canvas>
<! - 背景 - >
< Rectangle Width ={Binding ElementName = ButtonText,Path = ActualWidth}
Height ={Binding ElementName = ButtonText,Path = ActualHeight}
Fill =LightBlue/> ;
< / Canvas>
< Canvas>
<! - 按钮 - >
< 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>
<! - 文本 - >
< StackPanel Name =ButtonText
Orientation =Horizo​​ntal
IsHitTestVisible =False>
< Grid Name =CheckedText>
< Label Margin =7 0
Content ={Binding RelativeSource = {RelativeSource AncestorType = {x:Type local:SlideCheckBox}},Path = CheckedText}/>
< / Grid>
< Grid Name =UncheckedText
Horizo​​ntalAlignment =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>

后面的代码:

 使用System.Windows; 
使用System.Windows.Controls;
使用System.Windows.Input;

命名空间Smt.Controls
{
public partial class SlideCheckBox:CheckBox
{
public SlideCheckBox()
{
InitializeComponent ();
加载+ = OnLoaded;


public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register(CheckedText,typeof(string),typeof(SlideCheckBox),新的PropertyMetadata(Checked Text));
public string CheckedText
{
get {return(string)GetValue(CheckedTextProperty); }
set {SetValue(CheckedTextProperty,value); }
$ b 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本身中绑定到相同的ActualWidth,没有任何问题。只是似乎是与它有问题的CheckBox.Resources。



例如,以下将会破坏它:

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

我不知道这是否是因为它试图绑定到不存在的值直到渲染过程完成,或者如果它是别的。任何人都有这种动画绑定的经验?

解决方案

我在 ControlTemplate中有类似的情况 s我希望将To属性绑定到一个值(而不是硬编码),最后找到一个解决方案



快速备注:如果您在网路上挖掘,您会发现。但是,在这些示例中,故事板是 not在样式或控制模板。如果您的Storyboard属于Style或ControlTemplate,则必须使用其他方法,例如此解决方案。



此解决方案围绕着可冻结的问题,因为它简单将从0到1的双重值设置为动画。它可以巧妙地使用Tag属性和一个Multiply转换器。您可以使用多重绑定来绑定所需的属性和scale(标签),它们会相乘。基本上这个想法是,您的标签值是您的动画,并且其值的作用就像一个比例(从0到1),一旦您将标签动画为1,将所需的属性值带入满量程。 p>

你可以看到这个动作 这里 。其关键在于:

 < local:MultiplyConverter x:Key =multiplyConverter/> 
< ControlTemplate x:Key =RevealExpanderTempTargetType ={x:Type Expander}>
<! - (其他东西在这里...) - >
< ScrollViewer x:Name =ExpanderContentScrollView>
<! - **开始重要部分#1 ... - >
< ScrollViewer.Tag>
< sys:Double> 0.0< / sys:Double>
< /ScrollViewer.Tag>
< ScrollViewer.Height>
< MultiBinding Converter ={StaticResource multiplyConverter}>
< Binding Path =ActualHeightElementName =ExpanderContent/>
< Binding Path =TagRelativeSource ={RelativeSource Self}/>
< / MultiBinding>
< /ScrollViewer.Height>
<! - ...结束重要的部分#1。 - >
< ContentPresenter x:Name =ExpanderContentContentSource =Content/>

< / ScrollViewer>

< ControlTemplate.Triggers>
< Trigger Property =IsExpandedValue =True>
< Trigger.EnterActions>
< BeginStoryboard>
< Storyboard>
<! - **开始重要部分#2(使TargetProperty'Tag')... - >
< DoubleAnimation Storyboard.TargetName =ExpanderContentScrollView
Storyboard.TargetProperty =Tag
To =1
持续时间=0:0:0.4/>
<! - ... end important part#2 - >
< / Storyboard>
< /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]是double)
result * =(double)values [一世];
}

返回结果;
}

public object [] ConvertBack(object value,Type [] targetTypes,object parameter,CultureInfo culture)
{
throw new Exception(Not Implement );
}
}


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>
        </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天全站免登陆