25 data-binding wpf 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>
Run Code Online (Sandbox Code Playgroud)
而背后的代码:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我尝试将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}" />
Run Code Online (Sandbox Code Playgroud)
我不知道这是否是因为它试图绑定到一个不存在的值,直到完成渲染通道,或者它是否是其他东西.任何人都有这种动画绑定的经验吗?
Jas*_*ank 55
我有类似的情况ControlTemplate
,我想把"To"属性绑定到一个值(而不是硬编码),我终于找到了一个解决方案.
快速注释:如果您在网上挖掘,您会发现人们可以使用数据绑定"From"或"To"属性的示例.但是,在这些示例中,Storyboard 不在Style或ControlTemplate中.如果您的Storyboard位于Style或ControlTemplate中,则必须使用其他方法,例如此解决方案.
这个解决方案绕过了freezable问题,因为它简单地设置了从0到1的double值.它可以巧妙地使用Tag属性和Multiply转换器.您使用多绑定绑定到所需的属性和"缩放"(标记),它们相乘.基本上这个想法是你的标签值是你的动画,它的值就像一个"比例"(从0到1),一旦你将标签设置为1,你的所需属性值就会变为"满刻度".
你可以在这里看到这个.它的关键是:
<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>
Run Code Online (Sandbox Code Playgroud)
使用此值转换器:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
我喜欢@Jason Frank 的解决方案。然而,如果您不使用标签,而是使用空虚拟边框元素的 Width 属性,则更容易且不易出错。它是一个原生的双精度属性,因此不需要<sys:Double>
语法,您可以像使用变量一样命名边框,如下所示:
<!-- THIS IS JUST USED FOR SLIDING ANIMATION 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>
Run Code Online (Sandbox Code Playgroud)
这使得绑定更具可读性。
正如 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>
Run Code Online (Sandbox Code Playgroud)
然后将您想要设置动画的任何内容绑定到虚拟边框的宽度。这样你甚至可以将转换器相互链接起来,如下所示:
<Border x:Name="Slider" HorizontalAlignment="Left"
Margin="{Binding ElementName=Var_Slide_Length, Path=Width, Converter={StaticResource DoubleToThickness}, ConverterParameter=x 0 0 0}"/>
Run Code Online (Sandbox Code Playgroud)
与 MathConverter 结合使用,您几乎可以在样式中执行任何操作: https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML