Xamarin.Forms のバインド可能なレイアウト

バインド可能なレイアウトを使用すると、Layout<T> クラスから派生した任意のレイアウト クラスで、項目のコレクションにバインドしてコンテンツを生成すること、また、オプションで DataTemplate を使って各項目の外観を設定することができます。 バインド可能なレイアウトは BindableLayout によって提供されます。このクラスは、次の添付プロパティを公開します。

  • ItemsSource: レイアウトによって表示される IEnumerable 項目のコレクションを指定します。
  • ItemTemplate: レイアウトで表示される項目のコレクション内の、各項目に適用する DataTemplate を指定します。
  • ItemTemplateSelector: 実行時に項目の DataTemplate を選択するために使用する DataTemplateSelector を指定します。

Note

たとえば、ItemTemplateItemTemplateSelector の両方のプロパティを設定した場合、ItemTemplate プロパティが優先されます。

さらに、BindableLayout クラスは次のバインド可能なプロパティを公開します。

  • EmptyView: ItemsSource プロパティが null の場合、または ItemsSource プロパティで指定されたコレクションが null または空の場合、表示される string またはビューを指定します。 既定値は null です。
  • EmptyViewTemplate: ItemsSource プロパティが null の場合、または ItemsSource プロパティで指定されたコレクションが null または空の場合、表示される DataTemplate を指定します。 既定値は null です。

Note

たとえば、EmptyViewEmptyViewTemplate の両方のプロパティを設定した場合、EmptyViewTemplate プロパティが優先されます。

これらのプロパティはすべて、AbsoluteLayoutFlexLayoutGridRelativeLayoutStackLayout クラスにアッタッチできます。これらはすべて Layout<T> クラスから派生します。

Layout<T> クラスは Children コレクションを公開しており、それにレイアウトの子要素が追加されます。 BindableLayout.ItemsSource プロパティが項目のコレクションに設定され、Layout<T>-derived クラスにアタッチされると、コレクション内の各項目がレイアウトによって表示されるように Layout<T>.Children コレクションに追加されます。 その後、基になるコレクションが変更されると、Layout<T> から派生したクラスによって子のビューが更新されます。 Xamarin.Forms レイアウト サイクルの詳細については、「カスタム レイアウトの作成」を参照してください。

バインド可能なレイアウトは、表示する項目のコレクションが小さく、スクロールと選択が必要ない場合にのみ使用してください。 ScrollView ではスクロールはバインド可能なレイアウトをラップすることによって提供できますが、バインド可能なレイアウトには UI 仮想化がないため、これはお勧めしません。 スクロールが必要な場合は、UI 仮想化を含むスクロール可能なビュー (CollectionView または ListView など) を使用する必要があります。 この推奨事項を守らないと、パフォーマンスの問題が発生する可能性があります。

重要

バインド可能なレイアウトを、Layout<T> クラスから派生するレイアウト クラスにアタッチすることは技術的には可能ですが、特に、AbsoluteLayoutGridRelativeLayout クラスに対しては、そうすることが必ずしも実用的であるとは限りません。 たとえば、バインド可能なレイアウトを使用して Grid のデータのコレクションを表示するシナリオを考えてみましょう。この場合、コレクション内の各項目は複数のプロパティを含むオブジェクトです。 Grid の各行にはコレクションのオブジェクトが表示され、Grid の各列にはオブジェクトのプロパティの 1 つが表示されます。 バインド可能なレイアウトの DataTemplate には 1 つのオブジェクトしか含めることができないため、そのオブジェクトは、特定の Grid 列にオブジェクトのプロパティの 1 つをそれぞれ表示する複数のビューを含むレイアウト クラスである必要があります。 このシナリオはバインド可能なレイアウトで実現できますが、バインドされたコレクション内の各アイテムの子 Grid を含む親 Grid が作成されることになります。これは、Grid レイアウトの非常に非効率的で問題のある使用方法です。

