0

I used the default Xamarin.Forms project and try to convert them using the Prism.Forms.

I managed it to work to show the MenuPage in the SplitView and ItemsPage on the content by NavigateAsync("MainPage/ItemsPage") in the App.xaml.cs.

Going forward, I noticed that when I navigate to a new page, I am not getting Back button showing on the UWP app, and I read that I need to wrap the MainPage in NavigationPage, so I tried doing it by registering the NavigationPage to the container and NavigateAsync("NavigationPage/MainPage/ItemsPage"), but unfortunately I am getting blank UI.

What did I do wrong here?

I can provide more code if this is not enough.

In App.xaml.cs

protected override void OnInitialized()
{
    this.InitializeComponent();


    var navigationPage = $"{nameof(NavigationPage)}/{nameof(Views.MainPage)}/{nameof(ItemsPage)}";    // why does this shows blank UI?
    var navigationPage2 = $"{nameof(Views.MainPage)}/{nameof(Views.ItemsPage)}"; // this shows MenuPage on the menu and items page on the main content.
    _ = this.NavigationService.NavigateAsync($"{navigationPage}");
}

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<NavigationPage>(nameof(NavigationPage));
    containerRegistry.RegisterForNavigation<AboutPage>(nameof(AboutPage));
    containerRegistry.RegisterForNavigation<ItemDetailPage>(nameof(ItemDetailPage));
    containerRegistry.RegisterForNavigation<ItemsPage>(nameof(ItemsPage));
    containerRegistry.RegisterForNavigation<MainPage>(nameof(MainPage));
    containerRegistry.RegisterForNavigation<MenuPage>(nameof(MenuPage));
    containerRegistry.RegisterForNavigation<NewItemPage>(nameof(NewItemPage));
}

in MainPage.xaml (This is the MasterDetailPage)

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:d="http://xamarin.com/schemas/2014/forms/design"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            xmlns:views="clr-namespace:XamarinApp.Views"
            x:Class="XamarinApp.Views.MainPage">

    <MasterDetailPage.Master>
        <views:MenuPage />
    </MasterDetailPage.Master>

</MasterDetailPage>

in MenuPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:b="clr-namespace:XamarinApp.Behaviors"
             xmlns:prism="http://prismlibrary.com"
             mc:Ignorable="d"
             x:Class="XamarinApp.Views.MenuPage"
             prism:ViewModelLocator.AutowireViewModel="True"
             Title="Menu">

    <StackLayout VerticalOptions="FillAndExpand">
        <ListView
            x:Name="ListViewMenu"
            HasUnevenRows="True"
            ItemsSource="{Binding MenuItems, Mode=OneTime}"
            SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
            <ListView.Behaviors>
                <b:EventToCommandBehavior
                    EventName="ItemSelected"
                    Command="{Binding ItemSelectedCommand, Mode=OneTime}"/>
            </ListView.Behaviors>
            <d:ListView.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>Item 1</x:String>
                    <x:String>Item 2</x:String>
                </x:Array>
            </d:ListView.ItemsSource>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid Padding="10">
                            <Label Text="{Binding Title}" d:Text="{Binding .}" FontSize="20"/>
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>

In ItemsPage.xaml (Not that relevant though)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:b="clr-namespace:XamarinApp.Behaviors"
             xmlns:prism="http://prismlibrary.com"
             mc:Ignorable="d"
             x:Class="XamarinApp.Views.ItemsPage"
             Title="{Binding Title}"
             x:Name="BrowseItemsPage"
             prism:ViewModelLocator.AutowireViewModel="True">

    <ContentPage.Behaviors>
        <b:EventToCommandBehavior
            EventName="Appearing"
            Command="{Binding LoadItemsCommand, Mode=OneTime}"/>
    </ContentPage.Behaviors>

    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add" Command="{Binding AddItemCommand, Mode=OneTime}"/>
    </ContentPage.ToolbarItems>

    <StackLayout Grid.Row="1">
        <ListView
            x:Name="ItemsListView"
            ItemsSource="{Binding Items, Mode=OneWay}"
            SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
            VerticalOptions="FillAndExpand"
            HasUnevenRows="true"
            RefreshCommand="{Binding LoadItemsCommand, Mode=OneTime}"
            IsPullToRefreshEnabled="true"
            IsRefreshing="{Binding IsBusy, Mode=OneWay}"
            CachingStrategy="RecycleElement">
            <ListView.Behaviors>
                <b:EventToCommandBehavior
                    EventName="ItemSelected"
                    Command="{Binding OnItemSelectedCommand, Mode=OneTime}"/>
            </ListView.Behaviors>
            <d:ListView.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>First Item</x:String>
                    <x:String>Second Item</x:String>
                    <x:String>Third Item</x:String>
                    <x:String>Forth Item</x:String>
                    <x:String>Fifth Item</x:String>
                    <x:String>Sixth Item</x:String>
                </x:Array>
            </d:ListView.ItemsSource>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Padding="10">
                            <Label Text="{Binding Text}" 
                                d:Text="{Binding .}"
                                LineBreakMode="NoWrap" 
                                Style="{DynamicResource ListItemTextStyle}" 
                                FontSize="16" />
                            <Label Text="{Binding Description}" 
                                d:Text="Item descripton"
                                LineBreakMode="NoWrap"
                                Style="{DynamicResource ListItemDetailTextStyle}"
                                FontSize="13" />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

