UWP Connected 动画在第二次使用后崩溃
UWP Connected Animation Crashes After Second Use
我用一个点击事件连接了两个表单,该事件触发了两个方向的连接动画。第一次前进和后退它工作正常。第二次前进它有效,但第二次尝试返回会导致应用程序崩溃并出现以下异常:
System.ArgumentException: The parameter is incorrect. Cannot start animation - the source element is not in the element tree.
这发生在 SecondPage_BackRequested 的第一行,但仅在第二次执行时发生。第一次执行效果很好,动画也很完美。
如有任何帮助,我们将不胜感激。我已经仔细阅读了连接的动画文档,据我所知这是它应该如何使用,但我找不到任何地方发生的这个错误的参考。
我的代码(MainPageViewModel 省略,因为它不相关,但可以根据要求添加):
MainPage.xaml
<Page
x:Class="AnimTest.Views.Main.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:AnimTest.Models"
xmlns:main="using:AnimTest.Views.Main"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Style="{ThemeResource HeaderTextBlockStyle}"
Text="AnimTest"/>
<GridView x:Name="TileGrid"
Grid.Row="1"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ViewModel.Tiles, Mode=OneWay}"
ItemClick="GridView_ItemClick"
Loaded="TileGrid_Loaded">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:Tile">
<Border x:Name="TileBorder"
Background="Red"
MinHeight="150"
MinWidth="200">
<StackPanel Orientation="Vertical"
VerticalAlignment="Center">
<SymbolIcon Symbol="World"/>
<TextBlock Text="{x:Bind Name}"
HorizontalTextAlignment="Center"/>
</StackPanel>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel => (MainPageViewModel)DataContext;
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
}
private void GridView_ItemClick(object sender, ItemClickEventArgs e)
{
TileGrid.PrepareConnectedAnimation("borderIn", e.ClickedItem, "TileBorder");
Frame.Navigate(typeof(SecondPage));
}
private async void TileGrid_Loaded(object sender, RoutedEventArgs e)
{
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderOut");
if (animation != null)
{
var success = await TileGrid.TryStartConnectedAnimationAsync(animation, ViewModel.Tiles[0], "TileBorder");
}
}
}
SecondPage.xaml
<Page
x:Class="HomeTiles.Views.Thermostat.ThermostatPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HomeTiles.Views.Thermostat"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Border x:Name="MainBorder"
Background="Red">
</Border>
</Page>
SecondPage.xaml.cs
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SecondPage_BackRequested;
}
private void SecondPage_BackRequested(object sender, BackRequestedEventArgs e)
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
Frame?.GoBack();
e.Handled = true;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderIn");
animation?.TryStart(MainBorder);
}
}
问题其实不在于连接的动画,而在于导航事件。
第一次到达 SecondPage
时,您连接了 BackRequested
事件,当您返回时,一切都很好。但是,即使在您从 SecondPage
导航后,事件处理程序 仍然 附加到事件。这是一个问题,因为一旦您再次导航到 SecondPage
,现在 even 将被注册两次。而且处理程序第一次运行时失败,因为第一个处理程序连接到页面的前一个实例,并且连接的动画已经完成了这个。最后——由于该事件,页面将永远留在内存中,这可能会导致严重的内存泄漏。
解决方案非常简单 - 您必须确保在离开页面时不要忘记取消订阅 even 处理程序,例如在 OnNavigatedFrom
方法中并在 [=17= 中订阅] 更清晰的方法:
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().BackRequested -= SecondPage_BackRequested;
}
private void SecondPage_BackRequested(object sender, BackRequestedEventArgs e)
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
Frame?.GoBack();
e.Handled = true;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SecondPage_BackRequested;
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderIn");
animation?.TryStart(MainBorder);
}
}
为了避免此类问题,我通常在 App
中为整个应用程序设置 BackRequested
事件,并在启动时只订阅一次。然后,您可以将连接的动画代码放在 OnNavigatedFrom
方法中,而不必订阅 BackRequested
:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
}
我用一个点击事件连接了两个表单,该事件触发了两个方向的连接动画。第一次前进和后退它工作正常。第二次前进它有效,但第二次尝试返回会导致应用程序崩溃并出现以下异常:
System.ArgumentException: The parameter is incorrect. Cannot start animation - the source element is not in the element tree.
这发生在 SecondPage_BackRequested 的第一行,但仅在第二次执行时发生。第一次执行效果很好,动画也很完美。
如有任何帮助,我们将不胜感激。我已经仔细阅读了连接的动画文档,据我所知这是它应该如何使用,但我找不到任何地方发生的这个错误的参考。
我的代码(MainPageViewModel 省略,因为它不相关,但可以根据要求添加):
MainPage.xaml
<Page
x:Class="AnimTest.Views.Main.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:AnimTest.Models"
xmlns:main="using:AnimTest.Views.Main"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Style="{ThemeResource HeaderTextBlockStyle}"
Text="AnimTest"/>
<GridView x:Name="TileGrid"
Grid.Row="1"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ViewModel.Tiles, Mode=OneWay}"
ItemClick="GridView_ItemClick"
Loaded="TileGrid_Loaded">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:Tile">
<Border x:Name="TileBorder"
Background="Red"
MinHeight="150"
MinWidth="200">
<StackPanel Orientation="Vertical"
VerticalAlignment="Center">
<SymbolIcon Symbol="World"/>
<TextBlock Text="{x:Bind Name}"
HorizontalTextAlignment="Center"/>
</StackPanel>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel => (MainPageViewModel)DataContext;
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
}
private void GridView_ItemClick(object sender, ItemClickEventArgs e)
{
TileGrid.PrepareConnectedAnimation("borderIn", e.ClickedItem, "TileBorder");
Frame.Navigate(typeof(SecondPage));
}
private async void TileGrid_Loaded(object sender, RoutedEventArgs e)
{
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderOut");
if (animation != null)
{
var success = await TileGrid.TryStartConnectedAnimationAsync(animation, ViewModel.Tiles[0], "TileBorder");
}
}
}
SecondPage.xaml
<Page
x:Class="HomeTiles.Views.Thermostat.ThermostatPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HomeTiles.Views.Thermostat"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Border x:Name="MainBorder"
Background="Red">
</Border>
</Page>
SecondPage.xaml.cs
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SecondPage_BackRequested;
}
private void SecondPage_BackRequested(object sender, BackRequestedEventArgs e)
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
Frame?.GoBack();
e.Handled = true;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderIn");
animation?.TryStart(MainBorder);
}
}
问题其实不在于连接的动画,而在于导航事件。
第一次到达 SecondPage
时,您连接了 BackRequested
事件,当您返回时,一切都很好。但是,即使在您从 SecondPage
导航后,事件处理程序 仍然 附加到事件。这是一个问题,因为一旦您再次导航到 SecondPage
,现在 even 将被注册两次。而且处理程序第一次运行时失败,因为第一个处理程序连接到页面的前一个实例,并且连接的动画已经完成了这个。最后——由于该事件,页面将永远留在内存中,这可能会导致严重的内存泄漏。
解决方案非常简单 - 您必须确保在离开页面时不要忘记取消订阅 even 处理程序,例如在 OnNavigatedFrom
方法中并在 [=17= 中订阅] 更清晰的方法:
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
SystemNavigationManager.GetForCurrentView().BackRequested -= SecondPage_BackRequested;
}
private void SecondPage_BackRequested(object sender, BackRequestedEventArgs e)
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
Frame?.GoBack();
e.Handled = true;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += SecondPage_BackRequested;
var animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("borderIn");
animation?.TryStart(MainBorder);
}
}
为了避免此类问题,我通常在 App
中为整个应用程序设置 BackRequested
事件,并在启动时只订阅一次。然后,您可以将连接的动画代码放在 OnNavigatedFrom
方法中,而不必订阅 BackRequested
:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("borderOut", MainBorder);
}