次の方法で共有


DataGridでItemsSourceのバインド先を動的に変更したい

質問

2012年2月9日木曜日 16:30

お世話になります。

VisualStudio2010 ; WPF(C#) ; MVVM にて、

(;なるべくコードビハインドを使用しない方針で作成しています)

ファイル内容を画面に表示するアプリケーションを作成しています。

画面内にDataGridが1つとラジオボタンが2つあります。

ラジオボタンで表示するファイル内容を切り替える仕様です。

ラジオボタンで表示するファイルを選択して、

DataGridに表示させたいと思い、以下の記述をしました。

<DataGrid
  ItemsSource="{Binding Path=HOGE}"/>

HOGEはViewModel内に宣言されたプロパティで、ファイル内容を;納した、

ObservableCollection<classA>を正しく表示しています。

;これを、例えば、別のラジオボタンチェックされ、表示ボタンが押下された時、

ObservableCollection<classB>に、バインド先を変更したいのですが、

どのようにすればよいでしょうか(可能なのでしょうか)。

ご存じの方がおられましたら教えてください。よろしくお願いします。

すべての返信 (7)

2012年2月9日木曜日 22:02 ✅回答済み

VMで、CollectionViewSourceを持たせて、それのViewプロパティをVMのプロパティとして公開して、DataGridとバインド。

表示データの切り替えは、VMでCollectionViewSourceのSourceプロパティを差し替えで実現出来ないでしょうか?

かずき Blog:http://d.hatena.ne.jp/okazuki/ VS 2010のデザイナでBlendのBehaviorをサポートするツール公開してます。 http://vsbehaviorsupport.codeplex.com/


2012年2月10日金曜日 0:40 ✅回答済み

ObservableCollection<classB>に、バインド先を変更したいのですが、

どのようにすればよいでしょうか(可能なのでしょうか)。

どこまで求められているのかわからないのですが、バインド先を変える必要はなく、ViewModelで単にHOGEプロパティにObservableCollection<classB>を設定すれば良いだけのような気がします。

#(追記)HOGEをobject型で公開して下さい。DataGridのAutoGenerateColumnsはTrueであることを前提としています。

★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


2012年2月10日金曜日 2:15 ✅回答済み

VMで、CollectionViewSourceを持たせて、それのViewプロパティをVMのプロパティとして公開して、DataGridとバインド。

表示データの切り替えは、VMでCollectionViewSourceのSourceプロパティを差し替えで実現出来ないでしょうか?

CollectionViewSource、面白そうですね。 後学のため、上記かずきさんの回答を参考にして少し考えてみました。何かの参考になれば幸いです。あと MVVM インフラは Livet を使ってます。CollectionViewSource に関しては以下の記事を参考にしました。

http://msdn.microsoft.com/ja-jp/library/ms752347.aspx

まずModel

using Livet;

public class ModelA : NotificationObject {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
}

public class ModelB : NotificationObject {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Telephone { get; set; }
}

次に ViewModelです。

using System.Collections.Generic;
using System.Collections.ObjectModel;
using Livet;

public class MainWindowViewModel : ViewModel {

    public MainWindowViewModel() {
        this.SourceA = new ObservableCollection<modela>();
        this.SourceA.Add(new ModelA() { Id = 0, Name = "田中", Address = "東京都" });
        this.SourceA.Add(new ModelA() { Id = 1, Name = "佐藤", Address = "埼玉県" });

        this.SourceB = new ObservableCollection<modelb>();
        this.SourceB.Add(new ModelB() { Id = 1234, Name = "日本商事株式会社", Telephone = "06xxxxxxxx" });
        this.SourceB.Add(new ModelB() { Id = 5678, Name = "有限会社佐伯物産", Telephone = "03xxxxxxxx" });
    }

    #region SourceA変更通知プロパティ
    private ObservableCollection<modela> _SourceA;

    public ObservableCollection<modela> SourceA {
        get { return _SourceA; }
        set {
            if (EqualityComparer<observablecollection<modela>>.Default.Equals(_SourceA, value))
                 return;
            _SourceA = value;
            RaisePropertyChanged("SourceA");
        }
    }
    #endregion

    #region SourceB変更通知プロパティ
    private ObservableCollection<modelb> _SourceB;

    public ObservableCollection<modelb> SourceB {
        get { return _SourceB; }
        set {
            if (EqualityComparer<observablecollection<modelb>>.Default.Equals(_SourceB, value))
                 return;
            _SourceB = value;
            RaisePropertyChanged("SourceB");
        }
    }
    #endregion

    #region IsA変更通知プロパティ
    private bool _IsA;

    public bool IsA {
        get { return _IsA; }
        set { 
            if (EqualityComparer<bool>.Default.Equals(_IsA, value))
                 return;
            _IsA = value;
            RaisePropertyChanged("IsA");
        }
    }
    #endregion

    #region IsB変更通知プロパティ
    private string _IsB;

    public string IsB {
        get { return _IsB; }
        set { 
            if (EqualityComparer<string>.Default.Equals(_IsB, value))
                 return;
            _IsB = value;
            RaisePropertyChanged("IsB");
        }
    }
    #endregion
}</string></bool></observablecollection<modelb></modelb></modelb></observablecollection<modela></modela></modela></modelb></modela>

最後に View です。

<window Height="350" Width="525" title="MainWindow" x:Class="LivetWPFApplication1.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" xmlns:v="clr-namespace:LivetWPFApplication1.Views" xmlns:vm="clr-namespace:LivetWPFApplication1.ViewModels" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <window.datacontext>
        <vm:mainwindowviewmodel></vm:mainwindowviewmodel>
    </window.datacontext>
    
    <window.resources>
        <collectionviewsource Source="{Binding SourceA}" x:Key="SourceA">
        <collectionviewsource Source="{Binding SourceB}" x:Key="SourceB">
    </collectionviewsource></collectionviewsource></window.resources>
    
    <grid>
        <radiobutton Content="IsA" Height="26" HorizontalAlignment="Left" IsChecked="{Binding IsA}" Margin="12,23,0,0" Name="radioButton1" VerticalAlignment="Top" Width="60"></radiobutton>
        <radiobutton Content="IsB" Height="26" HorizontalAlignment="Left" IsChecked="{Binding IsB}" Margin="78,23,0,0" Name="radioButton2" VerticalAlignment="Top" Width="60"></radiobutton>

        <datagrid AutoGenerateColumns="True" Height="236" HorizontalAlignment="Left" Margin="12,63,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="479">
            <datagrid.style>
                <style TargetType="DataGrid">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsA}" Value="True" >
                            <Setter Property="ItemsSource" Value="{Binding Source={StaticResource SourceA}}" ></Setter>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding IsB}" Value="True" >
                            <Setter Property="ItemsSource" Value="{Binding Source={StaticResource SourceB}}" ></Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </style>
            </datagrid.style>
        </datagrid>
    </grid>
