now that you have those laid out add a new "Master Detail Page" to your views folder.
Now this will create four files, in your views folder.
Now this is what i don't like:
- Firstly the PageMenuItem.cs is a model, not view and I don't like it's naming convention,
- I rename it to BurgerMenuItem.cs
- Move it into my Models folder
- Update its namespace to Models
- Include the missing using statement
- Next You'd think that PageMaster is the master page, well it's not; it actually represents the Burger menu,
- I rename it to MenuPage.
- rename the file
- rename the code-behind
- rename the xaml markup
- In its code-behind you'll see that the view-model is included
- i create a separate file in my view-models folder rename it to MenuPageViewModel and include all of the missing using statements.
- I also extract the INotifyPropertyChanged implementation into an abstract base class called BaseViewModel
- I Inherit from BaseViewModel in my MenuPageViewModel
- I also make some modifications to the notification code
- Next let's look at the Page.xaml file, this is in fact the masterpage
- I Rename it to MasterPage.xaml
- I change the markup to MasterPage
- In the markup i change the name of the MenuPage to MenuPage
- I change the codebehind to MasterPage
- in the codebehind i change the reference to the MenuPage to the name i gave it.
- Now the PageDetail is actually the contentPage that loads, so i like to give it a more meaning full name, in this case i'll call it GamePage.
- Finally i open the App.xaml page and set my apps MainPage to MasterPage
When all is said and done my project looks like so.
So let's look at the BurgerMenuItem.cs class
using System;
using pav.tictactoe.Views;
namespace pav.tictactoe.Models
{
public class BurgerMenuItem
{
public BurgerMenuItem() => TargetType = typeof(GamePage);
public int Id { get; set; }
public string Title { get; set; }
public Type TargetType { get; set; }
}
}
No great leaps of faith here, it's basically the same as before, just changed the constructor a bit for the sake of brevity.
Next let's look at our BaseViewModel.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace pav.tictactoe.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Implementation
bool SetProperty<T>(ref T
backingField, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(backingField,value))
return false;
backingField = value;
NotifyPropertyChanged(propertyName);
return true;
}
void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (!String.IsNullOrEmpty(propertyName))
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event
PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
also nothing special here, this code has been around since wpf, just creates a generic setter for bindable fields and returns a Boolean letting you now if the backing field was updated or not and notifies any bound controls via the Observable Pattern.
next let's take a look at the MenuPageViewModel.cs file
using pav.tictactoe.Models;
using pav.tictactoe.Views;
using System.Collections.ObjectModel;
namespace pav.tictactoe.ViewModels
{
public class MenuPageViewModel : BaseViewModel
{
{
public ObservableCollection<BurgerMenuItem> MenuItems { get; set; }
public MenuPageViewModel()
{
{
MenuItems = new
ObservableCollection<BurgerMenuItem>(new[] {
new BurgerMenuItem { Id = 0, Title = "Game", TargetType= typeof(GamePage) },
new BurgerMenuItem { Id = 1, Title = "Settings" },
new BurgerMenuItem { Id = 2, Title = "Stats" }
});
}
}
}
also no rocket science here, i did include the TargetType for the first Menu item to illustrate how you'd set up the navigation to a page; later we'll add a settings and stats page.
Next we'll take a look at the game page only because we have to update the class name in two places and because we're just going to leave it as a place holder it'll be easiest to show where, you'll have to do the same for the MasterPage and the MenuPage.
Next we'll take a look at the game page only because we have to update the class name in two places and because we're just going to leave it as a place holder it'll be easiest to show where, you'll have to do the same for the MasterPage and the MenuPage.
firstly in the 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"
x:Class="pav.tictactoe.Views.GamePage"
Title="Tic Tac
Toe">
<StackLayout>
<Label Text="This
is test data" />
</StackLayout>
</ContentPage>
In the above snippet make sure to update the x:class path to your new page name
and make sure that it's aligned with the Class name in the codebehind
Now the Menu page, this is
This page represents the Slider for the Hamburger Menu
and again make sure to update it in the codebehind
And finally let's take a look at the MasterPage
here we have a couple things to update:
next take a look into the code behind
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace pav.tictactoe.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class GamePage : ContentPage
{
public GamePage() => InitializeComponent();
}
}
Now the Menu page, this is
<?xml
version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="pav.tictactoe.Views.MenuPage"
Title="Master">
<StackLayout>
<ListView x:Name="MenuItemsListView"
SeparatorVisibility="None"
HasUnevenRows="true"
ItemsSource="{Binding MenuItems}">
<ListView.Header>
<Grid BackgroundColor="#03A9F4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="80"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Label
Grid.Column="1"
Grid.Row="2"
Text="AppName"
Style="{DynamicResource SubtitleStyle}"/>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="15,10" HorizontalOptions="FillAndExpand">
<Label VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
Text="{Binding Title}"
FontSize="24"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
and again make sure to update it in the codebehind
using pav.tictactoe.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace pav.tictactoe.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MenuPage : ContentPage
{
public ListView ListView;
public MenuPage()
{
InitializeComponent();
BindingContext = new MenuPageViewModel();
ListView = MenuItemsListView;
}
}
}
And finally let's take a look at the MasterPage
<?xml
version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="pav.tictactoe.Views.MasterPage"
xmlns:pages="clr-namespace:pav.tictactoe.Views">
<MasterDetailPage.Master>
<pages:MenuPage x:Name="MenuPage" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<pages:GamePage />
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
- Updated that the class name to MasterPage
- Update the MenuPage and give it a more descriptive name
- Update the detailpage to GamePage
next take a look into the code behind
using pav.tictactoe.Models;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace pav.tictactoe.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterPage : MasterDetailPage
{
public MasterPage()
{
InitializeComponent();
MenuPage.ListView.ItemSelected +=
ListView_ItemSelected;
}
private void
ListView_ItemSelected(object sender,
SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as BurgerMenuItem;
if (item == null)
return;
var page = (Page)Activator.CreateInstance(item.TargetType);
page.Title = item.Title;
base.Detail = new
NavigationPage(page);
base.IsPresented = false;
MenuPage.ListView.SelectedItem = null;
}
}
}
In the codebehind again you have to update the class name but also the MenuItem cast to whatever we renamed our Model to, in this case BurgerMenuItem.
And finally let's open our App.Xaml.cs class
using pav.tictactoe.Views;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly:
XamlCompilation(XamlCompilationOptions.Compile)]
namespace pav.tictactoe
{
public partial class App : Application
{
public App ()
{
InitializeComponent();
MainPage = new MasterPage();
}
protected override void OnStart
() { /* Handle when your app starts */ }
protected override void OnSleep
() { /* Handle when your app sleeps */ }
protected override void OnResume
() { /* Handle when your app resumes */ }
}
}
Here in our constructor we simply set the page we want our application to set as it's MainPage.