-1

I have a window with a page named ShellPage that contains a NavigationView control and a Frame that contains the page content. When initializing the ShellPage, I load the ExploreCountriesPage. The ExploreCountriesPage contains a list with items to which you can then navigate to a new ExploreRadiosPage. A parameter is passed to this page with the selected ExploreCountriesPage list item.

Navigating to a new page doesn't cause me a problem, but I have a problem navigating backwards between pages. The NavigationView has its Back Button, but when I want to go back to the previous page (ExploreCountriesPage <-- ExploreRadiosPage) using that button the application crashes and throws an exception. I linked the NavigationView with the NavigationViewControl_BackRequested event which should check if it is possible to return to the previous page.

I also registered my pages to service collection using dependency injection in the App.xaml.cs.

What am I doing wrong?

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);
        }
    }
}
Sisimośki
  • 143
  • 1
  • 10
  • 1
    I'm trying to reproduce your problem but the ``Frame`` is null in this line ``this.Frame.Navigate(typeof(ExploreRadiosPage), CountriesViewModel.SelectedItem);``. Can you post code around there? – Andrew KeepCoding Apr 12 '23 at 00:44
  • Is the code you posted correct? ``ExploreCountriesPage`` navigates on ``ShellPage.RootFrame`` where ``ExploreRadiosPage`` navigates on ``ExploreCountriesPage.Frame``. – Andrew KeepCoding Apr 12 '23 at 00:49
  • @AndrewKeepCoding Sure, I tried to shorten the code to for the sake of the post, but you can check my repo: https://github.com/Sisimoski/Rad.io/tree/WinUI/Rad.io.Client.WinUI – Sisimośki Apr 12 '23 at 04:34
  • @AndrewKeepCoding I also updated post with my ViewModels and Pages – Sisimośki Apr 12 '23 at 04:44
  • I cloned your repo. What are the steps to reproduce your issue? – Andrew KeepCoding Apr 12 '23 at 06:41
  • @AndrewKeepCoding Run the app and select Explore menu item and then select a country and then go back using Back button in the left upper corner. Then you should get an exception – Sisimośki Apr 12 '23 at 06:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253103/discussion-between-sisimoski-and-andrew-keepcoding). – Sisimośki Apr 12 '23 at 07:01

2 Answers2

1

I guess the app crashes because InitializeDataAsync() is called twice or more at the same time.

This is because OnNavigatedTo() is called several times. It seems that unnecessary navigations are performed.

This is the same reason why you need to click the Back button several times. You can observe this by checking the BackStackCount on the RootFrame. It shows "2" when is supposed to be "1".

My advice would be that you need to reconsider your navigation implementation and make it simpler. Also I guess you need make sure that radioBrowserClient is not accessed while is busy. Use a SemaphoreSlim maybe.

Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21
1

I solved my problem. Initially I thought it was a problem caused by my Windows 11 for Arm64, but actually there was a problem with navigating. When selecting an item from list it navigated to another page, but added several pages in-between for some reason.

I still don't know why is that happening, but finally I removed SelectionMode, SelectedItem properties and SelectionChanged event from ListView and instead I added IsItemClickEnabled property on True and set ItemClick event.

Basically I switched selection item mechanism from SelectionChanged event to ItemClick event. That trick seems to work and there is now no problem when navigating back to previous pages.

Here's updated code:

ExploreCountriesPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<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>
        <ListView x:Name="CountriesListView"
            Grid.Row="2"
                  ItemsSource="{x:Bind CountriesViewModel.Countries, Mode=OneWay}"
                  SelectionMode="None"
                  IsItemClickEnabled="True"
                  ItemClick="CountriesListView_ItemClick">
            <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>

ExploreCountriesPage.xaml.cs

namespace Rad.io.Client.WinUI.Views
{
    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;
        }
        ...

        private void CountriesListView_ItemClick(object sender, ItemClickEventArgs e)
        {
            NameAndCount selectedItem = e.ClickedItem as NameAndCount;
            this.Frame.Navigate(typeof(ExploreRadiosPage), selectedItem);
        }
    }
}

Sisimośki
  • 143
  • 1
  • 10