【问题标题】:Can I use data-binding on the Duration of an Animation in a ControlTemplate?我可以在 ControlTemplate 中对动画的持续时间使用数据绑定吗?
【发布时间】:2017-06-06 19:33:29
【问题描述】:

我在这里找到了一个good explanation,关于如何将ColorAnimationDuration 属性绑定到SliderValue 属性。使用转换器将滑块中的Double 值转换为Duration,并使用Binding 设置ColorAnimationDuration。简而言之,这是它的工作原理:

<Window.Resources>
    <local:DoubleToDurationConverter x:Key="DoubleToDurationConverter" />
</Window.Resources>

<Slider x:Name="slider" />


<Button Content="Click me for an animation">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation To="Green"
                        Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)" 
                        FillBehavior="Stop" 
                        Duration="{Binding ElementName=slider, 
                                           Path=Value,
                                           Mode=OneWay,
                                           Converter={StaticResource DoubleToDurationConverter}}" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

我试过了,对我来说效果很好。但我想做的是将Duration 绑定到一个名为FadeTime 的依赖属性,我已添加到我的自定义控件中。所以,在那个控件的ControlTemplate 我有这个:

<ControlTemplate.Triggers>
    <Trigger Property="IsLit" Value="true">
        <Trigger.EnterActions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="glow"
                                     Storyboard.TargetProperty="Opacity"
                                     To="1"
                                     Duration="{Binding FadeTime, Mode=OneWay,
                                                RelativeSource={RelativeSource TemplatedParent}}"/>
                </Storyboard>
            </BeginStoryboard>
        </Trigger.EnterActions>
    </Trigger>
</ControlTemplate.Triggers>

这可以编译,但在运行时给我一条错误消息:

InvalidOperationException:无法冻结此 Storyboard 时间线树 跨线程使用。

如何将我的DoubleAnimationDuration 绑定到自定义控件的ControlTemplate 中的依赖变量?

谢谢!

更新

对于我想做的事情来说,数据绑定实际上是大材小用。真正的数据绑定将允许属性的值在运行时更改。我真正想要的是让使用我的自定义控件的开发人员能够在设计时设置DoubleAnimationDuration,而无需编辑ControlTemplate。如果开发者选择的值在运行时从不改变也没关系。

【问题讨论】:

    标签: wpf xaml data-binding


    【解决方案1】:

    您可以在 PropertyChangedCallback 中以编程方式为 IsLit 属性定义动画,而不是在 XAML 标记中定义动画。

    您可以简单地定义另一个属性,让控件的使用者指定动画的持续时间。

    这里有一个例子。

    控制:

    public class MyCustomControl : Control
    {
        private UIElement glow;
    
        public static readonly DependencyProperty DurationProperty =
             DependencyProperty.Register("Duration", typeof(TimeSpan),
             typeof(MyCustomControl), new PropertyMetadata(TimeSpan.FromSeconds(1)));
    
        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }
    
        public static readonly DependencyProperty IsLitProperty =
             DependencyProperty.Register("IsLit", typeof(bool),
             typeof(MyCustomControl), new PropertyMetadata(false, new PropertyChangedCallback(OnIsLitChanged)));
    
        public bool IsLit
        {
            get { return (bool)GetValue(IsLitProperty); }
            set { SetValue(IsLitProperty, value); }
        }
    
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            glow = Template.FindName("glow", this) as UIElement;
            if (glow != null && IsLit)
                Animate(glow);
    
        }
    
        private static void OnIsLitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            bool newValue = (bool)e.NewValue;
            if(newValue)
            {
                MyCustomControl c = d as MyCustomControl;
                if(c != null && c.glow != null)
                {
                    c.Animate(c.glow);
                }
            }
        }
    
        private void Animate(UIElement glow)
        {
            DoubleAnimation animation = new DoubleAnimation();
            animation.To = 1;
            animation.Duration = Duration;
            glow.BeginAnimation(OpacityProperty, animation);
        }
    }
    

    模板:

    <ControlTemplate x:Key="ct" TargetType="local:MyCustomControl">
        <Border x:Name="glow" Width="100" Height="100" Background="Red" Opacity="0.1">
        </Border>
    </ControlTemplate>
    

    用法:

    <local:MyCustomControl Template="{StaticResource ct}" Duration="0:0:5" IsLit="True" />
    

    【讨论】:

    • 完美运行,@mm8!遗憾的是(显然)没有纯粹的 XAML 方法可以做到这一点,但也许“XAML,只有 XAML”的口头禅不应该是绝对的。感谢您的大力协助!
    【解决方案2】:

    基本上,您不能在控件模板的情节提要中使用普通绑定。由于您只希望开发人员能够更改值,因此以下选项之一可能适合您:

    (1) 使用StaticResource:将持续时间对象放置在控件模板之外的某个位置,这样开发人员更容易更改。但是,它必须是控件模板可以静态访问的地方,因为DynamicResource 在这个地方不起作用。

    <Duration x:Key="MyCustomDuration">0:0:1</Duration>
    ... then later
    Duration="{StaticResource MyCustomDuration}"
    

    (2) 使用x:Static 的静态代码隐藏字段:

    public static class SettingsClass
    {
        public static Duration MyCustomDuration = new Duration(new TimeSpan(0, 0, 1));
    }
    

    并使用:

    Duration="{x:Static local:SettingsClass.MyCustomDuration}"
    

    【讨论】:

    • 谢谢!这两种方法都有效,但似乎要求开发人员对我的自定义控件的源代码进行更改,或者编辑我的控件ControlTemplate 的副本。这些方法本身并没有错,但我正在寻找更精简的方法。
    猜你喜欢
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 2013-07-19
    • 2018-11-12
    • 1970-01-01
    • 2022-01-19
    • 2022-11-22
    相关资源
    最近更新 更多