【问题标题】:Unnavigated pages still emit events in UWP app未导航的页面仍会在 UWP 应用中发出事件
【发布时间】:2016-10-19 18:07:21
【问题描述】:

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

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

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

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

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

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

网格布局在第 0 页更新

第 3 页更新了网格布局

第 1 页上更新了网格布局

第 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());
        }
    }
}

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;
        }
    }
}

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

【问题讨论】:

    标签: windows uwp


    【解决方案1】:

    LayoutUpdated 事件将为不在主视觉树中的元素触发,前提是该元素尚未被垃圾收集器收集。由于我们不知道 Frame 类的实现,我们不知道它如何引用它实例化的页面(也许它持有对未加载页面的引用的时间比它需要的时间稍长一些?谁知道)。

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


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

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

    LayoutUpdated 事件会在所有元素上触发(我认为),无论该特定元素的布局是否已更改。如果您阅读了该事件的docs,它说明有必要这样做,以防元素的布局受到兄弟姐妹的影响(例如)。

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

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

    我不会在 LayoutUpdated 事件中添加任何不必要的代码,或者我会在返回时取消绑定它们,但它仍然会自行重新计算所有控件的大小,对吗?

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

    即使控件不在可视化树中,是否还会触发其他事件?如何找到此类活动的列表?

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

    【讨论】:

    • 我明白了,谢谢。如果您的内存中有多个复杂页面,这不会降低应用程序的性能吗?我可以在我的应用程序中看到,数十个复杂页面仍在触发 LayoutUpdated 事件,因此所有控件都在计算每个页面导航的大小,对吧?我不会在 LayoutUpdated 事件中添加任何不必要的代码,或者我会在返回时取消绑定它们,但它仍然会自行重新计算所有控件的大小,对吧?还有一个问题:即使控件不在可视树中,是否还有其他事件会触发?如何找到此类活动的列表?
    猜你喜欢
    • 2017-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 2017-12-24
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    相关资源
    最近更新 更多