Уведомлять ObservableCollection при изменении позиции
Я нашел по этой ссылке
ObservableCollection не замечает, когда элемент в нем изменяется (даже с INotifyPropertyChanged)
некоторые методы, чтобы уведомить Observablecollection о том, что элемент изменился. TrulyObservableCollection в этой ссылке кажется тем, что я ищу.
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);
}
}
Но когда я пытаюсь использовать его, я не получаю уведомления в коллекции. Я не уверен, как правильно реализовать это в моем коде С#:
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);
}
}
}
Когда я запускаю программу, у меня есть флажок 3 для false, true, false, как в инициализации свойства.
но когда я изменяю состояние одного из ckeckbox, программа переходит через item_PropertyChanged, но никогда в MyItemsSource Property code.
Ответы
Ответ 1
Точка, которую вы прокомментировали как // Code to trig on item change...
, будет запускаться только тогда, когда объект коллекции будет изменен, например, когда он будет установлен на новый объект или будет установлен на нуль.
С вашей текущей реализацией TrulyObservableCollection, чтобы обрабатывать свойства, измененные события вашей коллекции, зарегистрируйте что-то в событии CollectionChanged
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
}
Лично мне действительно не нравится эта реализация. Вы поднимаете событие CollectionChanged
, в котором говорится, что вся коллекция была reset, при изменении свойства. Уверен, что это сделает обновление пользовательского интерфейса в любое время, когда элемент в коллекции изменится, но я вижу, что это плохой показатель производительности, и похоже, что у него нет способа определить, какое свойство изменилось, что является одной из ключевых частей информации Обычно мне нужно делать что-то на PropertyChanged
.
Я предпочитаю использовать обычный ObservableCollection
и просто подключать к ним события PropertyChanged
к элементам на CollectionChanged
. Предоставление вашего пользовательского интерфейса правильно привязывается к элементам в ObservableCollection
, вам не нужно указывать, что пользовательский интерфейс будет обновляться при изменении свойства элемента в коллекции.
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();
}
Ответ 2
Я решил этот случай, используя static Action
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
};
}
Ответ 3
ObservableCollection
и его производные внутренне меняют свои свойства. Код в вашем сеттере должен запускаться только в том случае, если вы назначили новый TrulyObservableCollection<MyType>
свойству MyItemsSource
. То есть, это должно произойти только один раз, от конструктора.
С этой точки вперед вы получите уведомления об изменении свойств из коллекции, а не из сеттера в вашей модели просмотра.
Ответ 4
Я знаю это поздно, но, возможно, это помогает другим. Я создал класс NotifyObservableCollection
, который решает проблему отсутствия уведомления для самого элемента, когда свойство элемента изменяется. Использование так же просто, как 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);
}
В то время как элементы добавляются или удаляются, класс перенаправляет события PropertyChanged
в коллекцию событий PropertyChanged
.
использование:
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; }
}
Если теперь свойство элемента в DataItems
изменяется, следующее xaml получит уведомление, хотя оно привязывается к Parameters[0]
или к самому элементу, кроме изменения свойства Value
элемента (Преобразователи в Триггеры называются надежными при каждом изменении).
<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>
Ответ 5
Вы можете использовать метод расширения, чтобы получить уведомление об измененном свойстве элемента в коллекции в общем виде.
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: http://stackoverflow.com/questions/298261/do-event-handlers-stop-garbage-collection-from-occuring
//publisher.SomeEvent += target.SomeHandler;
//then "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.
});
}
Ответ 6
Все решения здесь правильные, но в них отсутствует важный сценарий, в котором используется метод Clear(), t обеспечить OldItems
в объекте NotifyCollectionChangedEventArgs
.
это идеальный ObservableCollection
.
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;
}
Вы даже можете пройти лишнюю милю и изменить ItemPropertyChanged, чтобы предоставить список владельцев, как этот
Вне класса в некотором пространстве имен:
public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);
в классе измените эти:
private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//raise the event
ItemPropertyChanged?.Invoke(this,sender, e);
}
public ListedItemPropertyChangedEventHandler ItemPropertyChanged;