</window>

するとこうなる。

もっとスマートな方法がありそうですが、あくまで参考まで。あしからず。

ひらぽん http://d.hatena.ne.jp/hilapon/


2012年2月10日金曜日 5:03 ✅回答済み

後から考えたら、CollectionViewSource 使わなくても DataTrigger で ItemsSource 切り替えるだけですみますね。以下、変更した XAML です。

<Window x:Class="LivetWPFApplication2.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:LivetWPFApplication2.Views"
        xmlns:vm="clr-namespace:LivetWPFApplication2.ViewModels"
        Title="MainWindow" Height="222" Width="525">

    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <Grid>
        <RadioButton Content="IsA" Height="26" HorizontalAlignment="Left" 
                     Margin="12,23,0,0" Name="radioButton1" VerticalAlignment="Top" Width="60"
                     IsChecked="{Binding IsA}" />
        <RadioButton Content="IsB" Height="26" HorizontalAlignment="Left" 
                     Margin="78,23,0,0" Name="radioButton2" VerticalAlignment="Top" Width="60"
                     IsChecked="{Binding IsB}" />

        <DataGrid AutoGenerateColumns="True" Height="106" HorizontalAlignment="Left" 
                  Margin="12,63,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="479" >
            <DataGrid.Style>
                <Style TargetType="DataGrid" >
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsA}" Value="True" >
                            <Setter Property="ItemsSource" Value="{Binding SourceA}" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding IsB}" Value="True" >
                            <Setter Property="ItemsSource" Value="{Binding SourceB}" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Style>
        </DataGrid>
    </Grid>
