かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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