关于“InvalidOperationException: 无法冻结此 Storyboard 时间线树供跨线程使用”的解决方法

背景提要

我在封装样式时发现的问题,然而没有找到一个完整的例子来解决问题,或者说少。

问题出现

当给一个控件添加动画效果——当鼠标放置在控件上的时候,它的宽度会产生变化

我们可以使用EventTrigger,其中Width会在0.1秒内变化到110

<EventTrigger RoutedEvent="MouseEnter">
    <BeginStoryboard>
        <Storyboard>
            <DoubleAnimation Duration="0:0:0.100"
                             Storyboard.TargetProperty="Width" To="110" />
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
    <BeginStoryboard>
        <Storyboard>
            <DoubleAnimation Duration="0:0:0.100"
                             Storyboard.TargetProperty="Width" To="100" />
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>

但问题在于,我们在封装样式后,To的值是不可以被任意设置的。

于是将To绑定到一个变量中。

<DoubleAnimation Duration="0:0:0.100" Storyboard.TargetProperty="Width" 
                                      To="{TemplateBinding Width_increase}" />

就出现了开头的问题:

在查阅其他文献后,出现这个问题的原因是,计算机需要预知变化的值来渲染动画,这个过程叫作“冻结”,绑定后,To的值是难以预测的。

问题解决

相关文献

大致意思是

1)To的值必须设置为常数

2)使用多个控件无用的属性(Tag)来表示变化

3)Width的值多绑定在各个Tag值上,经过转换器计算最终的值

下面开始举实际例子

newbutton.xaml:

<Style x:Key="NewbuttonStyle" TargetType="example:Newbutton">
    <Style.Resources>
        <converter:MultiplyConverter x:Key="MultiplyConverter" />
    </Style.Resources>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="example:Newbutton">
                <Canvas Width="{Binding Width, ElementName=ButtonBorder}"
                        HorizontalAlignment="Center"
                        Height="{Binding Height}"
                        x:Name="Tag_0to1">
                    <Canvas.Tag>
                        <sys:Double>0</sys:Double>
                    </Canvas.Tag>
                    <Border x:Name="ButtonBorder"
                            Background="White"
                            Height="{TemplateBinding Height}"
                            Tag="{TemplateBinding ButtonIncreaseWidth}"
                            BorderThickness="1"
                            BorderBrush="Black">
                        <Border.Width>
                            <MultiBinding Converter="{StaticResource MultiplyConverter}">
                                <Binding ElementName="Tag_0to1" Path="Tag"/>
                                <Binding ElementName="ButtonBorder" Path="Tag"/>
                                <Binding ElementName="ori_width" Path="Tag"/>
                            </MultiBinding>
                        </Border.Width>
                        <StackPanel x:Name="ori_width" Orientation="Horizontal" HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Margin="{TemplateBinding Padding}" Tag="{TemplateBinding Width}">
                            <ContentPresenter HorizontalAlignment="Center"
                                              VerticalAlignment="Center">
                            </ContentPresenter>
                        </StackPanel>
                    </Border>
                </Canvas>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="MouseEnter">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Duration="0:0:0.100" Storyboard.TargetName="Tag_0to1" Storyboard.TargetProperty="Tag" To="1"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="MouseLeave">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Duration="0:0:0.100" Storyboard.TargetName="Tag_0to1" Storyboard.TargetProperty="Tag" To="0" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style TargetType="example:Newbutton" BasedOn="{StaticResource NewbuttonStyle}"></Style>

MultiplyConverter.cs:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace xxx.xx.x
{
    public class MultiplyConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return (double)values[0] /*会从0直接变成1*/ * (double)values[1] /*放大的宽度*/ + (double)values[2] /*控件原本的宽度*/ ;
        }

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

其中控件的Width,ButtonIncreaseWidth的数据类型是double,Canvas.Tag需要

<sys:Double>0</sys:Double>

来表示为double类型

效果:

总结

Tag0to1.Tag会在Duration内逐渐由0变化为1

ButtonIncreaseWidth是控件即将变化的宽度

ori_Width是控件原本的宽度

控件最终宽度=Tag0to1.Tag  * ButtonIncreaseWidth + ori_Width     (Tag0to1.Tag  0->1)

ButtonIncreaseWidth也可以设置为负数,这样的话,当触发时,控件宽度减小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值