绑定到可见性时动画不正确 属性(奇怪)
Animation not correct when bound to a visibility property(Oddity)
我遇到的问题是,每当我通过 MVVM 模型中的命令更改可见性 属性 以触发加载动画(i.g.isBusy = true)时,动画无法正常播放。结果在 运行 时间内是随机的,有时动画非常接近完美,有时它只进行到一半然后循环。
在任何一种情况下,执行此行为总是需要故事板的长度(i.g。它会旋转随机度数,忽略故事板,但总是需要 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" 只是我传递所需的参数来测试代码。请忽略与传递的对象的任何不一致。
我为此绞尽脑汁想了一段时间,就是想不通。
这是因为在 UI 线程中使用了一些长时间代码 运行。
让我们考虑一个例子。
- 您点击了某个按钮。
- 那个按钮执行一些命令。
- 该命令默认在 UI 线程中运行。
- 该命令设置 isBusy = true 而不是做一些 "long" 工作。
在 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());
通过在本地构建动画而不是引用包含动画的不同视图自行解决了这个问题。
之前:
<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>
可能不是很干净,因为我在项目的其他地方构建了这个确切的动画,但至少它现在可以正确地设置动画。
我遇到的问题是,每当我通过 MVVM 模型中的命令更改可见性 属性 以触发加载动画(i.g.isBusy = true)时,动画无法正常播放。结果在 运行 时间内是随机的,有时动画非常接近完美,有时它只进行到一半然后循环。
在任何一种情况下,执行此行为总是需要故事板的长度(i.g。它会旋转随机度数,忽略故事板,但总是需要 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" 只是我传递所需的参数来测试代码。请忽略与传递的对象的任何不一致。
我为此绞尽脑汁想了一段时间,就是想不通。
这是因为在 UI 线程中使用了一些长时间代码 运行。 让我们考虑一个例子。
- 您点击了某个按钮。
- 那个按钮执行一些命令。
- 该命令默认在 UI 线程中运行。
- 该命令设置 isBusy = true 而不是做一些 "long" 工作。
在 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());
通过在本地构建动画而不是引用包含动画的不同视图自行解决了这个问题。
之前:
<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>
可能不是很干净,因为我在项目的其他地方构建了这个确切的动画,但至少它现在可以正确地设置动画。