</ContentPage>

In ItemsPageViewModel.cs

private readonly INavigationService navigationService;
private ObservableCollection<Item> items;
private Item selectedItem;

public ItemsPageViewModel()
{
    this.Title = "Browse";
    this.Items = new ObservableCollection<Item>();
    this.LoadItemsCommand = new DelegateCommand(this.LoadItems, this.CanLoadItems).ObservesProperty(() => this.Items);
    this.OnItemSelectedCommand = new DelegateCommand(this.OnItemsSelected_Execute, this.OnItemsSelected_CanExecute).ObservesProperty(() => this.SelectedItem);
    this.AddItemCommand = new DelegateCommand(this.AddItem);

}

public ItemsPageViewModel(INavigationService navigationService)
    : this()
{
    this.navigationService = navigationService;
}

public ObservableCollection<Item> Items
{
    get => this.items;
    set => this.SetProperty(ref this.items, value);
}

public Item SelectedItem
{
    get => this.selectedItem;
    set => this.SetProperty(ref this.selectedItem, value);
}

public ICommand AddItemCommand { get; }

public ICommand OnItemSelectedCommand { get; }

public ICommand LoadItemsCommand { get; }

private bool CanLoadItems()
{
    return !(this.Items?.Any() ?? false);
}

private async void LoadItems()
{
    if (this.IsBusy)
    {
        return;
    }

    this.IsBusy = true;

    try
    {
        this.Items.Clear();
        var items = await this.DataStore.GetItemsAsync(true);
        foreach (var item in items)
        {
            this.Items.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        this.IsBusy = false;
    }
}

private bool OnItemsSelected_CanExecute()
{
    return this.SelectedItem != null;
}

private async void OnItemsSelected_Execute()
{
    var parameters = new NavigationParameters
    {
        { "hash", this.selectedItem.ToString() },
    };

    await this.navigationService.NavigateAsync($"{nameof(ItemDetailPage)}", parameters);
    // When I navigated to the ItemDetailPage, I do not see the (SplitView/HamburgerMenu) PageMenu
    // and I do not see Back button

    // Manually deselect item.
    this.SelectedItem = null;
}

private async void AddItem()
{
    await this.navigationService.NavigateAsync($"{nameof(NewItemPage)}");
}
kurakura88
  • 2,185
  • 2
  • 12
  • 18
  • Why exactly are you trying to Wrap a `MasterDetailPage` into a `NavigationPage`? https://stackoverflow.com/questions/49169049/hamburger-menu-xamarin-forms-masterdetailpage/49175351#49175351 – FreakyAli Sep 24 '19 at 07:19
  • According to [official doc](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/master-detail-page): _A MasterDetailPage is designed to be a root page, and using it as a child page in other page types could result in unexpected and inconsistent behavior._ – VahidShir Sep 24 '19 at 07:29
  • @FreakyAli I use this https://dzone.com/articles/prism-for-xamarin-forms-part-3-advanced-navigation as a guide, where it does show how to do it both with and without Prism. The purpose is that this is to enable Back Button when I navigate to a new page. I must be missing something here... – kurakura88 Sep 24 '19 at 08:15
  • So you do not want a back button when you navigate to a new page is that it? – FreakyAli Sep 24 '19 at 08:47
  • I DO want to have a back button. For that, I must wrap the MainPage in NavigationPage as they said. – kurakura88 Sep 24 '19 at 08:58

1 Answers1

0

I found my answer:

  1. We should not wrap MasterDetailPage in the NavigationPage. Instead Wrap the Content in Navigation Page where you want the Back Button to show up. In my case, I need to wrap ItemsPage in Navigation

In App.xaml.cs

var navigationPage = $"{nameof(Views.MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}";
_ = this.NavigationService.NavigateAsync($"{navigationPage}");
  1. In ItemsPageViewModel, I only need to navigate to the destination page without specifying MainPage and NavigationPage, therefore Back Button will appear (and keeping the Hamburger menu intact)

In ItemsPageViewModel.cs

// keep this code intact from example above to navigate, have a back button on the new page, and also keep the hamburger menu intact.
await this.navigationService.NavigateAsync($"{nameof(ItemDetailPage)}", parameters);
  1. In MenuPageViewModel, I would adjust the navigation to always show hamburger menu intact by:

In MenuPageViewModel.cs

private async void ItemSelectedHandler()
{
    if (this.SelectedMenuItem == null)
    {
        return;
    }

    var id = this.SelectedMenuItem.Id;
    switch (id)
    {
        case MenuItemType.Browse:
            // re-show the MenuPage in HamburgerMenu and wrap ItemsPage in NavigationPage.
            // However, navigating with this method will not show the back button in the new page.
            await this.navigationService.NavigateAsync($"/{nameof(MainPage)}/{nameof(NavigationPage)}/{nameof(ItemsPage)}");
            break;
        case MenuItemType.About:
            // re-show the MenuPage in HamburgerMenu and wrap ItemsPage in NavigationPage.
            // However, navigating with this method will not show the back button in the new page.
            await this.navigationService.NavigateAsync($"/{nameof(MainPage)}/{nameof(NavigationPage)}/{nameof(AboutPage)}");
            break;
    }
}
kurakura88
  • 2,185
  • 2
  • 12
  • 18