ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

前端 未结 18 2688
一生所求
一生所求 2020-11-22 02:06

Does anyone know why this code doesn\'t work:

public class CollectionViewModel : ViewModelBase {  
    public ObservableCollection Con         


        
相关标签:
18条回答
  • 2020-11-22 02:49

    If i know ObservableCollection make event only when we add/delete or move items in our collection. When we simly update some properties in collection items collection don`t signalize about it and UI will not be updated.

    You can simly implement INotifyPropertyChange in your Model class. And than when we update some propery in collection item it automatically will update UI.

    public class Model:INotifyPropertyChange
    {
    //...
    }
    

    and than

    public ObservableCollection<Model> {get; set;}
    

    In my case i used ListView to Bind for this collection and in ItemTemplate set Binding to Model property and it work good.

    Here is some snippet

    Windows XAML :

    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ListView 
            Margin="10"
            BorderBrush="Black"
            HorizontalAlignment="Center"
            SelectedItem="{Binding SelectedPerson}"
            ItemsSource="{Binding Persons}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Label Content="{Binding Name}"/>
                        <Label Content="-"/>
                        <Label Content="{Binding Age}"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Grid 
            Grid.Row="1"
            VerticalAlignment="Center"
            HorizontalAlignment="Center">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Label 
                VerticalAlignment="Center"
                Content="Name:"/>
            <TextBox
                Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                Margin="10"
                Grid.Column="1" 
                Width="100"/>
            <Label 
                VerticalAlignment="Center"
                Grid.Row="1"
                Content="Age:"/>
            <TextBox
                Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                Margin="10"
                Grid.Row="1"
                Grid.Column="1" 
                Width="100"/>
    
    
        </Grid>
    </Grid>
    

    Model code example:

    public class PersonModel:INotifyPropertyChanged
    {
        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                OnPropertyChanged();
            }
        }
    
        public int Age
        {
            get => _age;
            set
            {
                _age = value;
                OnPropertyChanged();
            }
        }
    
        private string _name;
        private int _age;
        //INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    And ViewModel implementation:

     public class ViewModel:INotifyPropertyChanged
    {
        public ViewModel()
        {
            Persons = new ObservableCollection<PersonModel>
            {
                new PersonModel
                {
                    Name = "Jack",
                    Age = 30
                },
                new PersonModel
                {
                    Name = "Jon",
                    Age = 23
                },
                new PersonModel
                {
                    Name = "Max",
                    Age = 23
                },
            };
        }
    
        public ObservableCollection<PersonModel> Persons { get;}
    
        public PersonModel SelectedPerson
        {
            get => _selectedPerson;
            set
            {
                _selectedPerson = value;
                OnPropertyChanged();
            }
        }
    
        //INotifyPropertyChanged Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        private PersonModel _selectedPerson;
    }
    
    0 讨论(0)
  • 2020-11-22 02:49

    Instead of an ObservableCollection or TrulyObservableCollection, consider using a BindingList and calling the ResetBindings method.

    For example:

    private BindingList<TfsFile> _tfsFiles;
    
    public BindingList<TfsFile> TfsFiles
    {
        get { return _tfsFiles; }
        set
        {
            _tfsFiles = value;
            NotifyPropertyChanged();
        }
    }
    

    Given an event, such as a click your code would look like this:

    foreach (var file in TfsFiles)
    {
        SelectedFile = file;
        file.Name = "Different Text";
        TfsFiles.ResetBindings();
    }
    

    My model looked like this:

    namespace Models
    {
        public class TfsFile 
        {
            public string ImagePath { get; set; }
    
            public string FullPath { get; set; }
    
            public string Name { get; set; }
    
            public string Text { get; set; }
    
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:50

    Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:

    public TrulyObservableCollection()
            : base()
        {
            HookupCollectionChangedEvent();
        }
    
        public TrulyObservableCollection(IEnumerable<T> collection)
            : base(collection)
        {
            foreach (T item in collection)
                item.PropertyChanged += ItemPropertyChanged;
    
            HookupCollectionChangedEvent();
        }
    
        public TrulyObservableCollection(List<T> list)
            : base(list)
        {
            list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);
    
            HookupCollectionChangedEvent();
        }
    
        private void HookupCollectionChangedEvent()
        {
            CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
        }
    
    0 讨论(0)
  • 2020-11-22 02:50

    I know that I'm too late for this party, but maybe - it will help to someone..

    Here you can find my implementation of ObservableCollectionEx. It has some features:

    • it supports everything from ObservableCollection
    • it's thread safe
    • it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
    • it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.

    Of course, any comments are appreciated ;)

    0 讨论(0)
  • 2020-11-22 02:51

    This uses the above ideas but makes it a derived 'more sensitive' collection:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Collections;
    
    namespace somethingelse
    {
        public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
        {
            // this collection also reacts to changes in its components' properties
    
            public ObservableCollectionEx() : base()
            {
                this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
            }
    
            void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    foreach(T item in e.OldItems)
                    {
                        //Removed items
                        item.PropertyChanged -= EntityViewModelPropertyChanged;
                    }
                }
                else if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach(T item in e.NewItems)
                    {
                        //Added items
                        item.PropertyChanged += EntityViewModelPropertyChanged;
                    }     
                }       
            }
    
            public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                //This will get called when the property of an object inside the collection changes - note you must make it a 'reset' - dunno why
                NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                OnCollectionChanged(args);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 02:51

    I used Jack Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach(T item in e.NewItems)
            {
                //Removed items
                item.PropertyChanged -= EntityViewModelPropertyChanged;
            }
        }
    

    I used this:

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach(T item in e.OldItems)
            {
                //Removed items
                item.PropertyChanged -= EntityViewModelPropertyChanged;
            }
        }
    

    It seems that the "e.NewItems" produces null if action is .Remove.

    0 讨论(0)
提交回复
热议问题