問題:
ItemsControl
(またはItemsControl
から派生したコントロール)を宣言します。ItemsControl.ItemsSource
プロパティをViewModelのObservableCollection
にバインドします。ObservableCollection
に追加/削除されると、ビューは期待どおりに更新されます。ObservableCollection
のアイテムのプロパティを変更しても、ビューは更新されません。背景:
これは、多くのWPF開発者が遭遇した一般的な問題のようです。それは数回尋ねられました:
アイテムが変更されたときにObservableCollectionに通知します
ObservableCollectionは、(INotifyPropertyChangedを使用しても)アイテムが変更されたことに気づきません
ObservableCollectionおよびItem PropertyChanged
私の実装:
受け入れられたソリューションを アイテムが変更されたときにObservableCollectionに通知する に実装しようとしました。基本的な考え方は、PropertyChanged
内の各アイテムのMainWindowViewModelにObservableCollection
ハンドラーを接続することです。アイテムのプロパティが変更されると、イベントハンドラーが呼び出され、何らかの方法でビューが更新されます。
実装を機能させることができませんでした。これが私の実装です。
ViewModels:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
アイテムViewModel:
class EmployeeViewModel : ViewModelBase
{
private int _age;
private string _name;
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged("Age");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public override string ToString()
{
return string.Format("{0} is {1} years old", Name, Age);
}
}
メインウィンドウViewModel:
class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<EmployeeViewModel> _collection;
public MainWindowViewModel()
{
_collection = new ObservableCollection<EmployeeViewModel>();
_collection.CollectionChanged += MyItemsSource_CollectionChanged;
AddEmployeeCommand = new DelegateCommand(() => AddEmployee());
IncrementEmployeeAgeCommand = new DelegateCommand(() => IncrementEmployeeAge());
}
public ObservableCollection<EmployeeViewModel> Employees
{
get { return _collection; }
}
public ICommand AddEmployeeCommand { get; set; }
public ICommand IncrementEmployeeAgeCommand { get; set; }
public void AddEmployee()
{
_collection.Add(new EmployeeViewModel()
{
Age = 1,
Name = "Random Joe",
});
}
public void IncrementEmployeeAge()
{
foreach (var item in _collection)
{
item.Age++;
}
}
private void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EmployeeViewModel item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
if (e.OldItems != null)
foreach (EmployeeViewModel item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged("Employees");
}
}
表示:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;Assembly=PresentationFramework.Aero"
xmlns:d="clr-namespace:Iress.IosPlus.DynamicOE.Controls"
Title="MainWindow" Height="350" Width="350">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.7*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Command="{Binding AddEmployeeCommand}">Add Employee</Button>
<Button Command="{Binding IncrementEmployeeAgeCommand}">Increment Employee Age</Button>
</StackPanel>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Employees[0]}"></TextBlock>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees}" BorderBrush="Red" BorderThickness="1"></ItemsControl>
</Grid>
</Grid>
私の結果:
実装を確認するために、このようなビューを作成します。 TextBlock.Text
は、コレクションの最初のアイテムにバインドされています。 ItemsControl
はコレクション自体にバインドされています。
EmployeeViewModel
オブジェクトが追加され、TextBlock
とItemsControl
の両方が期待どおりに更新されます。ItemsControl
が別のエントリで更新されます。すごい!Age
プロパティは1ずつ増加します。PropertyChanged
イベントが発生します。 ItemPropertyChanged
イベントハンドラーが呼び出されます。 Textblock
は期待どおりに更新されます。ただし、ItemsControl
は更新されません。Itemが変更されたときにObservableCollectionに通知する の回答に従ってEmployee.Age
が変更された場合、ItemsControl
も更新する必要があるという印象を受けています。
Snoop を使用してXAMLをデバッグすることで答えを見つけました。
問題は、ToString()メソッドにバインドしようとしていて、PropertyChangedイベントが発生しないことです。 XAMLバインディングを見ると、ObservableCollectionが実際に変更されていることがわかります。
次に、各アイテムコントロールと、「Text」プロパティにバインドされているテキストを確認します。何もありません、それはただのテキストです。
これを修正するには、表示したい要素を含むDataTemplateを含むItemsControlItemTemplateを追加するだけです。
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Employees, UpdateSourceTrigger=PropertyChanged}" BorderBrush="Red" BorderThickness="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat=" {0} is {1} years old">
<Binding Path="Name"/>
<Binding Path="Age"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
これで、バインディングが青信号になりました。 RaisePropertyChangedが呼び出されています。
タダ!