バインド可能なレイアウトへのデータの設定

バインド可能なレイアウトには、ItemsSource プロパティを IEnumerable を実装するコレクションに設定し、Layout<T> 派生クラスにアタッチすることでデータが設定されます。

<Grid BindableLayout.ItemsSource="{Binding Items}" />

同等の C# コードを次に示します。

IEnumerable<string> items = ...;
var grid = new Grid();
BindableLayout.SetItemsSource(grid, items);

BindableLayout.ItemsSource 添付プロパティがレイアウトに設定されているが、BindableLayout.ItemTemplate 添付プロパティが設定されていない場合、IEnumerable コレクション内のすべての項目は、BindableLayout クラスによって作成された Label によって表示されます。

項目の外観を定義

バインド可能なレイアウト内の各項目の外観は、添付プロパティ DataTemplateBindableLayout.ItemTemplate に設定することで定義できます。

<StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"
             Orientation="Horizontal"
             ...>
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <controls:CircleImage Source="{Binding}"
                                  Aspect="AspectFill"
                                  WidthRequest="44"
                                  HeightRequest="44"
                                  ... />
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

同等の C# コードを次に示します。

DataTemplate circleImageTemplate = ...;
var stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, circleImageTemplate);

この例では、TopFollowers コレクション内のすべての項目は、DataTemplate で定義されている CircleImage ビューによって表示されます。

DataTemplate を使用したバインド可能なレイアウト

データ テンプレートの詳細については、「Xamarin.Forms のデータ テンプレート」を参照してください。

実行時にアイテムの外観を選択

バインド可能なレイアウト内の各項目の外観は、BindableLayout.ItemTemplateSelector 添付プロパティを DataTemplateSelector に設定することで、項目の値に基づいて実行時に選択できます。

<FlexLayout BindableLayout.ItemsSource="{Binding User.FavoriteTech}"
            BindableLayout.ItemTemplateSelector="{StaticResource TechItemTemplateSelector}"
            ... />

同等の C# コードを次に示します。

DataTemplateSelector dataTemplateSelector = new TechItemTemplateSelector { ... };
var flexLayout = new FlexLayout();
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);

サンプル アプリケーションで使用されている DataTemplateSelector を次の例に示します。

public class TechItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultTemplate { get; set; }
    public DataTemplate XamarinFormsTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        return (string)item == "Xamarin.Forms" ? XamarinFormsTemplate : DefaultTemplate;
    }
}

TechItemTemplateSelector クラスは、異なるデータ テンプレートに設定される DefaultTemplate および XamarinFormsTemplate DataTemplate プロパティを定義します。 OnSelectTemplate メソッドは、項目が "Xamarin.Forms" と等しい場合に XamarinFormsTemplate を返します。これは、横にハートが付いた濃い赤色の項目を表示します。 項目が "Xamarin.Forms" と等しくない場合、OnSelectTemplate メソッドは DefaultTemplate を返します。これは、Label の既定の色を使用して項目を表示します。

DataTemplateSelector を使用したバインド可能なレイアウト

データ テンプレート セレクターの詳細については、「Xamarin.Forms DataTemplateSelector の作成」を参照してください。

データが使用できないときに文字列を表示

EmptyView プロパティは文字列に設定できます。これは、ItemsSource プロパティが null の場合、または ItemsSource プロパティで指定されたコレクションが null または空の場合、Label によって表示されます。 次の XAML は、このシナリオの例を示しています。

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}"
             BindableLayout.EmptyView="No achievements">
    ...
</StackLayout>

その結果、データ バインドされたコレクションが null の場合、EmptyView プロパティ値として設定された文字列が表示されます。

iOS および Android でのバインド可能なレイアウト文字列の空のビューのスクリーンショット

データが使用できない場合にビューを表示する

