未导航的页面仍然在UWP应用程序中发出事件

我在UWP Windows 10应用程序中观察到一个奇怪的行为。 即使我从页面导航并卸载页面,页面仍然继续发出事件。 例如,即使我转到一个完全不同的页面,我浏览的旧页面仍然会发出“LayoutUpdated”事件。

我准备了一个最小的例子来演示这个(代码如下)。 这很简单:

网格布局更新页面0

网格布局更新第3页

网格布局更新第1页

网格布局更新第2页

注意旧的页面仍然更新他们的布局。 旧的页面不应该再发出任何事件,但是它们会继续发送事件,尽pipe没有办法导航到它们,并且它们被卸载了。

这里是代码,有5个文件,只需在VS2015中创build一个新的UWP项目,然后:

MainPage.xaml中

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Button x:Name="NavigationButton" Click="NavigationButton_Click" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,20,0,0">Navigate</Button> </Grid> 

MainPage.xaml.cs中

 using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace App7 { public sealed partial class MainPage : Page { private App app; public MainPage() { this.InitializeComponent(); app = (App)Application.Current; } private void NavigationButton_Click(object sender, RoutedEventArgs e) { var viewModel = new ExamplePageViewModel(app.GetPageId()); Frame.Navigate(typeof(ExamplePage), viewModel); } } } 

ExamplePage.xaml

 <Grid x:Name="MainGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" LayoutUpdated="MainGrid_LayoutUpdated"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <Button x:Name="NavigationButton" Click="NavigationButton_Click" HorizontalAlignment="Center" Margin="0,20,0,0">Go Back</Button> <TextBlock Text="{Binding PageId}" Grid.Row="1" FontSize="30" HorizontalAlignment="Center"></TextBlock> </Grid> 

ExamplePage.xaml.cs

 using System.Diagnostics; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace App7 { public sealed partial class ExamplePage : Page { private ExamplePageViewModel viewModel; public ExamplePage() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { if (e.NavigationMode == NavigationMode.New || e.NavigationMode == NavigationMode.Back) { viewModel = (ExamplePageViewModel)e.Parameter; DataContext = viewModel; } } private void NavigationButton_Click(object sender, RoutedEventArgs e) { Frame.GoBack(); } private void MainGrid_LayoutUpdated(object sender, object e) { Debug.WriteLine("grid layout updated on page " + viewModel?.PageId.ToString()); } } } 

ExamplePageViewModel.cs

 using System.ComponentModel; using Windows.UI.Xaml; namespace App7 { public class ExamplePageViewModel : INotifyPropertyChanged { private App app; private int pageId; public event PropertyChangedEventHandler PropertyChanged; public int PageId { get { return pageId; } } public ExamplePageViewModel(int pageId) { app = (App)Application.Current; this.pageId = pageId; } } } 

注意:视图模型只是为了清楚哪个页面仍然在发射事件。 您可以删除视图模型,它不会改变问题。

如果元素没有被垃圾收集器收集,那么LayoutUpdated事件将被触发。 由于我们不知道Frame类的实现,所以我们不知道它是如何引用它实例化的页面(也许它拥有一个对于空载页面的引用,比它需要的时间稍长?谁知道)。

这与垃圾收集器的异步特性一起,意味着旧页面仍然可以引发LayoutUpdated事件,直到事件处理程序被删除或者GC收集该对象。 在你的例子中,GC根本没有收集你的旧页面。


如果您有几个复杂的页面仍然在内存中,这不会使应用性能下降吗? 我可以在我的应用程序中看到,数十个复杂的页面仍在触发LayoutUpdated事件,因此所有的控件都在计算每个页面导航的大小,对吧?

这些页面应该在下一个垃圾回收周期中由GC收集,这些垃圾回收周期会在需要的时候自动发生。 你可以用GC.Collect()强制垃圾收集,但是我不推荐这个。 GC在确定执行集合的时间方面比您(通常)要好。

LayoutUpdated事件在所有元素(我认为)上被触发,而不管该特定元素的布局是否已经改变。 如果您阅读了该事件的文档 ,则说明在元素的布局受到兄弟(例如)影响的情况下,有必要执行此操作。

布局系统是相当优化的。 每次收到LayoutUpdated事件时,我都不会为所有元素执行复杂的布局传递,所以我不担心这种情况。 但是,当元素不可见时,确保在这些事件处理程序中不要进行不必要的计算是非常重要的。

这里是另一个很好地解释LayoutUpdated事件的页面 。 这是一个静态事件 ,如果有任何元素更新了布局,就会被触发。

我不会把任何不必要的代码放到LayoutUpdated事件中,或者我会在导航返回时解除绑定,但仍然会重新计算所有控件的大小,对吧?

您对LayoutUpdated事件的响应应该是“更新了布局的某些元素,这可能会影响我,也可能不会影响到我”。 您可以解除绑定该事件,或者您可以检查this.Parentthis.Frame是否为空并this.Frame ,在这种情况下该页面不在框架中。

即使控件不在视觉树中,是否还有其他事件触发? 我怎样才能找到这样的事件清单?

我不确定。 你需要测试你的应用程序的这些情况,只是在每个事件处理程序中放置一个断点,以便知道它是否被解雇。