</Window>

でも trapemiya さんの方法がよりシンプルですね(汗

ひらぽん http://d.hatena.ne.jp/hilapon/


2012年2月10日金曜日 5:15 ✅回答済み

trapemiya さんの回答案だとこうなる・・・確かにシンプルですね。

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Hoge}" />
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Livet;

public class MainWindowViewModel : ViewModel {

    private ObservableCollection<ModelA> _SourceA;
    private ObservableCollection<ModelB> _SourceB;

    public MainWindowViewModel() {
        _SourceA = new ObservableCollection<ModelA>();
        _SourceA.Add(new ModelA() { Id = 0, Name = "田中", Address = "東京都" });
        _SourceA.Add(new ModelA() { Id = 1, Name = "佐藤", Address = "埼玉県" });

        _SourceB = new ObservableCollection<ModelB>();
        _SourceB.Add(new ModelB() { Id = 1234, Name = "日本商事株式会社", Telephone = "06xxxxxxxx" });
        _SourceB.Add(new ModelB() { Id = 5678, Name = "有限会社佐伯物産", Telephone = "03xxxxxxxx" });

        this.IsA = true;
    }

    #region Hoge変更通知プロパティ
    private object _Hoge;

    public object Hoge {
        get { return _Hoge; }
        set { 
            if (EqualityComparer<object>.Default.Equals(_Hoge, value))
                return;
            _Hoge = value;
            RaisePropertyChanged("Hoge");
        }
    }
    #endregion

    #region IsA変更通知プロパティ
    private bool _IsA;

    public bool IsA {
        get { return _IsA; }
        set { 
            if (EqualityComparer<bool>.Default.Equals(_IsA, value))
                 return;
            _IsA = value;
            RaisePropertyChanged("IsA");

            if (_IsA) {
                this.Hoge = _SourceA;
            }
        }
    }
    #endregion

    #region IsB変更通知プロパティ
    private bool _IsB;

    public bool IsB {
        get { return _IsB; }
        set {
            if (EqualityComparer<bool>.Default.Equals(_IsB, value))
                 return;
            _IsB = value;
            RaisePropertyChanged("IsB");

            if (_IsB) {
                this.Hoge = _SourceB;
            }
        }
    }
    #endregion
}

ひらぽん http://d.hatena.ne.jp/hilapon/


2012年2月12日日曜日 3:51 ✅回答済み

私のイメージだとこんな感じですね~。ひらぽんさんがLivetで書かれてるので、私もLivetで。

まず、ViewModel側。

