Notification de la collecte observable en cas de changement D'article

j'ai trouvé sur ce lien

collecte observable ne remarquant pas lorsque L'élément dans elle change (même avec INotifyPropertyChanged)

certaines techniques pour notifier une collecte observable qu'un article a changé. la collection TrulyObservableCollection dans ce lien semble être ce que je cherche.

public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
    public TrulyObservableCollection()
    : base()
    {
        CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
    }

    void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
    }

    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(a);
    }
}

mais quand j'essaie de l'utiliser, Je ne reçois pas de notifications sur la collection. Je ne suis pas sûr de savoir comment implémentez correctement ceci dans mon code C#:

XAML:

    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <DataGrid.Columns>
            <DataGridCheckBoxColumn Binding="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </DataGrid.Columns>
    </DataGrid>

ViewModel:

public class MyViewModel : ViewModelBase
{
    private TrulyObservableCollection<MyType> myItemsSource;
    public TrulyObservableCollection<MyType> MyItemsSource
    {
        get { return myItemsSource; }
        set 
        { 
            myItemsSource = value; 
            // Code to trig on item change...
            RaisePropertyChangedEvent("MyItemsSource");
        }
    }

    public MyViewModel()
    {
        MyItemsSource = new TrulyObservableCollection<MyType>()
        { 
            new MyType() { MyProperty = false },
            new MyType() { MyProperty = true },
            new MyType() { MyProperty = false }
        };
    }
}

public class MyType : ViewModelBase
{
    private bool myProperty;
    public bool MyProperty
    {
        get { return myProperty; }
        set 
        {
            myProperty = value;
            RaisePropertyChangedEvent("MyProperty");
        }
    }
}

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
            PropertyChanged(this, e);
        }
    }
}

quand j'exécute le programme, j'ai la case à cocher 3 false, true, false comme dans la propriété initialisation. mais quand je change l'état d'un des ckeckbox, le programme passe par item_PropertyChanged mais jamais dans le code de propriété MyItemsSource.

29
demandé sur Guillaume RAYMOND 2011-12-13 18:06:28

7 réponses

le spot que vous avez commenté comme // Code to trig on item change... ne se déclenche que lorsque l'objet collection est modifié, par exemple lorsqu'il est défini à un nouvel objet, ou défini à null.

avec votre mise en œuvre actuelle de TrulyObservableCollection, pour gérer les événements de propriété changée de votre collection, enregistrez quelque chose au CollectionChanged événement de MyItemsSource

public MyViewModel()
{
    MyItemsSource = new TrulyObservableCollection<MyType>();
    MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged;

    MyItemsSource.Add(new MyType() { MyProperty = false });
    MyItemsSource.Add(new MyType() { MyProperty = true});
    MyItemsSource.Add(new MyType() { MyProperty = false });
}


void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // Handle here
}

personnellement, je n'aime vraiment pas cette implémentation. Vous êtes soulever un événement CollectionChanged qui dit que toute la collection a été réinitialisée, chaque fois qu'une propriété change. Bien sûr, il va faire la mise à jour de L'UI à tout moment un élément dans les changements de collecte, mais je vois que d'être mauvais sur la performance, et il ne semble pas avoir un moyen d'identifier quelle propriété a changé, qui est l'un des éléments clés de l'information dont j'ai habituellement besoin quand je fais quelque chose sur PropertyChanged .

je préfère utiliser un ObservableCollection régulier et juste accrocher le PropertyChanged événements il est des points sur les CollectionChanged . Si votre UI est correctement liée aux éléments du ObservableCollection , vous ne devriez pas avoir besoin de dire à L'UI de mettre à jour quand une propriété sur un élément de la collection change.

public MyViewModel()
{
    MyItemsSource = new ObservableCollection<MyType>();
    MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged;

    MyItemsSource.Add(new MyType() { MyProperty = false });
    MyItemsSource.Add(new MyType() { MyProperty = true});
    MyItemsSource.Add(new MyType() { MyProperty = false });
}

void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach(MyType item in e.NewItems)
            item.PropertyChanged += MyType_PropertyChanged;

    if (e.OldItems != null)
        foreach(MyType item in e.OldItems)
            item.PropertyChanged -= MyType_PropertyChanged;
}

