绑定到可见性属性时动画不正确(奇数)

Posted

技术标签:

【中文标题】绑定到可见性属性时动画不正确(奇数)【英文标题】:Animation not correct when bound to a visibility property(Oddity) 【发布时间】:2015-10-13 00:44:36 【问题描述】:

我遇到的问题是,每当我通过 MVVM 模型中的命令更改可见性属性以触发加载动画(例如 isBusy = true)时,动画都无法正确播放。结果在运行时是随机的,有时动画非常接近完美,有时它只播放到一半然后循环。

在任何一种情况下,执行此行为总是需要故事板的长度(例如,它会旋转随机的度数,忽略故事板,但总是需要 0.5 秒才能完成。)

奇怪的是,如果我从构造函数触发 isBusy,动画将完美运行,但如果我通过 commandExecute 调用它,它会中断。下面是代码示例和我的 XAML。

        <Grid x:Name="LoadingGrid"  Visibility="Binding isBusy, Converter=StaticResource BooleanToVisibilityConverter, Mode=TwoWay" Grid.RowSpan="2">
        <LoadingViews:LoadingView x:Name="LoadingControl" />
    </Grid>

C#:

        public StoreSearchViewModel(MainViewModel mainViewModel)
    
        this.mainViewModel = mainViewModel;
        mainViewModel.LogUsage("Store Search");

        searchResultsCommand = new DelegateCommand(SearchResultsCommandExecute);
        storeSearchCommand = new DelegateCommand<object>(SetBusy, CanStoreSearchCommandExecute);
        CloseWindowCommand = new DelegateCommand(CloseWindowExecute);
        Setup();

    

        private void SetBusy(object obj)
    
        isBusy = true;
    

    private bool _isBusy;
    public bool isBusy
    
        get  return _isBusy; 
        set  _isBusy= value; OnPropertyChanged("isBusy"); 
    

上面的代码会导致加载动画出现故障,其中动画位于网格中,其可见性由 isBusy 确定,并由视图中的命令触发。被触发的命令是 storeSearchCommand。

但是下面的代码会产生很好的动画。

        private void Setup()
    
        //create view models
        _storeSearchResultsViewModel = new StoreSearchResultsViewModel(this);

        //set default selection to the dashboard
        isStoreSearchResultsSelected = true;
        SearchResultsCommandExecute();
        SetBusy();
    

请注意,“object obj”只是我传递所需的参数来测试代码。请忽略与传递的对象的任何不一致。

我已经为此绞尽脑汁了一段时间,根本无法弄清楚。

【问题讨论】:

请发布 CanStoreSearchCommandExecute 的代码。 CanStoreSearchCommandExecute 仅检查以确保传递的对象符合某些要求。它不应该以任何方式影响加载动画。 (它会彻底阻止代码运行,如果验证失败,则根本不会播放动画) 好的,你能发布一个测试项目的 zip 文件,以便我可以尝试在我的机器上获得相同的结果吗?我在这里尝试过,但没有运气。 【参考方案1】:

这是因为在 UI 线程中使用了一些长时间运行的代码。 让我们考虑一个例子。

    您点击了某个按钮。 那个按钮执行一些命令。 该命令默认在 UI 线程中运行。 该命令设置 isBusy = true 而不是做一些“长时间”的工作。

在 UI 中,只有当我们的命令丰富了其长代码运行的结尾时,您才会看到开始一些动画(即绑定到 isBusy 属性)。 这只是一个例子。

所以,你应该使用异步调用。这意味着,设置 isBusy = true,在单独的线程中执行一些代码。您几乎可以立即看到 isBusy 属性的变化。

这是一个例子:

        isBusy = true;
        Task.Factory.StartNew(() =>
            
                //do some code. It is a new thread
            ).ContinueWith((x) =>
            
                //do some code after previous task is done. This code runs in the UI thread
                isBusy = false;
            , TaskScheduler.FromCurrentSynchronizationContext());

【讨论】:

我还应该指出,为了测试代码,我唯一要做的就是将 isBusy 的属性设置为 true。我的一些代码流是“触发命令-> 将 isBusy 设置为 true”,之后没有代码。实际上,它应该触发并永久停止动画,因为我从不将其设置回 false 或在后台运行任何代码。【参考方案2】:

通过在本地构建动画而不是引用包含动画的不同视图,我自己解决了这个问题。

之前:

<Grid x:Name="LoadingGrid"  Visibility="Binding isBusy, Converter=StaticResource BooleanToVisibilityConverter, Mode=TwoWay" Grid.RowSpan="2">
    <LoadingViews:LoadingView x:Name="LoadingControl" />
</Grid>

现在:

    <Window.Resources>
    <Storyboard x:Key="Storyboard1" RepeatBehavior="Forever">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
            <SplineDoubleKeyFrame KeyTime="00:00:01" Value="360"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="StaticResource Storyboard1"/>
    </EventTrigger>
</Window.Triggers>

        <ContentControl x:Name="CurrentViewContentControl" Content="Binding currentView, Mode=TwoWay" Background="#FFDC00FF" Grid.Row="1"/>
    <Grid x:Name="LoadingGrid"  Visibility="Binding isBusy, Converter=StaticResource BooleanToVisibilityConverter" Grid.RowSpan="2" d:IsHidden="True">
        <Grid.Background>
            <RadialGradientBrush>
                <GradientStop Color="Black" Offset="0"/>
                <GradientStop Color="#BF000000" Offset="1"/>
            </RadialGradientBrush>
        </Grid.Background>
        <Grid Margin="446,285">

            <Ellipse x:Name="ellipse" StrokeThickness="6" RenderTransformOrigin="0.5,0.5">
                <Ellipse.Effect>
                    <DropShadowEffect ShadowDepth="3"/>
                </Ellipse.Effect>
                <Ellipse.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform/>
                    </TransformGroup>
                </Ellipse.RenderTransform>
                <Ellipse.Stroke>
                    <LinearGradientBrush EndPoint="0.445,0.997" StartPoint="0.555,0.003">
                        <GradientStop Color="#FF0244D1"/>
                        <GradientStop Color="#00000000" Offset="0.313"/>
                        <GradientStop Color="#FF0244D1" Offset="0.063"/>
                    </LinearGradientBrush>
                </Ellipse.Stroke>
            </Ellipse>
        </Grid>
    </Grid>

可能不是很干净,因为我在项目的其他地方构建了这个确切的动画,但至少它现在可以正确动画了。

【讨论】:

以上是关于绑定到可见性属性时动画不正确(奇数)的主要内容,如果未能解决你的问题,请参考以下文章

如何将 StackPane 的可见性绑定到属性?

将 XAML 中的可见性绑定到可见性属性

CSS可见性动画不起作用

WPF 绑定不更新可见性

WPF - 将 UserControl 可见性绑定到属性

在 UWP 中未检测到绑定可见性属性的变量