かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Xamarin.Forms + Prism.FormsでMasterDetailPageを使うには

ytabuchi.hatenablog.com

上記記事をPrism.Formsを使ってやるとどうなるだろうという奴です。

まず、Prism MasterDetailPage (Forms)でRootPageという名前のページを作ります。

続けて、メニュー部を担当するMenuPageをPrism ContentPage (Forms)で作ります。

Menuに表示するための要素を表すMenuItemクラスを作成しましょう。オリジナルと違うのはPrismの画面遷移は名前でやるので、PageTypeではなくPageNameを持たせてるところと、アイコンは用意するのがめんどくさいので諦めました。

namespace PrismUnityApp12.ViewModels
{
    public class MenuItem
    {
        public string Title { get; set; }
        public string PageName { get; set; }
    }
}

RootPageViewModelを作ってメニューを組み立てます。

public class RootPageViewModel : BindableBase
{
    public ObservableCollection<MenuItem> Menus { get; } = new ObservableCollection<MenuItem>
    {
        new MenuItem
        {
            Title = "Contracts",
            PageName = "ContractsPage"
        },
        new MenuItem
        {
            Title = "Leads",
            PageName = "LeadsPage"
        },
        new MenuItem
        {
            Title = "Accounts",
            PageName = "AccountsPage"
        },
        new MenuItem
        {
            Title = "Opportunities",
            PageName = "OpportunitiesPage"
        },
    };
}

このメニューを表示するための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"
             x:Class="PrismUnityApp12.Views.MenuPage"
             Title="Menu">
  <StackLayout>
    <Label Text="Menu" 
           FontSize="18"
           Margin="10,36,0,5"/>
    <ListView ItemsSource="{Binding Menus}"
              VerticalOptions="FillAndExpand"
              ItemSelected="ListViewMenu_ItemSelected">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout Orientation="Horizontal">
              <Label Text="{Binding Title}"
                     HorizontalOptions="FillAndExpand" />
            </StackLayout>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>

MenuPageは、RootPage.MasterプロパティにセットするのでBindingContextはRootPageViewModelです。コードビハインドは以下のような感じです。

using PrismUnityApp12.ViewModels;
using System;
using Xamarin.Forms;

namespace PrismUnityApp12.Views
{
    public partial class MenuPage : ContentPage
    {
        private RootPageViewModel ViewModel => this.BindingContext as RootPageViewModel;

        public MenuPage()
        {
            InitializeComponent();
        }

        private async void ListViewMenu_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            await this.ViewModel.PageChangeAsync(e.SelectedItem as ViewModels.MenuItem);
        }
    }
}

RootPageViewModelに以下のようなコードを追加して画面遷移対応させます。

using Prism.Mvvm;
using Prism.Navigation;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace PrismUnityApp12.ViewModels
{
    public class RootPageViewModel : BindableBase
    {
        public ObservableCollection<MenuItem> Menus { get; } = new ObservableCollection<MenuItem>
        {
            new MenuItem
            {
                Title = "Contracts",
                PageName = "ContractsPage"
            },
            new MenuItem
            {
                Title = "Leads",
                PageName = "LeadsPage"
            },
            new MenuItem
            {
                Title = "Accounts",
                PageName = "AccountsPage"
            },
            new MenuItem
            {
                Title = "Opportunities",
                PageName = "OpportunitiesPage"
            },
        };

        private INavigationService NavigationService { get; }

        private bool isPresented;

        public bool IsPresented
        {
            get { return this.isPresented; }
            set { this.SetProperty(ref this.isPresented, value); }
        }

        public RootPageViewModel(INavigationService navigationService)
        {
            this.NavigationService = navigationService;
        }

        public async Task PageChangeAsync(MenuItem menuItem)
        {
            await this.NavigationService.Navigate($"NavigationPage/{menuItem.PageName}");
            this.IsPresented = false;
        }
    }
}

Prism MasterDetailPage (Forms)をRootPageという名前で作って以下のように書きます。

<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
                  xmlns:Views="clr-namespace:PrismUnityApp12.Views;assembly=PrismUnityApp12"
                  prism:ViewModelLocator.AutowireViewModel="True"
                  x:Class="PrismUnityApp12.Views.RootPage"
                  IsPresented="{Binding IsPresented, Mode=TwoWay}">
  <MasterDetailPage.Master>
    <Views:MenuPage />
  </MasterDetailPage.Master>
  <MasterDetailPage.Detail>
    <ContentPage Title="Dummy" /> <!-- Dummy -->
  </MasterDetailPage.Detail>
</MasterDetailPage>

IsPresentedをViewModelとバインドするのを忘れずに。

あとは、AccountsPage, ContractsPage, LeadsPage, OpportunitiesPageを作ってApp.xaml.csで登録しておきます。

using Prism.Unity;
using PrismUnityApp12.Views;
using Xamarin.Forms;

namespace PrismUnityApp12
{
    public partial class App : PrismApplication
    {
        protected override async void OnInitialized()
        {
            InitializeComponent();

            await this.NavigationService.Navigate("/RootPage/NavigationPage/ContractsPage");
        }

        protected override void RegisterTypes()
        {
            this.Container.RegisterTypeForNavigation<RootPage>();
            this.Container.RegisterTypeForNavigation<NavigationPage>();
            this.Container.RegisterTypeForNavigation<ContractsPage>();
            this.Container.RegisterTypeForNavigation<LeadsPage>();
            this.Container.RegisterTypeForNavigation<AccountsPage>();
            this.Container.RegisterTypeForNavigation<OpportunitiesPage>();
        }
    }
}

これで実行すると、以下のようになります。

f:id:okazuki:20160423231619p:plain

f:id:okazuki:20160423231627p:plain

f:id:okazuki:20160423231655p:plain