[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する4

前回の「[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する3」では、最初に実行されるMainPage.csとリスト表示をするファイルItemsPage.xaml、ItemPages.xaml.csについて見てきました。
今回は、リストに表示するデータ作成部分について見ていきます。

おさらい

マスタ/詳細プロジェクトは実行すると以下のような画面になり、データがリスト表示されます。

見てわかる通り、1つの項目はアイテムのタイトルと詳細から成り立っています。

Items.cs

リストに表示される1つのデータはItemsクラスに格納します。このクラスはModelsフォルダ内にあります。Itemsクラスのコードは以下のようになっており、リストに表示するタイトル部分のTextプロパティと、詳細部分のDescriptionプロパティを持っていることがわかります。IDは主キーとして使用します。

using System;

namespace MasterDetailSample
{
    public class Item
    {
        public string Id { get; set; }
        public string Text { get; set; }
        public string Description { get; set; }
    }
}

ItemsViewModel.cs

ItemsViewModelクラスは、ItemsPageとItemデータの橋渡しとなるクラスです。このクラスはBaseViewModelクラスを継承して作られています。

 

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;

using Xamarin.Forms;

namespace MasterDetailSample
{
    public class ItemsViewModel : BaseViewModel
    {
        public ObservableCollection<Item> Items { get; set; }
        public Command LoadItemsCommand { get; set; }

        public ItemsViewModel()
        {
            Title = "Browse";
            Items = new ObservableCollection<Item>();
            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

            MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item) =>
            {
                var _item = item as Item;
                Items.Add(_item);
                await DataStore.AddItemAsync(_item);
            });
        }

        async Task ExecuteLoadItemsCommand()
        {
            if (IsBusy)
                return;

            IsBusy = true;

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

ItemsViewModelは、リストに表示するデータ(Itemsクラス)を管理します。

12行目をみるとわかる通り、このデータはObservableCollectionで管理をします。ObservableCollectionはアイテムに対して、追加、更新、削除などの変更があった場合に、コレクションの変更通知(CollectionChanged)を出すことができるようになっています。

次に21〜26行目を見てみましょう。MessagingCenter.SubscribeはXamarin.Forms側からメッセージを受け取った際に処理を行うものです。ここではアイテムの追加処理をしています。ここではAddItemというメッセージが送られてきた時に処理が行われるようにしています。

では、どこでAddItemのメッセージを送信しているのかというと、新規アイテム追加ページ()NewItemPage.xaml.cs)のSaveを押した時です。その部分のコードは以下のようになっています。

async void Save_Clicked(object sender, EventArgs e)
{
    MessagingCenter.Send(this, "AddItem", Item);
    await Navigation.PopToRootAsync();
}

次に29行目からExecuteLoadItemsCommand()ですが、このメソッドはページが表示されたときに実行されます。保存されているデータを取り出してリストに表示をします。

[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する3

前回の「[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する2」では、実行時の画面とApp.xamlのコードについて見てきました。

今回は、共通プロジェクトが持つViewsフォルダにある以下のファイルを見ていきます。そのほかのファイルについては、この連載の別の回で紹介します。

  • MainPage.cs
  • ItemPage.xaml

MainPage.cs

アプリ実行時に最初に実行されるのがこのファイルです。

ソースコードは以下のようになっています。

using System;

using Xamarin.Forms;

namespace MasterDetailSample
{
    public class MainPage : TabbedPage
    {
        public MainPage()
        {
            Page itemsPage, aboutPage = null;

            switch (Device.RuntimePlatform)
            {
                case Device.iOS:
                    itemsPage = new NavigationPage(new ItemsPage())
                    {
                        Title = "Browse"
                    };

                    aboutPage = new NavigationPage(new AboutPage())
                    {
                        Title = "About"
                    };
                    itemsPage.Icon = "tab_feed.png";
                    aboutPage.Icon = "tab_about.png";
                    break;
                default:
                    itemsPage = new ItemsPage()
                    {
                        Title = "Browse"
                    };

                    aboutPage = new AboutPage()
                    {
                        Title = "About"
                    };
                    break;
            }

            Children.Add(itemsPage);
            Children.Add(aboutPage);

            Title = Children[0].Title;
        }

        protected override void OnCurrentPageChanged()
        {
            base.OnCurrentPageChanged();
            Title = CurrentPage?.Title ?? string.Empty;
        }
    }
}

MainPageクラスはTabbedPageを継承して作られています。実行結果からもわかる通り、アプリの画面はタブで切り替えることができるようになっています。

続いて11行目ですが、タブページに表示するためのページを格納する変数を宣言しています。itemsPageはリスト表示をするためのページを、AboutPageはアプリ情報を表示するためのページを格納する変数です。

13行目のswitch文は、OSの判定を行うためのものです。15行目でiOSを、28行目でiOS以外(AndroidやUWPが対象)を判定します。

iOSの場合はNavigationPageクラスを使用してitemPageとaboutPageをそれぞれ作成しています。また、iOSはタブにアイコンを表示することができるので、作成したページのIconプロパティに表示するイメージ画像を設定しています(25行目と26行目)。これらのイメージ画像はiOS側のプロジェクトのResourcesフォルダに格納されています。

iOS以外の場合はItemPageクラスを使用してitemsPageとaboutPageを作成しています。

ページ作成後はChildrenクラスが持つAddメソッドで追加をします(41行目と42行目)。

最後にTitleプロパティに最初に追加したページ(itemsPage)のタイトルを設定します(44行目)。

ItemsPage.xaml

ItemsPage.xamlはリスト表示を担当するファイルです。

前回も掲載しましたがItemsViewが表示された時のアプリ画面を以下に示します。

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="MasterDetailSample.ItemsPage" Title="{Binding Title}" x:Name="BrowseItemsPage">
	<ContentPage.ToolbarItems>
		<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
	</ContentPage.ToolbarItems>
	<ContentPage.Content>
		<StackLayout>
			<ListView x:Name="ItemsListView" ItemsSource="{Binding Items}" VerticalOptions="FillAndExpand" HasUnevenRows="true" RefreshCommand="{Binding LoadItemsCommand}" IsPullToRefreshEnabled="true" IsRefreshing="{Binding IsBusy, Mode=OneWay}" CachingStrategy="RecycleElement" ItemSelected="OnItemSelected">
				<ListView.ItemTemplate>
					<DataTemplate>
						<ViewCell>
							<StackLayout Padding="10">
								<Label Text="{Binding Text}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemTextStyle}" FontSize="16" />
								<Label Text="{Binding Description}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemDetailTextStyle}" FontSize="13" />
							</StackLayout>
						</ViewCell>
					</DataTemplate>
				</ListView.ItemTemplate>
			</ListView>
		</StackLayout>
	</ContentPage.Content>
</ContentPage>

画面そのものはContentPageを使用して作られています(2行目)。ContentPageはツールバーと1つのコントロールを配置することができ、ツールバーには、[Add]ボタンが1つ配置されています(3〜5行目)。またコントロール配置部分(6〜21行目)ではStackLayoutコントロールの中にListViewコントロールを配置して、リスト表示を行うようにしています。

リスト表示部分は、バインディングでデータを表示するようにしています(8行目)。表示するデータは「 ItemsSource={Binding Items}」でバインディング(連結)しています。

また、リスト表示部分を下に引っ張って離すと最新のデータが表示されるようにするために「 IsPullToRefreshEnabled=true」という記述があります。データの更新は「 RefreshCommand={Binding LoadItemsCommand}」で行なっています。

HasUnevenRows=true」は重要なプロパティです。このプロパティがあることで項目のサイズを動的に変えることができるようになります。

リストにはテンプレートがあります(9行目)。テンプレートは、コレクション内の各要素を表示する方法を指示します。テンプレート内に置いたViewCell(11行目)は1つの項目をどのような要素で表示するかを自由に表現するためのものです。ここではStackPanelにフォントサイズが異なるLabelを2つ縦に並べて配置しています。これによりリストに表示される1つの項目は2つのテキストから構成されることになります。

続いてコードビハインドのItemPages.xaml.csファイルを見てみましょう。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Xamarin.Forms;

namespace MasterDetailSample
{
    public partial class ItemsPage : ContentPage
    {
        ItemsViewModel viewModel;

        public ItemsPage()
        {
            InitializeComponent();

            BindingContext = viewModel = new ItemsViewModel();
        }

        async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
        {
            var item = args.SelectedItem as Item;
            if (item == null)
                return;

            await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));

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

        async void AddItem_Clicked(object sender, EventArgs e)
        {
            await Navigation.PushAsync(new NewItemPage());
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            if (viewModel.Items.Count == 0)
                viewModel.LoadItemsCommand.Execute(null);
        }
    }
}

Xamlのコード説明の中でも触れましたが、ItemsPageはContentPageで作られています。よって、ContentPageクラスを継承しています(9行目)。

コンストラクタでは、表示するデータをBindingContextに設定しています(17行目)。これによりItemsPageが持つデータがセットされ、ListViewに連携されるようになります。

20行目のOnItemSelectedは、リストに表示された項目が選択された時に発生するイベントです。

項目が選択されると、26行目のNavigation.PushAsyncメソッドによって、その項目の詳細ページへと移動します。このとき引数には現在選択されている項目が渡されています。

32行目の AddItem_Clickedは、ツールバーの[Add]ボタンがタップされたときに発生するイベントで、アプリ情報を表示するページへと移動します。

37行目の OnAppearingは、アプリ内にItemsPageが表示された時に発生するイベントです。viewModdelのデータが0件の場合は、 viewModel.LoadItemsCommand.Execute(null);でデータを読み込みます。

今回は、主としてリストの表示部分について見てきました。次回は、実際のデータを作成する部分について見ていきたいと思います。

 

 

 

 

[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する2

前回の「[Xamarin.CrossPlatform] マスタ/詳細アプリのプロジェクトを理解する1」では、マスタ/詳細型アプリの要素技術について取り上げました。

今回は、実行時のアプリ画面と新規プロジェクト作成後のプロジェクト構成、共通プロジェクトのApp.xaml中身を見ていきます。

とりあえず実行してみる

新規プロジェクトを作成して実行すると、以下の画面が表示されます。
これはアプリのメインとなる画面で、項目が一覧表示されます。

メイン画面の右上にある「Add」をタップすると、以下のように項目を追加するための画面が表示されます。

画面下の「About」をタップすると、アプリの情報画面が表示されます。

上記のように、大きく3つの画面で構成されていることがわかります。

プロジェクト構成

続いてプロジェクトの構成をみてみましょう。

以下はプロジェクト名を「MasterDetailSample」として作成した例です。スクショはVS for Macのものですが、構成自体はWindows版も同じです。

ソリューションには3つのプロジェクトがあります。

  1. MasterDetailSample
    iOSとAndroidの共通プロジェクトです。
  2. MasterDetailSample.Droid
    Android用のプロジェクトです
  3. MasterDetailSample.iOS
    iOS用のプロジェクトです。

共通プロジェクトの構成

共通プロジェクトを展開すると、以下のようになります。

前回説明した通り、マスタ/詳細型はMVVMパターンを採用していますので、Models, ViewModels, Viewsというようにフォルダを分けてファイルが作成されています。Servicesについては、後ほど見ていくこととします。そのほか、App.xamlというファイルがあります。

App.xaml

App.xamlのコードは以下のようになっています。

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MasterDetailSample.App">
	<Application.Resources>
		<ResourceDictionary>
			<Color x:Key="Primary">#2196F3</Color>
			<Color x:Key="PrimaryDark">#1976D2</Color>
			<Color x:Key="Accent">#96d1ff</Color>
			<Color x:Key="LightBackgroundColor">#FAFAFA</Color>
			<Color x:Key="DarkBackgroundColor">#C0C0C0</Color>
			<Color x:Key="MediumGrayTextColor">#4d4d4d</Color>
			<Color x:Key="LightTextColor">#999999</Color>
			<Style TargetType="NavigationPage">
				<Setter Property="BarBackgroundColor" Value="{StaticResource Primary}" />
				<Setter Property="BarTextColor" Value="White" />
			</Style>
		</ResourceDictionary>
	</Application.Resources>
</Application>

<Application.Resources>や<ResourceDictionary>という名前から推測できるように、アプリケーション内で使用するリソースを定義しているファイルです。

例えば、5行目は、Primaryという名前で#2196F3の色を定義しています。このようにして定義された色は、データバインディングの機構を使用してViewから使用することができます。

<Color x:Key="Primary">#2196F3</Color>