2009-05-17 10 views
3

私は2つのObservableCollection < TimeValue>を持つクラスを持っています。ここで、TimeValueは(INotifyPropertyChangedを介して)変更通知付きのカスタムDateTime/Valueペアです。私はこれらの目標と実績と呼ぶ。WPF DataGrid - MultiBindingでTimeSeriesを組み合わせると、変更通知が失われます。どうして?

これらをグラフにバインドすると、すべてが正常に動作し、2つのLineSeriesが得られます。私がDataGridにバインドすると、 "Date"の列と "Value"の列が正しく動作します。私は必要なTwoWayバインディングを取得することさえできます。

ただし、「日付」列を持つDataGridと、それぞれの対象と実際の列が必要です。問題は、範囲内のすべての日付をリストする必要があるのに対して、これらの日付の一部はターゲット、実績、またはその両方に対応する値を持たない可能性があることです。

したがって、ターゲットと実績を入力とし、いずれかのオリジナルが値を持たない場合はいつでも、null値を組み合わせたTimeSeriesCを出力するマルチバインディングを行うことにしました。

これは機能しますが、基礎となるデータの変更には反応しません。

これは、(1つのObservableCollectionに結合する)正常に動作します:

<ctrls:DataGrid Grid.Row="1" Height="400" AutoGenerateColumns="False" CanUserDeleteRows="False" SelectionUnit="Cell"> 
<ctrls:DataGrid.ItemsSource> 
    <Binding Path="Targets"/> 
    <!--<MultiBinding Converter="{StaticResource TargetActualListConverter}"> 
     <Binding Path="Targets"/> 
     <Binding Path="Actuals"/> 
    </MultiBinding>--> 
</ctrls:DataGrid.ItemsSource> 
<ctrls:DataGrid.Columns> 
    <ctrls:DataGridTextColumn Header="Date" Binding="{Binding Date,StringFormat={}{0:ddd, MMM d}}"/> 
    <ctrls:DataGridTextColumn Header="Target" Binding="{Binding Value}"/> 
    <!--<ctrls:DataGridTextColumn Header="Target" Binding="{Binding Value[0]}"/> 
    <ctrls:DataGridTextColumn Header="Actual" Binding="{Binding Value[1]}"/>--> 
</ctrls:DataGrid.Columns> 

これは動作しますが、最初に初期化する場合にのみ。通知を変更する応答:

<ctrls:DataGrid Grid.Row="1" Height="400" AutoGenerateColumns="False" CanUserDeleteRows="False" SelectionUnit="Cell"> 
<ctrls:DataGrid.ItemsSource> 
    <!--<Binding Path="Targets"/>--> 
    <MultiBinding Converter="{StaticResource TargetActualListConverter}"> 
     <Binding Path="Targets"/> 
     <Binding Path="Actuals"/> 
    </MultiBinding> 
</ctrls:DataGrid.ItemsSource> 
<ctrls:DataGrid.Columns> 
    <ctrls:DataGridTextColumn Header="Date" Binding="{Binding Date,StringFormat={}{0:ddd, MMM d}}"/> 
    <!--<ctrls:DataGridTextColumn Header="Target" Binding="{Binding Value}"/>--> 
    <ctrls:DataGridTextColumn Header="Target" Binding="{Binding Value[0]}"/> 
    <ctrls:DataGridTextColumn Header="Actual" Binding="{Binding Value[1]}"/> 
</ctrls:DataGrid.Columns> 

そして、ここでは私のIMultiValueConverterである:それは値を表示するので

class TargetActualListConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     TimeSeries<double> Targets = values[0] as TimeSeries<double>; 
     TimeSeries<double> Actuals = values[1] as TimeSeries<double>; 
     DateTime[] range = TimeSeries<double>.GetDateRange(Targets, Actuals);//Get min and max Dates 
     int count = (range[1] - range[0]).Days;//total number of days 
     DateTime currDate = new DateTime(); 
     TimeSeries<double?[]> combined = new TimeSeries<double?[]>(); 
     for (int i = 0; i < count; i++) 
     { 
      currDate = range[0].AddDays(i); 
      double?[] vals = { Targets.Dates.Contains(currDate) ? (double?)Targets.GetValueByDate(currDate) : null, Actuals.Dates.Contains(currDate) ? (double?)Actuals.GetValueByDate(currDate) : null }; 
      combined.Add(new TimeValue<double?[]>(currDate, vals)); 
     } 
     return combined; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) 
    { 
     TimeSeries<double?[]> combined = value as TimeSeries<double?[]>; 
     TimeSeries<double> Targets = new TimeSeries<double>(); 
     TimeSeries<double> Actuals = new TimeSeries<double>(); 

     foreach (TimeValue<double?[]> tv in combined) 
     { 
      if(tv.Value[0]!=null) 
       Targets.Add(new TimeValue<double>(tv.Date,(double)tv.Value[0])); 
      if (tv.Value[1] != null) 
       Actuals.Add(new TimeValue<double>(tv.Date, (double)tv.Value[1])); 
     } 
     TimeSeries<double>[] result = { Targets, Actuals }; 
     return result; 

    } 
} 

私は、あまりにも遠くなることはできません。

私は間違っていますか? または、これを行う簡単な方法がありますか?

ありがとうございます!

答えて

2

これはコンバータによって発生したようです。 ObservableCollectionはINotifyCollectionChangedを実装しています。これは、コレクションの変更(追加/削除/置換/移動/リセット)が発生したときにUIに通知します。これらはコレクションの内容ではなく、コレクションに対するすべての変更です。したがって、前回のアップデートでは、クラスがINotifyPropertyChangedを実装していたためです。 MultiCoverterは新しいオブジェクトの新しいコレクションを返すので、初期コレクションのデータは、元のオブジェクトに通知するためのバインディングがないため、これらに伝播しません。

まず、CompositeCollection要素を見て、それがあなたのニーズに合っているかどうかを確認することをお勧めします。

代わりにあなたがいるとしてのItemsSourceを設定するので、あなたのようなもので、元のオブジェクトを維持することができます:

<ctrls:DataGrid.ItemsSource> 
    <CompositeCollection> 
      <CollectionContainer Collection="{Binding Targets}" /> 
      <CollectionContainer Collection="{Binding Actuals}" /> 
     </CompositeCollection> 
</ctrls:DataGrid.ItemsSource> 

(I「の基になるデータの変更に応答しない」と仮定していますが変更することを指し、私が間違っていれば、コレクションを変更しない値が私に知らせてください、そして、私はそれをより深く見ていきます。それが代替機能しないこと場合)

編集追加
は、ターゲットと実際のコレクションの両方をラップする新しいクラスを作成することです。次に、これらのラッパーを使用して1つのObservableCollectionを作成できます。これは、実際には、ValueConverterを使用するか、CompositeCollectionを使用するより優れた方法です。どちらの場合でも、元々存在していた機能の一部が失われています。値コンバーターを使用してコレクションを再作成すると、元のオブジェクトに直接バインドされなくなり、プロパティ通知が失われる可能性があります。 CompositeCollectionを使用することで、操作の対象となるコレクションを知る必要があるため、追加/削除/移動などで反復処理または変更できる単一のコレクションはなくなりました。

このタイプのラップ機能は、WPFで非常に便利であり、M-V-VMデザインパターンの一部であるViewModelの非常に単純化されたバージョンです。 INotifyPropertyChangedまたはIDataErrorInfoを追加するための基底クラスへのアクセス権がなく、基になるモデルに状態や対話などの追加機能を追加するときにも使用できます。

ここでは、両方の初期クラスが同じNameプロパティを持ち、それらの間で共有されていないINotifyPropertyChangedを実装していないこの機能を示す短い例を示します。