EmptyView プロパティはビューに設定できます。このビューは、ItemsSource プロパティが null の場合、または ItemsSource プロパティで指定されたコレクションが null または空の場合に表示されます。 単一のビューでも、複数の子ビューを含むビューでも表示できます。 次の XAML の例は、複数の子ビューを含むビューに設定された EmptyView プロパティを示しています。

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyView>
        <StackLayout>
            <Label Text="None."
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
            <Label Text="Try harder and return later?"
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
        </StackLayout>
    </BindableLayout.EmptyView>
    ...
</StackLayout>

その結果、データ バインドされたコレクションが null の場合、StackLayout とその子ビューが表示されます。

iOS および Android 上の複数のビューを含むバインド可能なレイアウト空のビューのスクリーンショット

同様に、EmptyViewTemplateDataTemplate に設定できます。これは、ItemsSource プロパティが null の場合、または ItemsSource プロパティで指定されたコレクションが null または空の場合に表示されます。 DataTemplate には、単一のビュー、または複数の子ビューを含むビューを含めることができます。 さらに、EmptyViewTemplateBindingContext は、BindableLayoutBindingContext から継承されます。 次の XAML の例は、単一のビューを含む DataTemplate に設定された EmptyViewTemplate プロパティを示しています。

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyViewTemplate>
        <DataTemplate>
            <Label Text="{Binding Source={x:Reference usernameLabel}, Path=Text, StringFormat='{0} has no achievements.'}" />
        </DataTemplate>
    </BindableLayout.EmptyViewTemplate>
    ...
</StackLayout>

その結果、データ バインドされたコレクションが null の場合、DataTemplate 内の Label が表示されます。

iOS および Android 上のバインド可能なレイアウトの空のビュー テンプレートのスクリーンショット

Note

EmptyViewTemplate プロパティは DataTemplateSelector を介して設定できません。

実行時に EmptyView を選択する

データを使用できない場合に EmptyView として表示されるビューは、ResourceDictionary 内の ContentView オブジェクトとして定義できます。 EmptyView プロパティは、実行時にビジネス ロジックに基づいて特定の ContentView に設定できます。 次の XAML は、このシナリオの例を示しています。

<ContentPage ...>
    <ContentPage.Resources>
        ...    
        <ContentView x:Key="BasicEmptyView">
            <StackLayout>
                <Label Text="No achievements."
                       FontSize="14" />
            </StackLayout>
        </ContentView>
        <ContentView x:Key="AdvancedEmptyView">
            <StackLayout>
                <Label Text="None."
                       FontAttributes="Italic"
                       FontSize="14" />
                <Label Text="Try harder and return later?"
                       FontAttributes="Italic"
                       FontSize="14" />
            </StackLayout>
        </ContentView>
    </ContentPage.Resources>

    <StackLayout>
        ...
        <Switch Toggled="OnEmptyViewSwitchToggled" />

        <StackLayout x:Name="stackLayout"
                     BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
            ...
        </StackLayout>
    </StackLayout>
</ContentPage>

XAML では、ページレベル ResourceDictionary に 2 つの ContentView オブジェクトが定義されており、Switch オブジェクトは、どの ContentView オブジェクトを EmptyView プロパティ値として設定するかを制御します。 Switch が切り替わると、OnEmptyViewSwitchToggled イベント ハンドラーは ToggleEmptyView メソッドを実行します。

void ToggleEmptyView(bool isToggled)
{
    object view = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
    BindableLayout.SetEmptyView(stackLayout, view);
}

ToggleEmptyView メソッドは、Switch.IsToggled プロパティの値に基づいて、stackLayout オブジェクトの EmptyView プロパティを、ResourceDictionary に格納されている 2 つの ContentView オブジェクトのいずれかに設定します。 次に、データ バインド コレクションが null の場合、EmptyView プロパティとして設定された ContentView オブジェクトが表示されます。

iOS および Android での空のビューの実行時における選択肢のスクリーンショット