2009-05-17 1 views
3

ObservableCollection 두 개가있는 클래스가 있습니다. 여기서 TimeValue는 (INotifyPropertyChanged를 통해) 변경 알림이있는 사용자 지정 DateTime/Value 쌍인 TimeValue>입니다. 나는이 표적과 실제를 부른다.WPF DataGrid - MultiBinding이있는 TimeSeries 결합, 변경 알림을 잃습니다. 왜?

차트에 이들을 바인딩하면 모든 것이 완벽하게 작동하고 두 개의 LineSeries를 얻게됩니다. DataGrid에 바인딩 할 때 "Date"열과 "Value"열이 함께 있으면 완벽하게 다시 작동합니다. 나는 심지어 내가 필요로하는 TwoWay 바인딩을 얻는다.

그러나 "대상"과 "실제"에 대해 각각 "날짜"열과 열을 가진 DataGrid가 있어야합니다. 문제는 범위의 모든 날짜를 나열해야하지만 일부 날짜는 대상, 실제 또는 둘 다에서 해당 값을 갖지 않을 수 있다는 것입니다.

그래서 Target과 Actuals를 입력으로 사용하고 원본 중 하나에 값이없는 경우 null 값이 결합 된 TimeSeriesC를 출력하는 MultiBinding을 수행하기로 결정했습니다.

작동하지만 기본 데이터의 변경에 응답하지 않습니다.

이 (한 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> 

. 응답이 변경되지-통지합니다 : 그것은 값을 표시하기 때문에 내가 너무 멀리 떨어져있을 수 없습니다

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; 

    } 
} 

: 여기

그리고

<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입니다.

내가 뭘 잘못하고 있니? 또는이 작업을 수행하는 더 쉬운 방법이 있습니까?

감사합니다.

답변

2

이 오류는 변환기로 인해 발생합니다. ObservableCollection은 INotifyCollectionChanged를 구현하며 컬렉션에 대한 변경 (추가/제거/바꾸기/이동/재설정)이있을 때 UI에 알립니다. 이것들은 컬렉션의 내용이 아닌 컬렉션의 모든 변경 사항이므로 이전에보고 있던 업데이트는 클래스가 INotifyPropertyChanged를 구현했기 때문입니다. MultiCoverter는 새로운 객체 컬렉션을 반환하기 때문에 원본 객체에 바인딩 할 필요가 없으므로 초기 컬렉션의 데이터는 이들 객체로 전파되지 않습니다.

내가 먼저 제안하는 것은 CompositeCollection 요소를 살펴보고 이것이 필요한지 확인하는 것입니다.

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

(I '는 기본 데이터의 변경에 응답하지 않습니다'있으리라 믿고있어 변화를 의미한다 : 대신 ItemsSource 당신이, 당신은 같은과 원래의 개체를 유지 수를 설정

값을, 컬렉션을 수정하지, 내가 틀렸다면 알려주고 좀 더 자세히 살펴 보겠습니다.그 대안을 작동하지 않는 경우)

편집 추가
은 목표와 실제 컬렉션을 모두 포장하는 새로운 클래스를 작성하는 것입니다. 그런 다음이 래퍼를 사용하여 하나의 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 이벤트에 응답하지 않습니다. 나는 W/CompositeCollections를 시도했지만, 표시하고자하는 컬렉션이 두 개의 입력과 같은 크기가 아니기 때문에 어떻게 작동하는지 알 수 없다. 이상하게도 Convert 메서드는 초기 화면에서 한 번만 (값 당) 호출되며 다시는 호출되지 않습니다. – AdrianoFerrari

+1

그런 경우, 더 이상 알지 못하면서 이것을 해결하는 가장 쉬운 방법은 대상 및/또는 실제를 병합 할 수있는 일종의 래퍼 클래스를 만드는 것입니다. 그런 다음 이러한 래퍼에 대해 ObservableCollection을 하나만 만들면 훨씬 쉽게 바인딩 할 수 있습니다. – rmoore

+0

그런 식으로해야 할 것 같네요. 내가 의도 한 방식대로 처리하면 여기에 다시 게시 할 것이므로보다 직관적 일 것입니다. 감사합니다. rmoore. 이 점을 대답으로 표시하지 않고 점수를 부여 할 여지가 있습니까? – AdrianoFerrari