かずきのBlog@hatena

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

DataGridComboBoxColumnのItemsSourceのバインド方法

久しぶりのWPFネタです。小ネタ。

DataGridComboBoxColumnクラスを使うと、簡単にDataGridにComboBoxを設定できます。しかし、DataGridComboBoxColumnクラスのItemsSourceプロパティをBindingしようとすると、BindingのSourceがWindowのDataContextではなく、DataGridのItemsSourceに設定されたコレクションの行に該当するオブジェクトがSourceとして使用されます。そのため、WindowのDataContextが持っているコレクションを表示しようとするだけでも、Bindingが多少複雑になります。

AncestorTypeなどを使って親要素を辿ったりということを考えがちですが、もっと簡単なやり方があります。WindowのResourcesにCollectionViewSourceを使って、DataContextのコレクションをStaticResourceで参照出来るようにして、DataGridComboBoxColumnのItemsSourceにバインドする方法です。コード例を以下に示します。

まずDataGridの行に該当するクラスとして、以下のPersonクラスを定義します。このPersonクラスのParentIdをComboBoxから選択するという機能を実装します。

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
    {
        field = value;
        var h = this.PropertyChanged;
        if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
    }

    private string id;

    public string Id
    {
        get { return this.id; }
        set { this.SetProperty(ref this.id, value); }
    }

    private string name;

    public string Name
    {
        get { return this.name; }
        set { this.SetProperty(ref this.name, value); }
    }

    private string parentId;

    public string ParentId
    {
        get { return this.parentId; }
        set { this.SetProperty(ref this.parentId, value); }
    }

    public Person()
    {
        this.Id = Guid.NewGuid().ToString();
    }
}

続けて、MainWindowのViewModelを作成します。今回は以下のようなPersonクラスのコレクションを持っただけのシンプルなものになります。

public class MainWindowViewModel
{
    public ObservableCollection<Person> People { get; private set; }

    public MainWindowViewModel()
    {
        this.People = new ObservableCollection<Person>(
            Enumerable.Range(1, 100).Select(x => new Person { Name = "okazuki" + x }));
    }
}

このクラスをMainWindowのDataContextに設定して、DataGridに表示します。そして、ParentIdプロパティをPeopleコレクションの中から選択出来るようにします。XAMLは以下のようになります。

<Window x:Class="MVVMDataGridComboboxColumnSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:MVVMDataGridComboboxColumnSample"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowViewModel />
    </Window.DataContext>
    <Window.Resources>
        <!-- CollectionViewSourceで参照出来るようにしておいて -->
        <CollectionViewSource
            x:Key="PeopleSource"
            Source="{Binding People}" />
    </Window.Resources>
    <Grid>
        <DataGrid
            AutoGenerateColumns="False"
            ItemsSource="{Binding People}">
            <DataGrid.Columns>
                <DataGridTextColumn
                    Header="名前"
                    Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <!-- DataGridComboBoxColumnのItemsSourceで使用する -->
                <DataGridComboBoxColumn
                    Header="親"
                    SelectedValuePath="Id"
                    DisplayMemberPath="Name" 
                    ItemsSource="{Binding Source={StaticResource PeopleSource}}" 
                    SelectedValueBinding="{Binding ParentId}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

WindowのResourcesにPeopleをCollectionViewSourceとして登録しているため、簡単にDataGridComboBoxColumnのItemsSourceが設定できています。実行結果を以下に示します。

f:id:okazuki:20150319203213p:plain