windows - 未导航的页面仍会在 UWP 应用中发出事件

标签 windows uwp

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

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

  • 有 2 个页面:MainPage 和 ExamplePage。您可以从 MainPage 转到ExamplePage,也可以从ExamplePage 返回到MainPage。

  • 每次导航到 ExamplePage 时,都会为新创建的页面提供一个新 ID(页面不会被缓存)。

  • ExamplePage 中的网格发出 LayoutChanged 事件。事件处理程序将文本写入调试控制台,例如:“网格布局已在第 0 页更新”。 0 是我赋予该页面的页面 ID。

  • 如果您来回几次,您将看到旧页面仍然将布局更新文本写入控制台。例如,如果我转到 ID 为 3 的页面,它会写入控制台:

grid layout updated on page 0

grid layout updated on page 3

grid layout updated on page 1

grid layout updated on page 2

请注意,旧页面仍在更新其布局。旧页面不应再发出任何事件,但它们会继续发出事件,尽管无法再导航到它们并且它们已被卸载。

这是代码,有5个文件,只需在VS2015中新建一个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());
        }
    }
}

示例PageViewModel.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;
        }
    }
}

注意: View 模型只是为了清楚地看到哪个页面仍在发出事件。您可以删除 View 模型,但这不会改变问题。

最佳答案

LayoutUpdated对于不在主视觉树中的元素,如果该元素尚未被垃圾收集器收集,则事件将被触发。由于我们不知道 Frame 类的实现,因此我们不知道它如何引用它实例化的页面(也许它对已卸载页面的引用保存的时间比需要的时间稍长一些?谁知道呢)。

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


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?

这些页面应该由 GC 在下一个垃圾收集周期中收集,这将在需要时自动发生。您可以使用 GC.Collect() 强制进行垃圾回收,但我不建议这样做。 GC 比您(通常)更擅长确定执行收集的时间。

LayoutUpdated 事件会在所有元素上触发(我认为),无论该特定元素的布局是否已更改。如果您阅读docs对于该事件,它解释了有必要执行此操作,以防元素的布局受到同级元素的影响(例如)。

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

Here is another page这很好地解释了 LayoutUpdated 事件。它是一个静态事件,如果任何地方的任何元素的布局已更新,该事件就会被触发。

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?

您对 LayoutUpdated 事件的响应应该是“某处的某个元素的布局已更新,这可能会或可能不会影响我”。您可以取消绑定(bind)事件,或者您可以检查 this.Parentthis.Frame 是否为 null 并退出,在这种情况下,页面不在框架中.

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?

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

关于windows - 未导航的页面仍会在 UWP 应用中发出事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40116576/

相关文章:

python - 在 apache(linux 和 windows)上运行 python 脚本

windows - UWP - 从基页 C# 继承

JavaFX 媒体播放器 : MP4 Won't Loop on Windows 7

Windows7批处理,如何在第一个For循环后中断

c - PeekNamedPipe 总是为 totalBytesAvailable 返回 0

xaml - UWP - 单击空白区域关闭我的内容对话框

c# - 为应用程序中的所有文本框选择 TextBox 中的所有文本

sql - Azure sql 数据同步代理 2 无法安装

visual-studio - 使用 msbuild 从命令行创建应用程序包

javascript - 如何将工具栏中的按钮设置为禁用