Reputation: 1275
I observed a strange behavior in my UWP Windows 10 app. Even if I navigate back from a page and the page is unloaded, the page still continues to emit events. For example, the old page which I navigated from, still emits "LayoutUpdated" event even if I go to a completely different page.
I prepared a minimal example to demonstrate this (code is below). It is pretty simple:
There are 2 pages: MainPage and ExamplePage. You can go from MainPage to ExamplePage and you can go back from ExamplePage to MainPage.
Every time you navigate to ExamplePage, a new ID is given to that newly created page (page is not cached).
A Grid in ExamplePage emits LayoutChanged event. And event handler writes a text to Debug console like: "grid layout updated on page 0". 0 is the page ID I gave to that page.
If you go back and forth a few times, you will see that the old pages still write layout updated text to the console. For example, if I go to page with ID 3, it writes to the console:
grid layout updated on page 0
grid layout updated on page 3
grid layout updated on page 1
grid layout updated on page 2
notice that the old pages still update their layouts. The old pages, should not emit any events anymore but they keep emitting events although there is no way to navigate to them anymore and they are unloaded.
Here is the code, there are 5 files, just create a new UWP project in VS2015 and then:
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;
}
}
}
Note: The viewmodel is just to see clearly which page is still emiting events. You can remove the viewmodel, and it won't change the problem.
Upvotes: 2
Views: 227
Reputation: 34306
The LayoutUpdated event will be fired for elements that are not in the main visual tree, provided that the element has not been collected by the garbage collector. Since we don't know the implementation of the Frame class, we don't know how it references the pages it instantiates (maybe it holds a reference to unloaded pages for slightly longer than it needs to? Who knows).
This, together with the asynchronous nature of the garbage collector, means that old pages can still raise LayoutUpdated events until either the event handler is removed or the object is collected by the GC. In your example, the GC simply hasn't gotten around to collecting your old pages.
Doesn't this make app performance drop if you have several complex pages still on memory? I can see on my app, that tens of complex pages are still firing the LayoutUpdated event, so all the controls are calculating their sizes on every page navigation, right?
Those pages should be collected by the GC during the next garbage collection cycle, which will automatically occur at some point when it is necessary to do so. You can force a garbage collection with GC.Collect()
, but I don't recommend this. The GC is better at determining times to perform a collection than you are (generally).
The LayoutUpdated event is fired on all elements (I think), regardless of whether or not that particular element's layout has changed. If you read the docs for that event, it explains that it is necessary to do this in case the element's layout is affected by a sibling (for example).
The layout system is quite optimized. I don't think complex layout passes are performed for all elements every time they receive a LayoutUpdated event, so I wouldn't worry about that. However, it is important to make sure you're not doing unnecessary calculations in these event handlers when the element isn't visible.
Here is another page which explains the LayoutUpdated event quite well. It is a static event, which gets fired if any element anywhere had its layout updated.
I won't put any unnecessary code to LayoutUpdated event or I will unbind them on navigating back but still it will recalculate all the control's sizes on its own, right?
Your response to the LayoutUpdated event should be "some element somewhere had its layout updated which may or may not affect me". You could unbind the event, or you can just check if this.Parent
or this.Frame
is null and bail out, in which case the Page isn't in the Frame.
Are there any other events that fire even if the control is not in the visual tree? How can I find a list for such events?
I'm not sure. You need to test your app for these situations, just put a breakpoint in each event handler so you know if it's getting fired.
Upvotes: 2