WinUI中Frame页面返回导航崩溃问题求助
问题描述
我有一个包含ShellPage的WinUI窗口,ShellPage内包含NavigationView控件和承载页面内容的Frame,初始化时会加载ExploreCountriesPage。该页面的列表项被选中后,可导航至ExploreRadiosPage并传递选中项作为参数。向前导航无问题,但点击NavigationView的返回按钮从ExploreRadiosPage返回ExploreCountriesPage时,应用崩溃并抛出异常。我已将NavigationView与NavigationViewControl_BackRequested事件绑定,该事件会判断是否可返回至上一页,且已在App.xaml.cs中通过依赖注入注册页面。请问我哪里操作有误?
ShellPage.xaml
<Page x:Class="Rad.io.Client.WinUI.Views.ShellPage"> <Grid> <NavigationView x:Name="NavigationViewControl" IsBackButtonVisible="Auto" PaneDisplayMode="Auto" ItemInvoked="NavigationViewControl_ItemInvoked" IsBackEnabled="{x:Bind RootFrame.CanGoBack, Mode=OneWay}" BackRequested="NavigationViewControl_BackRequested" Loaded="NavigationViewControl_Loaded"> <Frame Grid.Row="0" x:Name="RootFrame"/> </Grid> </NavigationView> </Grid> </Page>
ShellPage.xaml.cs
public sealed partial class ShellPage : Page { public ShellViewModel ShellViewModel { get; set; } public ShellPage() { this.ShellViewModel = App.Current.Services.GetService<ShellViewModel>(); this.InitializeComponent(); RootFrame.Content = new ExploreCountriesPage(); } private void NavigationViewControl_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { switch (args.InvokedItemContainer.Tag) { case "Explore": RootFrame.Navigate(typeof(ExploreCountriesPage)); break; case "Library": RootFrame.Navigate(typeof(LibraryPage)); break; } } private void NavigationViewControl_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) { if (RootFrame.CanGoBack) { RootFrame.GoBack(); } } }
ExploreCountriesPage.xaml.cs
public sealed partial class ExploreCountriesPage : Page { public ExploreCountriesViewModel CountriesViewModel { get; set; } public ExploreCountriesPage() { this.InitializeComponent(); this.CountriesViewModel = App.Current.Services.GetService<ExploreCountriesViewModel>(); this.DataContext = CountriesViewModel; } protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); await CountriesViewModel.InitializeDataAsync(); } private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { CountriesViewModel.SelectedItem = (RadioBrowser.Models.NameAndCount)CountriesListView.SelectedItem; this.Frame.Navigate(typeof(ExploreRadiosPage), CountriesViewModel.SelectedItem); } }
ExploreRadiosPage.xaml.cs
public sealed partial class ExploreRadiosPage : Page { public ExploreRadiosViewModel ExploreRadiosViewModel { get; set; } public ExploreRadiosPage() { this.InitializeComponent(); this.ExploreRadiosViewModel = App.Current.Services.GetService<ExploreRadiosViewModel>(); this.DataContext = ExploreRadiosViewModel; } protected override async void OnNavigatedTo(NavigationEventArgs e) { ExploreRadiosViewModel.SelectedCountry = e.Parameter as NameAndCount; base.OnNavigatedTo(e); } }
ExploreCountriesPage.xaml
<Page x:Class="Rad.io.Client.WinUI.Views.ExploreCountriesPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Rad.io.Client.WinUI.Views" xmlns:viewmodels="using:Rad.io.Client.WinUI.ViewModels" xmlns:nameandcount="using:RadioBrowser.Models" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="Explore" Style="{ThemeResource TitleTextBlockStyle}"/> <TextBox Text="{x:Bind CountriesViewModel.EntryQuery, Mode=OneWay}" Grid.Row="1" PlaceholderText="Search countries..."/> <ListView x:Name="CountriesListView" Grid.Row="2" ItemsSource="{x:Bind CountriesViewModel.Countries, Mode=OneWay}" SelectionMode="Single" SelectionChanged="ListView_SelectionChanged" SelectedItem="{x:Bind CountriesViewModel.SelectedItem}"> <ListView.ItemTemplate> <DataTemplate x:DataType="nameandcount:NameAndCount"> <Grid RowSpacing="4" Margin="8"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{x:Bind Name}"/> <TextBlock Grid.Row="1" Text="{x:Bind Stationcount}"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Page>
ExploreRadiosPage.xaml
<?xml version="1.0" encoding="utf-8"?> <Page x:Class="Rad.io.Client.WinUI.Views.ExploreRadiosPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Rad.io.Client.WinUI.Views" xmlns:radiobrowsermodels="using:RadioBrowser.Models" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="Explore" Style="{ThemeResource TitleTextBlockStyle}"/> <TextBox Text="{x:Bind ExploreRadiosViewModel.EntryQuery, Mode=OneWay}" Grid.Row="1" PlaceholderText="Search countries..."/> <ListView x:Name="RadiosListView" Grid.Row="2" ItemsSource="{x:Bind ExploreRadiosViewModel.Stations, Mode=OneWay}" SelectionChanged="RadiosListView_SelectionChanged"> <ListView.ItemTemplate> <DataTemplate x:DataType="radiobrowsermodels:StationInfo"> <Grid RowSpacing="4" Margin="8"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="{x:Bind Name}"/> <TextBlock Grid.Row="1" Text="{x:Bind Url}"/> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Page>
ExploreCountriesViewModel.cs
public class ExploreCountriesViewModel : INotifyPropertyChanged { private readonly IRadioBrowserClient radioBrowserClient; private readonly INavigationService navigationService; private List<NameAndCount> _countries; private List<NameAndCount> filteredCountries; private NameAndCount selectedItem; private string entryQuery; public List<NameAndCount> Countries { get => _countries; set { _countries = value; RaisePropertyChanged(); } } public List<NameAndCount> FilteredCountries { get { return filteredCountries; } set { if (EntryQuery is null) filteredCountries = Countries; else { filteredCountries = Countries.Where(value => value.Name.Contains(EntryQuery, StringComparison.OrdinalIgnoreCase)).ToList(); } RaisePropertyChanged(); } } public NameAndCount SelectedItem { get; set; } public string EntryQuery { get => entryQuery; set { entryQuery = value; RaisePropertyChanged(); } } public ExploreCountriesViewModel(IRadioBrowserClient radioBrowserClient, INavigationService navigationService) { this.radioBrowserClient = radioBrowserClient; this.navigationService = navigationService; } public async Task InitializeDataAsync() { try { Countries = await radioBrowserClient.Lists.GetCountriesAsync(); foreach (var result in Countries) { Debug.WriteLine(result.Name); } } catch (Exception e) { Debug.WriteLine(e); } } public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
ExploreRadiosViewModel.cs
public class ExploreRadiosViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private readonly IRadioBrowserClient radioBrowserClient; private NameAndCount selectedCountry; private List<StationInfo> stations; private List<StationInfo> filteredStations; private string entryQuery; public List<StationInfo> Stations { get => stations; set { stations = value; RaisePropertyChanged(); } } public NameAndCount SelectedCountry { get => selectedCountry; set { selectedCountry = value; RaisePropertyChanged(); } } public string EntryQuery { get => entryQuery; set { entryQuery = value; RaisePropertyChanged(); } } public List<StationInfo> FilteredStations { get { if (entryQuery is null) return Stations; return Stations.Where(value => value.Name.Contains(EntryQuery, StringComparison.OrdinalIgnoreCase)).ToList(); } } public ExploreRadiosViewModel(IRadioBrowserClient radioBrowserClient) { this.radioBrowserClient = radioBrowserClient; } public async Task InitializeDataAsync() { try { Stations = await radioBrowserClient.Search.AdvancedAsync(new AdvancedSearchOptions { Country = SelectedCountry.Name }); foreach (var result in Stations) { Debug.WriteLine(result.Name); } } catch (Exception e) { Debug.WriteLine(e); } } }
问题排查与解决方案
核心问题1:导航栈初始化错误
ShellPage构造函数中直接用RootFrame.Content = new ExploreCountriesPage();设置页面,这种方式不会将页面加入Frame的导航栈。当从ExploreRadiosPage返回时,导航栈为空,调用RootFrame.GoBack()会触发异常。
修复:
将构造函数中的初始化代码改为:
RootFrame.Navigate(typeof(ExploreCountriesPage));
核心问题2:ListView选中事件重复触发
ExploreCountriesPage的ListView同时绑定了SelectedItem和SelectionChanged事件,返回页面时SelectedItem的绑定会再次触发SelectionChanged,导致重复导航引发异常。
修复:
- 移除XAML中的
SelectionChanged="ListView_SelectionChanged"绑定 - 修改ViewModel的SelectedItem属性,在变更时处理导航:
private NameAndCount selectedItem; public NameAndCount SelectedItem { get => selectedItem; set { if (selectedItem != value) { selectedItem = value; RaisePropertyChanged(); if (selectedItem != null) { navigationService.Navigate(typeof(ExploreRadiosPage), selectedItem); } } } }
- 删除页面中的
ListView_SelectionChanged方法
额外优化
- 在BackRequested事件中添加异常捕获:
private void NavigationViewControl_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) { try { if (RootFrame.CanGoBack) { RootFrame.GoBack(); } } catch (Exception ex) { Debug.WriteLine($"返回导航异常:{ex.Message}"); } }
- 修正ExploreRadiosPage的OnNavigatedTo方法顺序,先调用基类方法再处理参数:
protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); ExploreRadiosViewModel.SelectedCountry = e.Parameter as NameAndCount; await ExploreRadiosViewModel.InitializeDataAsync(); }
内容的提问来源于stack exchange,提问作者Sisimośki
相关产品推荐
相关产品推荐