void MyType_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "MyProperty")
        DoWork();
}
64
répondu Rachel 2015-01-06 14:51:55

j'ai résolu ce cas en utilisant l'Action statique


public class CatalogoModel 
{
    private String _Id;
    private String _Descripcion;
    private Boolean _IsChecked;

    public String Id
    {
        get { return _Id; }
        set { _Id = value; }
    }
    public String Descripcion
    {
        get { return _Descripcion; }
        set { _Descripcion = value; }
    }
    public Boolean IsChecked
    {
        get { return _IsChecked; }
        set
        {
           _IsChecked = value;
            NotifyPropertyChanged("IsChecked");
            OnItemChecked.Invoke();
        }
    }

    public static Action OnItemChecked;
} 

public class ReglaViewModel : ViewModelBase
{
    private ObservableCollection<CatalogoModel> _origenes;

    CatalogoModel.OnItemChecked = () =>
            {
                var x = Origenes.Count;  //Entra cada vez que cambia algo en _origenes
            };
}
6
répondu sat1582 2013-08-01 23:50:20

le ObservableCollection et ses dérivés soulèvent ses changements de propriété interne. Le code dans votre setter ne doit être déclenché que si vous assignez un nouveau TrulyObservableCollection<MyType> à la propriété MyItemsSource . C'est, il ne doit se produire une fois, à partir du constructeur.

à partir de ce point, vous recevrez des notifications de changement de propriété à partir de la collection, pas à partir du setter dans votre viewmodel.

2
répondu Jay 2011-12-13 14:37:25

je sais qu'il est tard, mais peut-être que ça aide les autres. J'ai créé une classe NotifyObservableCollection , qui résout le problème de notification manquante à l'élément lui-même, quand une propriété de l'élément change. L'usage est aussi simple que ObservableCollection .

public class NotifyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    private void Handle(object sender, PropertyChangedEventArgs args)
    {
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null) {
            foreach (object t in e.NewItems) {
                ((T) t).PropertyChanged += Handle;
            }
        }
        if (e.OldItems != null) {
            foreach (object t in e.OldItems) {
                ((T) t).PropertyChanged -= Handle;
            }
        }
        base.OnCollectionChanged(e);
    }

pendant que les éléments sont ajoutés ou supprimés la classe transmet les éléments PropertyChanged événement aux collections PropertyChanged événement.

utilisation:

public abstract class ParameterBase : INotifyPropertyChanged
{
    protected readonly CultureInfo Ci = new CultureInfo("en-US");
    private string _value;

    public string Value {
        get { return _value; }
        set {
            if (value == _value) return;
            _value = value;
            OnPropertyChanged();
        }
    }
}

public class AItem {
    public NotifyObservableCollection<ParameterBase> Parameters {
        get { return _parameters; }
        set {
            NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged();
            if (_parameters != null) _parameters.CollectionChanged -= cceh;
            _parameters = value;
            //needed for Binding to AItem at xaml directly
            _parameters.CollectionChanged += cceh; 
        }
    }

    public NotifyObservableCollection<ParameterBase> DefaultParameters {
        get { return _defaultParameters; }
        set {
            NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged();
            if (_defaultParameters != null) _defaultParameters.CollectionChanged -= cceh;
            _defaultParameters = value;
            //needed for Binding to AItem at xaml directly
            _defaultParameters.CollectionChanged += cceh;
        }
    }


public class MyViewModel {
    public NotifyObservableCollection<AItem> DataItems { get; set; }
}

si maintenant un propriété d'un article dans DataItems change, le XAML suivant recevra une notification, bien qu'il se lie à Parameters[0] ou à l'article lui-même sauf à la propriété changeante Value de l'article (Les Convertisseurs aux déclencheurs sont appelés fiables sur chaque changement).

<DataGrid CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding DataItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Parameters[0].Value}" Header="P1">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="Background" Value="Aqua" />
                    <Style.Triggers>
                        <DataTrigger Value="False">
                            <!-- Bind to Items with changing properties -->
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{StaticResource ParameterCompareConverter}">
                                    <Binding Path="DefaultParameters[0]" />
                                    <Binding Path="Parameters[0]" />
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter Property="Background" Value="DeepPink" />
                        </DataTrigger>
                        <!-- Binds to AItem directly -->
                        <DataTrigger Value="True" Binding="{Binding Converter={StaticResource CheckParametersConverter}}">
                            <Setter Property="FontWeight" Value="ExtraBold" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