namespace DataGridSample.ViewModels
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows.Data;
    using Livet;
    using Livet.Commands;

    public class MainWindowViewModel : ViewModel
    {
        #region ChangeDataCommand
        private ViewModelCommand _ChangeDataCommand;

        /// <summary>
        /// 表示データの切り替えを行うコマンド
        /// </summary>
        public ViewModelCommand ChangeDataCommand
        {
            get
            {
                if (_ChangeDataCommand == null)
                {
                    _ChangeDataCommand = new ViewModelCommand(ChangeData);
                }
                return _ChangeDataCommand;
            }
        }

        public void ChangeData()
        {
            // CheckBoxの状態に応じてCollectionViewSourceのSourceを変える
            if (this.IsA)
            {
                this.DataGridDataSource.Source = this.modelAItems;
            }
            else if (this.IsB)
            {
                this.DataGridDataSource.Source = this.modelBItems;
            }
        }
        #endregion


        #region A変更通知プロパティ
        private bool _IsA = true;

        public bool IsA
        {
            get
            { return _IsA; }
            set
            { 
                if (EqualityComparer<bool>.Default.Equals(_IsA, value))
                    return;
                _IsA = value;
                RaisePropertyChanged("IsA");
            }
        }
        #endregion


        #region IsB変更通知プロパティ
        private bool _IsB;

        public bool IsB
        {
            get
            { return _IsB; }
            set
            { 
                if (EqualityComparer<bool>.Default.Equals(_IsB, value))
                    return;
                _IsB = value;
                RaisePropertyChanged("IsB");
            }
        }
        #endregion

        // 表示用のでーた
        private ObservableCollection<ModelA> modelAItems = new ObservableCollection<ModelA>(ModelA.Load());
        private ObservableCollection<ModelB> modelBItems = new ObservableCollection<ModelB>(ModelB.Load());

        // DataGridにバインドするためのCollectionViewSource
        public CollectionViewSource DataGridDataSource { get; private set; }

        public MainWindowViewModel()
        {
            // 初期状態はModelAを表示するようにする
            this.DataGridDataSource = new CollectionViewSource { Source = modelAItems };
        }
    }

    // 表示用のダミーデータ
    public class ModelA
    {
        public string ModelAProperty { get; set; }

        public static IEnumerable<ModelA> Load()
        {
            return Enumerable.Range(1, 10).Select(i => new ModelA { ModelAProperty = "ModelA: " ; i });
        }
    }

    public class ModelB
    {
        public string ModelBProperty { get; set; }

        public static IEnumerable<ModelB> Load()
        {
            return Enumerable.Range(1, 10).Select(i => new ModelB { ModelBProperty = "ModelB: " ; i });
        }
    }
}

そして、XAML側。DataGridのItemsSourceのBindingでUpdateSourceTriggerにPropertyChangedつけてないと表示が切り替わらないので注意って感じです。

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:DataGridSample.Views"
        xmlns:vm="clr-namespace:DataGridSample.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="DataGridSample.Views.MainWindow"
        Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignData /SampleData/MainWindowViewModelSampleData.xaml}">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <StackPanel x:Name="stackPanel1" Orientation="Horizontal" HorizontalAlignment="Right">
            <RadioButton Content="A" x:Name="radioButton1" Margin="2.5" Padding="0" IsChecked="{Binding IsA}" />
            <RadioButton Content="B" x:Name="radioButton2" Margin="2.5" Padding="0" IsChecked="{Binding IsB}" />
            <Button Content="ChangeData" Margin="0,0,0,8" MinWidth="75" Command="{Binding ChangeDataCommand}"/>
        </StackPanel>
        <DataGrid Grid.Row="1" x:Name="dataGrid1" ItemsSource="{Binding Mode=OneWay, Path=DataGridDataSource.View, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</Window>

かずき Blog:http://d.hatena.ne.jp/okazuki/ VS 2010のデザイナでBlendのBehaviorをサポートするツール公開してます。 http://vsbehaviorsupport.codeplex.com/


2012年2月13日月曜日 1:37

ふむ・・・さすが、かずき_okazukiさん、素晴らしいサンプルありがとうございます。
Enumerable.Range の使い方、たいへん参考になります。(今までどう使うか知らなかった(汗))

ちなみにここで CollectionViewSource を使っておられますが、やはり DataGrid とのバインドには CollectionViewSource を使った方がいいとの見解ですか?

ひらぽん http://d.hatena.ne.jp/hilapon/