web-dev-qa-db-ja.com

ObservableCollection内のアイテムが更新されたときにItemsControlを更新します

問題:

  • ビューで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オブジェクトが追加され、TextBlockItemsControlの両方が期待どおりに更新されます。
  • もう一度「従業員の追加」を押すと、ItemsControlが別のエントリで更新されます。すごい!
  • 「従業員年齢の増分」ボタンを押します。各アイテムのAgeプロパティは1ずつ増加します。PropertyChangedイベントが発生します。 ItemPropertyChangedイベントハンドラーが呼び出されます。 Textblockは期待どおりに更新されます。ただし、ItemsControlは更新されません。

Itemが変更されたときにObservableCollectionに通知する の回答に従ってEmployee.Ageが変更された場合、ItemsControlも更新する必要があるという印象を受けています。

enter image description here

10
Frank Liu

Snoop を使用してXAMLをデバッグすることで答えを見つけました。

問題は、ToString()メソッドにバインドしようとしていて、PropertyChangedイベントが発生しないことです。 XAMLバインディングを見ると、ObservableCollectionが実際に変更されていることがわかります。

Snoop Showing Correct Binding

次に、各アイテムコントロールと、「Text」プロパティにバインドされているテキストを確認します。何もありません、それはただのテキストです。

Snoop showing no data binding to elements in the items control

これを修正するには、表示したい要素を含む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が呼び出されています。

Binding correctly shows green

タダ!

Solution shown

11
AzzamAziz