항목이 변경 될 때 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
등을 위해 모든 것을 담아 두고이 문제를 제외한 다른 모든 것을 위해 노력하고 있습니다.
컬렉션 내에서 값을 변경할 때 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
다음은 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);
}
}
이것은 위의 아이디어를 사용하지만 파생 된 '더 민감한'컬렉션으로 만듭니다.
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);
}
}
}
나는 다른 답변의 기술 중 일부를 포함하여 매우 강력한 솔루션 인 희망을 모았습니다. 그것은에서 파생 된 새로운 클래스 ObservableCollection<>
내가 전화 했어,FullyObservableCollection<>
다음과 같은 기능이 있습니다.
- 새로운 이벤트 인을 추가합니다
ItemPropertyChanged
. 나는 이것을 기존의 것과 별도로 유지했습니다CollectionChanged
.- 이전 버전과의 호환성을 지원합니다.
- 따라서 새로운 내용과 관련하여 더 관련성이 높은 세부 사항을 제공 할 수 있습니다
ItemPropertyChangedEventArgs
. 원본PropertyChangedEventArgs
과 컬렉션 내의 색인.
- 의 모든 생성자를 복제합니다
ObservableCollection<>
. ObservableCollection<>.Clear()
가능한 메모리 누수를 피하면서 재설정되는 목록 ( )을 올바르게 처리합니다 .OnCollectionChanged()
자원을 많이 사용하는CollectionChanged
이벤트 구독이 아닌 기본 클래스의를 대체합니다 .
암호
완전한 .cs
파일은 다음과 같습니다. C # 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
합니다. 기본 코드에서 PRISM에 대한 종속성이 없습니다.
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)");
}
}
}
ObservableCollection은 개별 항목 변경 사항을 CollectionChanged 이벤트로 전파하지 않습니다. 각 이벤트를 구독하고 수동으로 전달하거나 BindingList [T] 클래스를 확인하면됩니다.
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's a "TrulyObservableCollection" that's 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
}
Jack Kenyons의 답변을 사용하여 내 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;
}
}
action이 .Remove 인 경우 "e.NewItems"는 널을 생성하는 것으로 보입니다.
이 주제에 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);
}
나는이 파티에 너무 늦었다는 것을 알고 있지만 어쩌면 누군가에게 도움이 될 것이다 ..
여기 에서 ObservableCollectionEx의 구현을 찾을 수 있습니다. 몇 가지 기능이 있습니다.
- 그것은 ObservableCollection에서 모든 것을 지원합니다
- 스레드 안전
- ItemPropertyChanged 이벤트를 지원합니다 (Item.PropertyChanged 항목이 실행될 때마다 발생 함).
- 필터를 지원합니다 (따라서 ObservableCollectionEx를 생성하고 다른 컬렉션을 소스로 전달하고 간단한 술어로 필터링 할 수 있습니다. WPF에서 매우 유용합니다.이 기능은 내 응용 프로그램에서 많이 사용합니다). 훨씬 더-필터는 INotifyPropertyChanged 인터페이스를 통해 항목의 변경 사항을 추적합니다.
물론, 모든 의견을 부탁드립니다;)
ObservableCollection을 알고 있다면 컬렉션에서 항목을 추가 / 삭제하거나 이동할 때만 이벤트를 만듭니다. 컬렉션 아이템 컬렉션에서 일부 속성을 간단하게 업데이트 할 때이를 알리지 않고 UI가 업데이트되지 않습니다.
Model 클래스에서 INotifyPropertyChange 를 간단하게 구현할 수 있습니다 . 컬렉션 항목에서 일부 속성을 업데이트 할 때보 다 자동으로 UI가 업데이트됩니다.
public class Model:INotifyPropertyChange
{
//...
}
그리고보다
public ObservableCollection<Model> {get; set;}
제 경우에는 ListView를 사용 하여이 컬렉션과 BindTemplate에서 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;
}
내가 사용한 표준 관찰 가능 수집을위한 간단한 솔루션 :
속성에 추가하거나 내부 항목을 직접 변경하지 마십시오. 대신 다음과 같이 임시 컬렉션을 만드십시오.
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
항목을 추가하거나 tmpList를 변경하십시오.
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
그런 다음 할당하여 실제 부동산에 전달하십시오.
ContentList=tmpList;
그러면 전체 속성이 변경되어 필요에 따라 INotifyPropertyChanged가 표시됩니다.
이 솔루션을 시도하지만 컬렉션이 변경 될 때 RaisePropertyChange ( "SourceGroupeGridView")처럼 작동합니다. 각 항목이 추가되거나 변경 될 때마다 발생합니다.
문제는 다음과 같습니다.
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Reset이 조치는 groupedgrid의 모든 항목을 완전히 리 바인드하며 RaisePropertyChanged와 동일합니다. 사용하면 모든 gridview 그룹이 새로 고쳐졌습니다.
UI에서 새 항목의 그룹 만 새로 고치려면 재설정 작업을 사용하지 않고 다음과 같이 항목 속성에서 추가 작업을 시뮬레이션해야합니다.
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);
}
내 영어로 미안하고 기본 코드에 감사드립니다 :), 이것이 누군가를 돕기를 바랍니다 ^ _ ^
엔조이 !!
위의 솔루션에 대한 확장 방법은 다음과 같습니다.
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;
}
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; }
}
}
여기 내 버전의 구현이 있습니다. 목록의 객체가 INotifyPropertyChanged를 구현하지 않으면 오류를 확인하고 throw하므로 개발 중에 해당 문제를 잊을 수 없습니다. 외부에서는 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); }
}
}
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;
이 확장 메소드를 사용하여 관련 콜렉션에서 항목 특성 변경을위한 핸들러를 쉽게 등록 할 수도 있습니다. 이 메소드는 INotifyPropertyChanged를 구현하는 항목을 보유하는 INotifyCollectionChanged를 구현하는 모든 콜렉션에 자동으로 추가됩니다.
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
});
}
}
ObservableCollection 목록에서 OnChange를 트리거하려면
- 선택된 아이템의 인덱스 가져 오기
- 부모에서 항목을 제거하십시오
- 부모의 같은 인덱스에 항목을 추가
예:
int index = NotificationDetails.IndexOf(notificationDetails);
NotificationDetails.Remove(notificationDetails);
NotificationDetails.Insert(index, notificationDetails);
'programing tip' 카테고리의 다른 글
Linux에서 단일 프로세스의 CPU 사용량 및 메모리 사용량을 검색 하시겠습니까? (0) | 2020.06.07 |
---|---|
경로에 대한 액세스가 거부되었습니다 (0) | 2020.06.07 |
각도 2 요소 표시 및 숨기기 (0) | 2020.06.06 |
Go를 사용하여 JSON을 예쁘게 인쇄하려면 어떻게해야합니까? (0) | 2020.06.06 |
모듈을 만들 수 없습니다 : 관련 gradle 구성을 찾을 수 없습니다. (0) | 2020.06.06 |