ObservableCollection не замечает, когда элемент в нем изменяется (даже с INotifyPropertyChanged)
Кто-нибудь знает, почему этот код не работает:
public class CollectionViewModel : ViewModelBase {
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
set
{
_contentList = value;
RaisePropertyChanged("ContentList");
//I want to be notified here when something changes..?
//debugger doesn't stop here when IsRowChecked is toggled
}
}
}
public class EntityViewModel : ViewModelBase
{
private bool _isRowChecked;
public bool IsRowChecked
{
get { return _isRowChecked; }
set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
}
}
ViewModelBase
содержит все для RaisePropertyChanged
и т.д., и он работает на все остальное, кроме этой проблемы.
Ответы
Ответ 1
Метод ContentList Set не будет вызываться при изменении значения внутри коллекции, вместо этого вы должны искать срабатывание события CollectionChanged.
public class CollectionViewModel : ViewModelBase
{
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
}
public CollectionViewModel()
{
_contentList = new ObservableCollection<EntityViewModel>();
_contentList.CollectionChanged += ContentCollectionChanged;
}
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//This will get called when the collection is changed
}
}
Хорошо, что сегодня дважды я был укушен ошибкой документации MSDN. В приведенной мной ссылке сказано:
Происходит, когда элемент добавляется, удаляется, изменяется, перемещается или обновляется весь список.
Но на самом деле он не срабатывает при смене предмета. Я думаю, вам понадобится более грубый метод, чем:
public class CollectionViewModel : ViewModelBase
{
public ObservableCollection<EntityViewModel> ContentList
{
get { return _contentList; }
}
public CollectionViewModel()
{
_contentList = new ObservableCollection<EntityViewModel>();
_contentList.CollectionChanged += ContentCollectionChanged;
}
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(EntityViewModel item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach(EntityViewModel 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
}
}
Если вам это понадобится очень часто, вы можете создать подкласс собственного ObservableCollection
который запускает событие CollectionChanged
когда член автоматически запускает событие PropertyChanged
(как сказано в документации...)
Ответ 2
Вот класс drop-in для подклассов ObservableCollection и фактически вызывает действие Reset при изменении свойства в элементе списка. Он применяет все элементы для реализации INotifyPropertyChanged
.
Преимущество здесь в том, что вы можете привязать данные к этому классу, и все ваши привязки будут обновляться с изменениями свойств вашего объекта.
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
Ответ 3
Это использует вышеупомянутые идеи, но делает его производной "более чувствительной" коллекцией:
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);
}
}
}
Ответ 4
Я собрал, как я надеюсь, довольно надежное решение, включая некоторые приемы из других ответов. Это новый класс, производный от ObservableCollection<>
, который я называю FullyObservableCollection<>
Он имеет следующие особенности:
- Он добавляет новое событие
ItemPropertyChanged
. Я сознательно держал это отдельно от существующего CollectionChanged
: - Чтобы помочь обратной совместимости.
- Таким образом, более подробная информация может быть предоставлена в новом
ItemPropertyChangedEventArgs
который ее сопровождает: исходный PropertyChangedEventArgs
и индекс в коллекции.
- Он реплицирует все конструкторы из
ObservableCollection<>
. - Он корректно обрабатывает сбрасываемый список (
ObservableCollection<>.Clear()
), избегая возможной утечки памяти. - Он переопределяет базовый класс
OnCollectionChanged()
, а не более ресурсоемкую подписку на событие CollectionChanged
.
Код
Полный файл .cs
следует. Обратите внимание, что было использовано несколько функций С# 6, но это должно быть довольно просто:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Utilities
{
public class FullyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection() : base()
{ }
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (T item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T typedSender = (T)sender;
int i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{ }
}
}
Тесты NUnit
Таким образом, вы можете проверить изменения, которые вы можете внести (и посмотреть, что я тестировал в первую очередь!), Я также включил мой тестовый класс NUnit. Очевидно, что в следующем коде нет необходимости просто использовать FullyObservableCollection<T>
в вашем проекте.
NB Тестовый класс использует BindableBase
из PRISM для реализации INotifyPropertyChanged
. Нет зависимости от ПРИЗМЫ от основного кода.
using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;
namespace Test_Utilities
{
[TestFixture]
public class Test_FullyObservableCollection : AssertionHelper
{
public class NotifyingTestClass : BindableBase
{
public int Id
{
get { return _Id; }
set { SetProperty(ref _Id, value); }
}
private int _Id;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
private string _Name;
}
FullyObservableCollection<NotifyingTestClass> TestCollection;
NotifyingTestClass Fred;
NotifyingTestClass Betty;
List<NotifyCollectionChangedEventArgs> CollectionEventList;
List<ItemPropertyChangedEventArgs> ItemEventList;
[SetUp]
public void Init()
{
Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };
TestCollection = new FullyObservableCollection<NotifyingTestClass>()
{
Fred,
new NotifyingTestClass() {Id = 2, Name = "Barney" },
new NotifyingTestClass() {Id = 3, Name = "Wilma" }
};
CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
ItemEventList = new List<ItemPropertyChangedEventArgs>();
TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
}
// Change existing member property: just ItemPropertyChanged(IPC) should fire
[Test]
public void DetectMemberPropertyChange()
{
TestCollection[0].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
}
// Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
[Test]
public void DetectNewMemberPropertyChange()
{
TestCollection.Add(Betty);
Expect(TestCollection.Count, Is.EqualTo(4));
Expect(TestCollection[3].Name, Is.EqualTo("Betty"));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
TestCollection[3].Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
}
// Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
[Test]
public void CeaseListentingWhenMemberRemoved()
{
TestCollection.Remove(Fred);
Expect(TestCollection.Count, Is.EqualTo(2));
Expect(TestCollection.IndexOf(Fred), Is.Negative);
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
// Move member in list, change property: CPC should fire for move, IPC should fire for change
[Test]
public void MoveMember()
{
TestCollection.Move(0, 1);
Expect(TestCollection.Count, Is.EqualTo(3));
Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
}
// Clear list, chnage property: only CPC should fire for clear and neither for property change
[Test]
public void ClearList()
{
TestCollection.Clear();
Expect(TestCollection.Count, Is.EqualTo(0));
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");
Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
CollectionEventList.Clear(); // Empty for next operation
ItemEventList.Clear();
Fred.Id = 7;
Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
}
}
}
Ответ 5
ObservableCollection не будет распространять отдельные изменения элементов как события CollectionChanged. Вам необходимо либо подписаться на каждое событие, либо отправить его вручную, либо вы можете проверить класс BindingList [T], который будет делать это для вы.
Ответ 6
Добавлен в событие TruelyObservableCollection "ItemPropertyChanged":
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObservableCollectionTest
{
class Program
{
static void Main(string[] args)
{
// ATTN: Please note it a "TrulyObservableCollection" that instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
// REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });
Trades.CollectionChanged += Trades_CollectionChanged;
Trades.ItemPropertyChanged += PropertyChangedHandler;
Trades.RemoveAt(2);
Trades[0].Qty = 999;
Console.WriteLine("Hit any key to exit");
Console.ReadLine();
return;
}
static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
return;
}
static void Trades_CollectionChanged(object sender, EventArgs e)
{
Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
return;
}
}
#region TrulyObservableCollection
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
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);
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(sender, e);
}
}
}
#endregion
#region Sample entity
class Trade : INotifyPropertyChanged
{
protected string _Symbol;
protected int _Qty = 0;
protected DateTime _OrderPlaced = DateTime.Now;
public DateTime OrderPlaced
{
get { return _OrderPlaced; }
}
public string Symbol
{
get
{
return _Symbol;
}
set
{
_Symbol = value;
NotifyPropertyChanged("Symbol");
}
}
public int Qty
{
get
{
return _Qty;
}
set
{
_Qty = value;
NotifyPropertyChanged("Qty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
Ответ 7
Я использовал ответ Джека Кеньона, чтобы реализовать свой собственный OC, но я хотел бы указать на одно изменение, которое я должен был сделать, чтобы заставить его работать. Вместо:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
Я использовал это:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
Кажется, что "e.NewItems" создает null, если действие есть. Удалить.
Ответ 8
Просто добавьте 2 цента на эту тему. Войдя в TrulyObservableCollection, потребовались два других конструктора, найденных с помощью 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);
}
Ответ 9
Я знаю, что я слишком поздно для этой вечеринки, но, может быть, это поможет кому-то..
Здесь вы можете найти мою реализацию ObservableCollectionEx. Он имеет некоторые функции:
- он поддерживает все, от ObservableCollection
- он потокобезопасен
- он поддерживает событие ItemPropertyChanged (он возникает каждый раз, когда объект Item.PropertyChanged запущен)
- он поддерживает фильтры (поэтому вы можете создать ObservableCollectionEx, передать другую коллекцию в качестве источника для нее и Filter с простым предикатом. Очень полезно в WPF, я часто использую эту функцию в своих приложениях). Более того - фильтр отслеживает изменения элементов через интерфейс INotifyPropertyChanged.
Конечно, любые комментарии приветствуются;)
Ответ 10
Если я знаю, что ObservableCollection делает событие только тогда, когда мы добавляем/удаляем или перемещаем элементы в нашей коллекции. Когда мы просто обновляем некоторые свойства в коллекции элементов коллекции, не сигнализируем об этом, и пользовательский интерфейс не будет обновляться.
Вы можете просто реализовать INotifyPropertyChange в классе модели.
И чем когда мы обновим некоторые свойства в элементе коллекции, он автоматически обновит пользовательский интерфейс.
public class Model:INotifyPropertyChange
{
//...
}
и чем
public ObservableCollection<Model> {get; set;}
В моем случае я использовал ListView для привязки для этой коллекции и в ItemTemplate задал свойство Binding to Model, и он работает хорошо.
Вот фрагмент
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>
Пример кода модели:
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));
}
}
И реализация ViewModel:
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;
}
Ответ 11
Простое решение для стандартного наблюдаемого коллекционирования, которое я использовал:
НЕ ДОБАВЛЯЙТЕ к своему свойству ИЛИ ИЗМЕНИТЕ его внутренние элементы НЕПОСРЕДСТВЕННО, вместо этого создайте такую временную коллекцию, как этот
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
и добавить элементы или внести изменения в tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
затем передать его в ваше фактическое свойство по заданию.
ContentList=tmpList;
это изменит целое свойство, из-за чего вы заметите, что INotifyPropertyChanged по мере необходимости.
Ответ 12
Здесь метод расширения для вышеупомянутого решения...
public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
where T : INotifyPropertyChanged
{
var newList = new TrulyObservableCollection<T>();
if (list != null)
{
list.ForEach(o => newList.Add(o));
}
return newList;
}
Ответ 13
Я пытаюсь это решение, но работает только для меня, как RaisePropertyChange ( "SourceGroupeGridView" ), когда коллекция была изменена, которая активировалась для каждого элемента, добавляемого или измененного.
Проблема заключается в следующем:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset это действие делает полную переустановку всех элементов в groupedgrid, эквивалентно в RaisePropertyChanged. Когда вы используете его, все группы gridview обновляются.
ЕСЛИ вы только хотите обновить в пользовательском интерфейсе группу нового элемента, вы не используете действие Reset, вам нужно будет симулировать действие Add в itemproperty с чем-то вроде этого:
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var index = this.IndexOf((T)sender);
this.RemoveAt(index);
this.Insert(index, (T)sender);
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
OnCollectionChanged(a);
}
Извините, мой английский, и спасибо за базовый код:),
Надеюсь, это поможет кому-то ^ _ ^
Enjoi!!
Ответ 14
Вместо ObservableCollection или TrulyObservableCollection рассмотрите возможность использования BindingList и вызова метода ResetBindings.
Например:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Учитывая событие, например щелчок, ваш код будет выглядеть так:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
Моя модель выглядела так:
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; }
}
}
Ответ 15
Вот моя версия реализации. Он проверяет и выдает ошибку, если объекты в списке не реализуют INotifyPropertyChanged, поэтому не могут забыть эту проблему при разработке. Снаружи вы используете событие ListItemChanged, чтобы определить, изменился ли этот список или элемент списка.
public class SpecialObservableCollection<T> : ObservableCollection<T>
{
public SpecialObservableCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AddOrRemoveListToPropertyChanged(e.NewItems,true);
AddOrRemoveListToPropertyChanged(e.OldItems,false);
}
private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
{
if (list == null) { return; }
foreach (object item in list)
{
INotifyPropertyChanged o = item as INotifyPropertyChanged;
if (o != null)
{
if (add) { o.PropertyChanged += ListItemPropertyChanged; }
if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
}
else
{
throw new Exception("INotifyPropertyChanged is required");
}
}
}
void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnListItemChanged(this, e);
}
public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);
public event ListItemChangedEventHandler ListItemChanged;
private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
{
if (ListItemChanged != null) { this.ListItemChanged(this, e); }
}
}
Ответ 16
Простое решение в 2 строках кода. Просто используйте конструктор копирования.
Не нужно писать TrulyObservableCollection и т.д.
Пример:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Другой метод без конструктора копирования. Вы можете использовать сериализацию.
speakers.list[0].Status = "offline";
//speakers.list[0] = new Speaker(speakers.list[0]);
var tmp = JsonConvert.SerializeObject(speakers.list[0]);
var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
speakers.list[0] = tmp2;
Ответ 17
Вы также можете использовать этот метод расширения, чтобы легко зарегистрировать обработчик для изменения свойства элемента в соответствующих коллекциях. Этот метод автоматически добавляется во все коллекции, реализующие INotifyCollectionChanged, которые содержат элементы, реализующие INotifyPropertyChanged:
public static class ObservableCollectionEx
{
public static void SetOnCollectionItemPropertyChanged<T>(this T _this, PropertyChangedEventHandler handler)
where T : INotifyCollectionChanged, ICollection<INotifyPropertyChanged>
{
_this.CollectionChanged += (sender,e)=> {
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += handler;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= handler;
}
}
};
}
}
Как пользоваться:
public class Test
{
public static void MyExtensionTest()
{
ObservableCollection<INotifyPropertyChanged> c = new ObservableCollection<INotifyPropertyChanged>();
c.SetOnCollectionItemPropertyChanged((item, e) =>
{
//whatever you want to do on item change
});
}
}
Ответ 18
Для запуска OnChange в списке наблюдаемых коллекций
- Получить индекс выбранного элемента
- Удалить элемент из родительского
- Добавить элемент с тем же индексом в родительский
Пример:
int index = NotificationDetails.IndexOf(notificationDetails);
NotificationDetails.Remove(notificationDetails);
NotificationDetails.Insert(index, notificationDetails);