かずきのBlog@hatena

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

ストアアプリでのマスター詳細バインディングためしてみた(エラーでないか)

ちょいとCollectionViewSourceを使ったパターンを試してみました。CollectionViewSourceに適当なデータを突っ込みます。

<!-- MainPage.xaml -->
<CollectionViewSource
    x:Name="source" />
// MainPage.xaml.cs
private ObservableCollection<Person> people = new ObservableCollection<Person>();

private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
    this.source.Source = people;
}

DataTemplateSelectorを使いたいので以下のような感じで適当なのを作りました。

using MasterDetailApp.DataModel;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MasterDetailApp.Common
{
    public class PersonDataTemplateSelector : DataTemplateSelector
    {
        public IList<DataTemplate> Templates { get; private set; }

        public PersonDataTemplateSelector()
        {
            this.Templates = new List<DataTemplate>();
        }

        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            if(item == null)
            {
                return null;
            }

            var p = (Person)item;
            return this.Templates[Math.Abs(p.Age) % this.Templates.Count];
        }
    }
}

MainPage.xamlのリソースにNull用のPersonオブジェクトとともに定義をしておきます。

<dataModel:Person x:Key="NullPerson" Name="NullPerson" Age="-1" />
<common:PersonDataTemplateSelector x:Key="personTemplateSelector">
    <common:PersonDataTemplateSelector.Templates>
        <DataTemplate>
            <Border Padding="10">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" TextWrapping="Wrap" Text="名前:"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Row="1" TextWrapping="Wrap" Text="年齢:"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Column="1" TextWrapping="Wrap" Text="{Binding Name}"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Age}"/>
                </Grid>
            </Border>
        </DataTemplate>
        <DataTemplate>
            <Border Background="Red" Padding="10">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" TextWrapping="Wrap" Text="名前:"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Row="1" TextWrapping="Wrap" Text="年齢:"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Column="1" TextWrapping="Wrap" Text="{Binding Name}"/>
                    <TextBlock Style="{StaticResource BaseTextBlockStyle}" Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Age}"/>
                </Grid>
            </Border>
        </DataTemplate>
    </common:PersonDataTemplateSelector.Templates>
</common:PersonDataTemplateSelector>

背景色なしと、背景色赤の2パターンのDataTemplateから年齢をもとに選択して返します。

んで、MasterとなるListBoxとDetailとなるContentControlをさくっと定義します。ContentControlのほうのBindingはPathにCurrentItemを指定するようにしておきました。こうすることで、現在選択中のアイテムを強制的にわたせるかな。

<ListBox 
    Grid.Row="1" 
    Margin="15"
    ItemsSource="{Binding Source={StaticResource source}}" 
    ItemTemplateSelector="{StaticResource personTemplateSelector}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <Setter Property="VerticalContentAlignment" Value="Stretch" />
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
<ContentControl 
    Grid.Column="1" 
    Grid.Row="1" 
    Margin="15"
    Content="{Binding CurrentItem, Source={StaticResource source}, TargetNullValue={StaticResource NullPerson}}" 
    ContentTemplateSelector="{StaticResource personTemplateSelector}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />

あとは、AppBarに適当にボタンをおいて要素の追加や削除や選択解除などの処理を書いてみました。

private void CreateData()
{
    // 適当にデータをつくる
    var items = Enumerable.Range(1, 20)
            .Select(i => new Person
            {
                Name = "田中" + i,
                Age = i
            });
    foreach (var item in items)
    {
        this.people.Add(item);
    }
}


private void ClearData()
{
    // データクリア
    this.people.Clear();
}

private void AddData()
{
    // 適当にデータを追加
    this.people.Add(new Person { Name = "tanaka" + this.people.Count, Age = 100 });
}

private void AppBarButton_Click(object sender, RoutedEventArgs e)
{
    this.ClearData();
}

private void AppBarButton_Click_1(object sender, RoutedEventArgs e)
{
    this.AddData();
}

private void AppBarButton_Click_2(object sender, RoutedEventArgs e)
{
    this.ClearData();
    this.CreateData();
}

private void AppBarButton_Click_3(object sender, RoutedEventArgs e)
{
    // 選択解除
    ICollectionView v = this.source.View;
    v.MoveCurrentTo(null);
}

private void AppBarButton_Click_4(object sender, RoutedEventArgs e)
{
    // 選択中の要素を削除
    if (source.View.CurrentItem == null)
    {
        return;
    }

    source.View.Remove(source.View.CurrentItem);
}

実行して動かしてみると。 起動直後。

f:id:okazuki:20131212083812p:plain

データ作ってみたところ

f:id:okazuki:20131212083927p:plain

選択してみたところ

f:id:okazuki:20131212084019p:plain

選択したものを削除したところ

f:id:okazuki:20131212084124p:plain

データを選択してからクリアしたところ

f:id:okazuki:20131212084232p:plain

ここまでの一連の操作をしたときのVSのデバッグ出力

'MasterDetailApp.exe' (CLR v4.0.30319: DefaultDomain): 'C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: DefaultDomain): 'c:\users\kazuki\documents\visual studio 2013\Projects\MasterDetailApp\MasterDetailApp\bin\Debug\AppX\MasterDetailApp.exe' が読み込まれました。シンボルが読み込まれました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.UI.Xaml.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.InteropServices.WindowsRuntime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.WindowsRuntime.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.ApplicationModel.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Threading.Tasks\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.Tasks.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.UI.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.Foundation.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.WindowsRuntime\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.WindowsRuntime.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Diagnostics.Debug\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Diagnostics.Debug.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.WindowsRuntime.UI.Xaml\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.WindowsRuntime.UI.Xaml.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.Globalization.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Collections\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Collections.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'c:\users\kazuki\documents\visual studio 2013\Projects\MasterDetailApp\MasterDetailApp\bin\Debug\AppX\GalaSoft.MvvmLight.DLL' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.ObjectModel\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.ObjectModel.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Extensions\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.Extensions.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Threading\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Threading.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\system32\WinMetadata\Windows.System.winmd' が読み込まれました。モジュールがシンボルなしでビルドされました。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Linq\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Linq.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'MasterDetailApp.exe' (CLR v4.0.30319: Immersive Application Domain): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。

あやしげなバインド系のエラーは出てなさそう。ということで、ストアアプリでマスター詳細シナリオで出てたエラーはContentControlのBindingにCurrentItem渡してやればエラーでなくなりそう・・・?