かずきのBlog@hatena

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

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