2
répondu WPFGermany 2017-01-13 12:30:29

, Vous pouvez utiliser une méthode d'extension pour être averti de la propriété modifiée d'un élément dans une collection de manière générique.

public static class ObservableCollectionExtension
{
    public static void NotifyPropertyChanged<T>(this ObservableCollection<T> observableCollection, Action<T, PropertyChangedEventArgs> callBackAction)
        where T : INotifyPropertyChanged
    {
        observableCollection.CollectionChanged += (sender, args) =>
        {
            //Does not prevent garbage collection says: /q/do-event-handlers-stop-garbage-collection-from-occurring-73382/"publisher" will keep "target" alive, but "target" will not keep "publisher" alive.
            if (args.NewItems == null) return;
            foreach (T item in args.NewItems)
            {
                item.PropertyChanged += (obj, eventArgs) =>
                {
                    callBackAction((T)obj, eventArgs);
                };
            }
        };
    }
}

public void ExampleUsage()
{
    var myObservableCollection = new ObservableCollection<MyTypeWithNotifyPropertyChanged>();
    myObservableCollection.NotifyPropertyChanged((obj, notifyPropertyChangedEventArgs) =>
    {
        //DO here what you want when a property of an item in the collection has changed.
    });
}
1
répondu Mike de Klerk 2016-11-10 15:25:45

toutes les solutions ici sont correctes,mais elles manquent un scénario important dans lequel la méthode Clear () est utilisée, qui ne fournit pas OldItems dans l'objet NotifyCollectionChangedEventArgs .

c'est le parfait 151940920" .

public class ObservableCollectionEX<T> : ObservableCollection<T>
{
    #region Constructors
    public ObservableCollectionEX() : base()
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }
    public ObservableCollectionEX(IEnumerable<T> c) : base(c)
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }
    public ObservableCollectionEX(List<T> l) : base(l)
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }

    #endregion



    public new void Clear()
    {
        foreach (var item in this)
        {
            if (item is INotifyPropertyChanged i)
            {
                if (i != null)
                    i.PropertyChanged -= Element_PropertyChanged;
            }
        }
        base.Clear();
    }
    private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
            foreach (var item in e.OldItems)
            {
                if (item != null && item is INotifyPropertyChanged i)
                {
                    i.PropertyChanged -= Element_PropertyChanged;
                }
            }
        if (e.NewItems != null)
            foreach (var item in e.NewItems)
            {
                if (item != null && item is INotifyPropertyChanged i)
                {
                    i.PropertyChanged -= Element_PropertyChanged;
                    i.PropertyChanged += Element_PropertyChanged;
                }
            }
    }
    private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //raise the event
        ItemPropertyChanged?.Invoke(sender, e);
    }


    /// <summary>
    /// the sender is the Item
    /// </summary>
    public PropertyChangedEventHandler ItemPropertyChanged;

}

vous pouvez même aller le mile supplémentaire et changer le Itepropertychanged pour fournir la liste de propriétaire comme ceci

en dehors de la classe dans un espace de noms :

public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);

dans la classe, remplacer par ce qui suit:

private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    //raise the event
    ItemPropertyChanged?.Invoke(this,sender, e);
}
public ListedItemPropertyChangedEventHandler ItemPropertyChanged;  
0
répondu bigworld12 2017-08-06 14:03:35

une solution simple est d'utiliser BindingList<T> au lieu de ObservableCollection<T> . En effet, les notifications de changement D'item relay de BindingList. Ainsi, avec une liste contraignante, si l'élément implémente l'interface INotifyPropertyChanged alors vous pouvez simplement obtenir des notifications en utilisant l'événement ListChanged .

Voir aussi ce DONC réponse.

0
répondu Malick 2018-06-19 13:56:56