Notification de la collecte observable en cas de changement D'article
j'ai trouvé sur ce lien
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.
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();
}
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
};
}
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.
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>
, 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.
});
}
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;
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.