You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,导致重复导航引发异常。

修复:

  1. 移除XAML中的SelectionChanged="ListView_SelectionChanged"绑定
  2. 修改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);
            }
        }
    }
}
  1. 删除页面中的ListView_SelectionChanged方法

额外优化

  1. 在BackRequested事件中添加异常捕获:
private void NavigationViewControl_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
    try
    {
        if (RootFrame.CanGoBack)
        {
            RootFrame.GoBack();
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"返回导航异常:{ex.Message}");
    }
}
  1. 修正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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:49:53