public partial class Window1 : Window 
{ 
    public Window1() 
    { 
     InitializeComponent(); 

     Foo foo1 = new Foo { ID = 1, Name = "Foo1" }; 
     Foo foo3 = new Foo { ID = 3, Name = "Foo3" }; 
     Foo foo5 = new Foo { ID = 5, Name = "Foo5" }; 
     Bar bar1 = new Bar { ID = 1, Name = "Bar1" }; 
     Bar bar2 = new Bar { ID = 2, Name = "Bar2" }; 
     Bar bar4 = new Bar { ID = 4, Name = "Bar4" }; 

     ObservableCollection<FooBarViewModel> fooBar = new ObservableCollection<FooBarViewModel>(); 
     fooBar.Add(new FooBarViewModel(foo1, bar1)); 
     fooBar.Add(new FooBarViewModel(bar2)); 
     fooBar.Add(new FooBarViewModel(foo3)); 
     fooBar.Add(new FooBarViewModel(bar4)); 
     fooBar.Add(new FooBarViewModel(foo5)); 

     this.DataContext = fooBar; 
    } 
} 

public class Foo 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

public class Bar 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

public class FooBarViewModel : INotifyPropertyChanged 
{ 
    public Foo WrappedFoo { get; private set; } 
    public Bar WrappedBar { get; private set; } 

    public int ID 
    { 
     get 
     { 
      if (WrappedFoo != null) 
      { return WrappedFoo.ID; } 
      else if (WrappedBar != null) 
      { return WrappedBar.ID; } 
      else 
      { return -1; } 
     } 
     set 
     { 
      if (WrappedFoo != null) 
      { WrappedFoo.ID = value; } 
      if (WrappedBar != null) 
      { WrappedBar.ID = value; } 

      this.NotifyPropertyChanged("ID"); 
     } 
    } 

    public string BarName 
    { 
     get 
     { 
      return WrappedBar.Name; 
     } 
     set 
     { 
      WrappedBar.Name = value; 
      this.NotifyPropertyChanged("BarName"); 
     } 
    } 

    public string FooName 
    { 
     get 
     { 
      return WrappedFoo.Name; 
     } 
     set 
     { 
      WrappedFoo.Name = value; 
      this.NotifyPropertyChanged("FooName"); 
     } 
    } 

    public FooBarViewModel(Foo foo) 
     : this(foo, null) { } 
    public FooBarViewModel(Bar bar) 
     : this(null, bar) { } 
    public FooBarViewModel(Foo foo, Bar bar) 
    { 
     WrappedFoo = foo; 
     WrappedBar = bar; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    private void NotifyPropertyChanged(String info) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     } 
    } 
} 

し、ウィンドウ内:

<ListView ItemsSource="{Binding}"> 
    <ListView.View> 
     <GridView> 
      <GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}"/> 
      <GridViewColumn Header="Foo Name" DisplayMemberBinding="{Binding FooName}"/> 
      <GridViewColumn Header="Bar Name" DisplayMemberBinding="{Binding BarName}"/> 
     </GridView> 
    </ListView.View> 
</ListView> 
+1

おかげでrmoore。残念ながら、私が持っているセットアップは、CollectionChangedイベントに応答しません。私はCompositeCollectionsを試しましたが、表示したいコレクションは2つの入力と同じサイズではないので、どのように動作させるのか分かりません。奇妙なことに、Convertメソッドは最初の表示で1回だけ(値ごとに)呼び出され、再び表示されません。 – AdrianoFerrari

+1

その場合、もっと知ることなくこれを解決する最も簡単な方法は、TargetやActualをマージすることができる何らかの種類のラッパークラスを作成することです。次に、これらのラッパーのObservableCollectionを1つ作成すると、バインドするほうがずっと簡単になります。 – rmoore

+0

私はそうする必要があると思います。私が意図したようにすることができれば、私はここに戻ってきます。なぜなら、それはより簡単だったからです。 ありがとうございましたrmoore。これと答えて、あなたにポイントを与えてもらえますか? – AdrianoFerrari

関連する問題