Improve the logic to listen for INotifyPropertyChanged.

This commit is contained in:
APopatanasov 2018-08-06 16:00:10 +03:00
Родитель a677b4eb92
Коммит 04c273db66
8 изменённых файлов: 411 добавлений и 28 удалений

Просмотреть файл

@ -114,6 +114,7 @@
<Compile Include="Data\Engine\DataProviders\IDataStatusListener.cs" />
<Compile Include="Data\Engine\DataSouceView\CollectionViewDataSourceView.cs" />
<Compile Include="Data\Engine\DataSouceView\IDataSourceCurrency.cs" />
<Compile Include="Data\Engine\DataSouceView\NestedPropertyInfo.cs" />
<Compile Include="Data\Engine\EntityProviders\Entity.cs" />
<Compile Include="Data\Engine\EntityProviders\EntityChangedEventArgs.cs" />
<Compile Include="Data\Engine\EntityProviders\EntityProperty.cs" />

Просмотреть файл

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Telerik.Core;
@ -21,8 +22,10 @@ namespace Telerik.Data.Core
private WeakEventHandler<object> currentItemChangedEventHandler;
private ICollectionView source;
private List<Tuple<object, NotifyCollectionChangedEventArgs>> pendingCollectionChanges = new List<Tuple<object, NotifyCollectionChangedEventArgs>>();
private Dictionary<object, NestedPropertyInfo> nestedObjectInfos;
private List<Tuple<object, int, int>> sourceGroups = new List<Tuple<object, int, int>>();
private bool shouldSubsribeToPropertyChanged;
private bool itemSupportsPropertyChanged;
private bool supportsPropertyChangedInitialized;
@ -61,13 +64,13 @@ namespace Telerik.Data.Core
foreach (var item in collectionView)
{
if (!this.supportsPropertyChangedInitialized)
{
this.itemSupportsPropertyChanged = item is INotifyPropertyChanged;
this.supportsPropertyChangedInitialized = true;
}
//if (!this.supportsPropertyChangedInitialized)
//{
// this.itemSupportsPropertyChanged = item is INotifyPropertyChanged;
// this.supportsPropertyChangedInitialized = true;
//}
this.AddPropertyChangedHandler(item);
//this.AddPropertyChangedHandler(item);
if (!isSourceGrouped)
{
@ -191,12 +194,128 @@ namespace Telerik.Data.Core
INotifyPropertyChanged inpc = sender as INotifyPropertyChanged;
if (inpc != null)
{
this.HandlePropertyChanged(sender, (PropertyChangedEventArgs)args);
if (this.internalList.Contains(inpc))
{
if (this.nestedObjectInfos.Count > 0)
{
this.NestedPropertyChanged(sender, sender, ((PropertyChangedEventArgs)args).PropertyName);
}
this.HandlePropertyChanged(sender, (PropertyChangedEventArgs)args);
}
else
{
NestedPropertyInfo info;
if (this.nestedObjectInfos.TryGetValue(sender, out info))
{
string propertyName = ((PropertyChangedEventArgs)args).PropertyName;
this.NestedPropertyChanged(sender, info.rootItem, propertyName, info.nestedPropertyPath);
PropertyChangedEventArgs arguments = new PropertyChangedEventArgs(info.nestedPropertyPath + propertyName);
this.HandlePropertyChanged(info.rootItem, arguments);
}
}
}
}
}
}
void IDataSourceView.SubscribeToItemPropertyChanged()
{
this.shouldSubsribeToPropertyChanged = true;
if (this.nestedObjectInfos == null)
{
this.nestedObjectInfos = new Dictionary<object, NestedPropertyInfo>();
}
StringBuilder nestedPropertyPath = new StringBuilder();
for (int i = 0; i < this.internalList.Count; i++)
{
object item = this.internalList[i];
this.SubscribeToINotifyPropertyChanged(item, nestedPropertyPath, i);
}
}
void IDataSourceView.UnsubscribeFromItemPropertyChanged()
{
this.shouldSubsribeToPropertyChanged = false;
foreach (var item in this.internalList)
{
this.RemovePropertyChangedHandler(item);
}
if (this.nestedObjectInfos != null)
{
foreach (var nestedItem in this.nestedObjectInfos.Keys)
{
this.RemovePropertyChangedHandler(nestedItem);
}
this.nestedObjectInfos.Clear();
}
}
private void NestedPropertyChanged(object changedItem, object rootItem, string propertyName, string nestedPropertyName = null)
{
Type changedObjectType = changedItem.GetType();
PropertyInfo propertyInfo = changedObjectType.GetRuntimeProperty(propertyName);
object changedObjectValue = propertyInfo.GetValue(changedItem);
if (changedObjectValue is INotifyPropertyChanged)
{
string pathName = nestedPropertyName + propertyName;
var keys = this.nestedObjectInfos.Where(a => a.Value.nestedPropertyPath.Contains(pathName) && a.Value.rootItem == rootItem).ToList();
foreach (var nestedItem in keys)
{
this.RemovePropertyChangedHandler(nestedItem.Key);
this.nestedObjectInfos.Remove(nestedItem.Key);
}
StringBuilder propertyNameBuilder = new StringBuilder();
nestedPropertyName += propertyName + ".";
propertyNameBuilder.Append(nestedPropertyName);
this.nestedObjectInfos.Add(changedObjectValue, new NestedPropertyInfo(rootItem, nestedPropertyName));
this.SubscribeToINotifyPropertyChanged(changedObjectValue, propertyNameBuilder, this.internalList.IndexOf(rootItem));
keys = this.nestedObjectInfos.Where(a => a.Value.nestedPropertyPath.Contains(pathName) && a.Value.rootItem == rootItem).ToList();
}
}
private void SubscribeToINotifyPropertyChanged(object item, StringBuilder nestedPropertyPath, int currentIndex)
{
if (!this.supportsPropertyChangedInitialized)
{
this.itemSupportsPropertyChanged = item is INotifyPropertyChanged;
this.supportsPropertyChangedInitialized = true;
}
this.AddPropertyChangedHandler(item);
Type itemType = item.GetType();
IEnumerable<PropertyInfo> properties = itemType.GetRuntimeProperties();
object tempItem = item;
foreach (PropertyInfo info in properties)
{
tempItem = info.GetValue(tempItem);
if (!(tempItem is INotifyPropertyChanged))
{
tempItem = item;
continue;
}
string infoName = info.Name;
nestedPropertyPath.Append(info.Name);
nestedPropertyPath.Append('.');
string propertyPath = nestedPropertyPath.ToString();
this.nestedObjectInfos.Add(tempItem, new NestedPropertyInfo(this.internalList[currentIndex], propertyPath));
this.SubscribeToINotifyPropertyChanged(tempItem, nestedPropertyPath, currentIndex);
int dotIndex = propertyPath.LastIndexOf('.');
nestedPropertyPath.Remove(dotIndex - infoName.Length, infoName.Length + 1);
}
}
object IDataSourceView.GetGroupKey(object item, int level)
{
if (this.source != null && this.source.CollectionGroups != null && level == 0)
@ -311,25 +430,70 @@ namespace Telerik.Data.Core
this.RemovePropertyChangedHandler(item);
}
if (this.nestedObjectInfos != null)
{
foreach (var item in this.nestedObjectInfos.Keys)
{
this.RemovePropertyChangedHandler(item);
}
this.nestedObjectInfos.Clear();
}
this.internalList.Clear();
}
private void InsertItem(int newIndex, object newItem)
{
this.AddPropertyChangedHandler(newItem);
this.internalList.Insert(newIndex, newItem);
if (this.shouldSubsribeToPropertyChanged)
{
if (this.nestedObjectInfos != null && this.nestedObjectInfos.Count > 0)
{
StringBuilder propertyName = new StringBuilder();
this.SubscribeToINotifyPropertyChanged(newItem, propertyName, newIndex);
}
else
{
this.AddPropertyChangedHandler(newItem);
}
}
}
private void RemoveItem(int index)
{
this.RemovePropertyChangedHandler(this.internalList[index]);
object oldItem = this.internalList[index];
this.RemovePropertyChangedHandler(oldItem);
this.internalList.RemoveAt(index);
if (this.nestedObjectInfos != null && this.nestedObjectInfos.Count > 0)
{
var keys = this.nestedObjectInfos.Where(a => a.Value.rootItem == oldItem).ToList();
foreach (var item in keys)
{
this.RemovePropertyChangedHandler(item.Key);
this.nestedObjectInfos.Remove(item.Key);
}
}
}
private void ChangeItem(int index, object newItem)
{
this.AddPropertyChangedHandler(newItem);
this.internalList[index] = newItem;
if (this.shouldSubsribeToPropertyChanged)
{
if (this.nestedObjectInfos != null && this.nestedObjectInfos.Count > 0)
{
StringBuilder propertyName = new StringBuilder();
this.SubscribeToINotifyPropertyChanged(newItem, propertyName, index);
}
else
{
this.AddPropertyChangedHandler(newItem);
}
}
}
private void AddPropertyChangedHandler(object item)
@ -340,7 +504,7 @@ namespace Telerik.Data.Core
this.supportsPropertyChangedInitialized = true;
}
if (this.itemSupportsPropertyChanged)
if (this.itemSupportsPropertyChanged && this.shouldSubsribeToPropertyChanged)
{
this.propertyChangedEventHandler.Subscribe(item);
}

Просмотреть файл

@ -3,6 +3,9 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Telerik.Core;
using Telerik.Core.Data;
using Windows.UI.Xaml.Data;
@ -14,12 +17,14 @@ namespace Telerik.Data.Core
private List<object> internalList;
private WeakEventHandler<NotifyCollectionChangedEventArgs> collectionChangedEventHandler;
private WeakEventHandlerList<PropertyChangedEventArgs> propertyChangedEventHandler;
private Dictionary<object, NestedPropertyInfo> nestedObjectInfos;
private List<Tuple<object, NotifyCollectionChangedEventArgs>> pendingCollectionChanges = new List<Tuple<object, NotifyCollectionChangedEventArgs>>();
private List<Tuple<object, int, int>> sourceGroups = new List<Tuple<object, int, int>>();
private bool itemSupportsPropertyChanged;
private bool supportsPropertyChangedInitialized;
private bool shouldSubsribeToPropertyChanged;
public EnumerableDataSourceView(IEnumerable enumerableSource)
{
@ -37,13 +42,6 @@ namespace Telerik.Data.Core
foreach (var item in enumerableSource)
{
if (!this.supportsPropertyChangedInitialized)
{
this.itemSupportsPropertyChanged = item is INotifyPropertyChanged;
this.supportsPropertyChangedInitialized = true;
}
this.AddPropertyChangedHandler(item);
this.internalList.Add(item);
}
@ -68,7 +66,7 @@ namespace Telerik.Data.Core
}
public IBatchLoadingProvider BatchDataProvider { get; private set; }
public List<Tuple<object, int, int>> SourceGroups
{
get
@ -124,11 +122,127 @@ namespace Telerik.Data.Core
INotifyPropertyChanged inpc = sender as INotifyPropertyChanged;
if (inpc != null)
{
this.HandlePropertyChanged(sender, (PropertyChangedEventArgs)args);
if (this.internalList.Contains(inpc))
{
if (this.nestedObjectInfos.Count > 0)
{
this.NestedPropertyChanged(sender, sender, ((PropertyChangedEventArgs)args).PropertyName);
}
this.HandlePropertyChanged(sender, (PropertyChangedEventArgs)args);
}
else
{
NestedPropertyInfo info;
if (this.nestedObjectInfos.TryGetValue(sender, out info))
{
string propertyName = ((PropertyChangedEventArgs)args).PropertyName;
this.NestedPropertyChanged(sender, info.rootItem, propertyName, info.nestedPropertyPath);
PropertyChangedEventArgs arguments = new PropertyChangedEventArgs(info.nestedPropertyPath + propertyName);
this.HandlePropertyChanged(info.rootItem, arguments);
}
}
}
}
}
void IDataSourceView.SubscribeToItemPropertyChanged()
{
this.shouldSubsribeToPropertyChanged = true;
if (this.nestedObjectInfos == null)
{
this.nestedObjectInfos = new Dictionary<object, NestedPropertyInfo>();
}
StringBuilder nestedPropertyPath = new StringBuilder();
for (int i = 0; i < this.internalList.Count; i++)
{
object item = this.internalList[i];
this.SubscribeToINotifyPropertyChanged(item, nestedPropertyPath, i);
}
}
void IDataSourceView.UnsubscribeFromItemPropertyChanged()
{
this.shouldSubsribeToPropertyChanged = false;
foreach (var item in this.internalList)
{
this.RemovePropertyChangedHandler(item);
}
if (this.nestedObjectInfos != null)
{
foreach (var nestedItem in this.nestedObjectInfos.Keys)
{
this.RemovePropertyChangedHandler(nestedItem);
}
this.nestedObjectInfos.Clear();
}
}
private void NestedPropertyChanged(object changedItem, object rootItem, string propertyName, string nestedPropertyName = null)
{
Type changedObjectType = changedItem.GetType();
PropertyInfo propertyInfo = changedObjectType.GetRuntimeProperty(propertyName);
object changedObjectValue = propertyInfo.GetValue(changedItem);
if (changedObjectValue is INotifyPropertyChanged)
{
string pathName = nestedPropertyName + propertyName;
var keys = this.nestedObjectInfos.Where(a => a.Value.nestedPropertyPath.Contains(pathName) && a.Value.rootItem == rootItem).ToList();
foreach (var nestedItem in keys)
{
this.RemovePropertyChangedHandler(nestedItem.Key);
this.nestedObjectInfos.Remove(nestedItem.Key);
}
StringBuilder propertyNameBuilder = new StringBuilder();
nestedPropertyName += propertyName + ".";
propertyNameBuilder.Append(nestedPropertyName);
this.nestedObjectInfos.Add(changedObjectValue, new NestedPropertyInfo(rootItem, nestedPropertyName));
this.SubscribeToINotifyPropertyChanged(changedObjectValue, propertyNameBuilder, this.internalList.IndexOf(rootItem));
keys = this.nestedObjectInfos.Where(a => a.Value.nestedPropertyPath.Contains(pathName) && a.Value.rootItem == rootItem).ToList();
}
}
private void SubscribeToINotifyPropertyChanged(object item, StringBuilder nestedPropertyPath, int currentIndex)
{
if (!this.supportsPropertyChangedInitialized)
{
this.itemSupportsPropertyChanged = item is INotifyPropertyChanged;
this.supportsPropertyChangedInitialized = true;
}
this.AddPropertyChangedHandler(item);
Type itemType = item.GetType();
IEnumerable<PropertyInfo> properties = itemType.GetRuntimeProperties();
object tempItem = item;
foreach (PropertyInfo info in properties)
{
tempItem = info.GetValue(tempItem);
if (!(tempItem is INotifyPropertyChanged))
{
tempItem = item;
continue;
}
string infoName = info.Name;
nestedPropertyPath.Append(info.Name);
nestedPropertyPath.Append('.');
string propertyPath = nestedPropertyPath.ToString();
this.nestedObjectInfos.Add(tempItem, new NestedPropertyInfo(this.internalList[currentIndex], propertyPath));
this.SubscribeToINotifyPropertyChanged(tempItem, nestedPropertyPath, currentIndex);
int dotIndex = propertyPath.LastIndexOf('.');
nestedPropertyPath.Remove(dotIndex - infoName.Length, infoName.Length + 1);
}
}
private void InitializeBatchDataLoader(ISupportIncrementalLoading supportIncrementalLoading)
{
if (supportIncrementalLoading != null)
@ -143,11 +257,7 @@ namespace Telerik.Data.Core
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
var pc = this.ItemPropertyChanged;
if (pc != null)
{
pc(sender, e);
}
this.ItemPropertyChanged?.Invoke(sender, e);
}
private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@ -226,19 +336,51 @@ namespace Telerik.Data.Core
this.RemovePropertyChangedHandler(item);
}
if (this.nestedObjectInfos != null)
{
foreach (var item in this.nestedObjectInfos.Keys)
{
this.RemovePropertyChangedHandler(item);
}
this.nestedObjectInfos.Clear();
}
this.internalList.Clear();
}
private void InsertItem(int newIndex, object newItem)
{
this.AddPropertyChangedHandler(newItem);
this.internalList.Insert(newIndex, newItem);
if (this.shouldSubsribeToPropertyChanged)
{
if (this.nestedObjectInfos != null && this.nestedObjectInfos.Count > 0)
{
StringBuilder propertyName = new StringBuilder();
this.SubscribeToINotifyPropertyChanged(newItem, propertyName, newIndex);
}
else
{
this.AddPropertyChangedHandler(newItem);
}
}
}
private void RemoveItem(int index, object oldItem)
{
this.RemovePropertyChangedHandler(oldItem);
this.internalList.RemoveAt(index);
if (this.nestedObjectInfos != null && this.nestedObjectInfos.Count > 0)
{
var keys = this.nestedObjectInfos.Where(a => a.Value.rootItem == oldItem).ToList();
foreach (var item in keys)
{
this.RemovePropertyChangedHandler(item.Key);
this.nestedObjectInfos.Remove(item.Key);
}
}
}
private void AddPropertyChangedHandler(object item)
@ -249,7 +391,7 @@ namespace Telerik.Data.Core
this.supportsPropertyChangedInitialized = true;
}
if (this.itemSupportsPropertyChanged)
if (this.itemSupportsPropertyChanged && this.shouldSubsribeToPropertyChanged)
{
this.propertyChangedEventHandler.Subscribe(item);
}

Просмотреть файл

@ -22,5 +22,8 @@ namespace Telerik.Data.Core
void Dispose();
object GetGroupKey(object item, int level);
void SubscribeToItemPropertyChanged();
void UnsubscribeFromItemPropertyChanged();
}
}

Просмотреть файл

@ -0,0 +1,14 @@
namespace Telerik.Data.Core
{
internal class NestedPropertyInfo
{
internal readonly object rootItem;
internal readonly string nestedPropertyPath;
internal NestedPropertyInfo(object rootItem, string nestedPropertyPath)
{
this.rootItem = rootItem;
this.nestedPropertyPath = nestedPropertyPath;
}
}
}

Просмотреть файл

@ -28,6 +28,7 @@ namespace Telerik.UI.Xaml.Controls.Grid.Model
private FilterDescriptorCollection filterDescriptors;
private AggregateDescriptorCollection aggregateDescriptors;
private bool isCurrentItemSynchronizing = false;
private bool updateOnPropertyChanged;
public object ItemsSource
{
@ -129,6 +130,19 @@ namespace Telerik.UI.Xaml.Controls.Grid.Model
}
}
internal bool UpdateOnPropertyChanged
{
get
{
return this.updateOnPropertyChanged;
}
set
{
this.updateOnPropertyChanged = value;
this.UpdateDataViewPropertyChangeSubscription(this.updateOnPropertyChanged);
}
}
void IDataDescriptorsHost.OnDataDescriptorPropertyChanged(DataDescriptor descriptor)
{
this.dataChangeFlags |= descriptor.UpdateFlags;
@ -212,6 +226,7 @@ namespace Telerik.UI.Xaml.Controls.Grid.Model
}
this.localDataProvider.ItemsSource = this.itemsSource;
this.UpdateDataViewPropertyChangeSubscription(this.updateOnPropertyChanged);
}
this.UpdateRequestedItems(null, false);
@ -631,5 +646,20 @@ namespace Telerik.UI.Xaml.Controls.Grid.Model
}
}
}
private void UpdateDataViewPropertyChangeSubscription(bool shouldSubscribe)
{
if (this.localDataProvider != null)
{
if (shouldSubscribe)
{
this.localDataProvider.DataView.SubscribeToItemPropertyChanged();
}
else
{
this.localDataProvider.DataView.UnsubscribeFromItemPropertyChanged();
}
}
}
}
}

Просмотреть файл

@ -130,6 +130,12 @@ namespace Telerik.UI.Xaml.Controls.Grid
public static readonly DependencyProperty GroupPanelPositionProperty =
DependencyProperty.Register(nameof(GroupPanelPosition), typeof(GroupPanelPosition), typeof(RadDataGrid), new PropertyMetadata(GroupPanelPosition.Left, OnGroupPanelPositionChanged));
/// <summary>
/// Identifies the <see cref="UpdateOnPropertyChanged"/> dependency property.
/// </summary>
public static readonly DependencyProperty UpdateOnPropertyChangedProperty =
DependencyProperty.Register(nameof(UpdateOnPropertyChanged), typeof(bool), typeof(RadDataGrid), new PropertyMetadata(false, OnUpdateOnPropertyChanged));
private DataGridColumnHeaderPanel columnHeadersPanel;
private DataGridCellsPanel cellsPanel;
private DataGridRootPanel rootPanel;
@ -574,6 +580,21 @@ namespace Telerik.UI.Xaml.Controls.Grid
}
}
/// <summary>
/// Gets or sets a value indicating whether the DataGrid should be updated if INotifyPropertyChanged item from its source is changed.
/// </summary>
public bool UpdateOnPropertyChanged
{
get
{
return (bool)this.GetValue(UpdateOnPropertyChangedProperty);
}
set
{
this.SetValue(UpdateOnPropertyChangedProperty, value);
}
}
/// <summary>
/// Gets or sets the position of the Group Panel related to the DataGrid.
/// </summary>
@ -981,6 +1002,12 @@ namespace Telerik.UI.Xaml.Controls.Grid
}
}
private static void OnUpdateOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RadDataGrid grid = d as RadDataGrid;
grid.model.UpdateOnPropertyChanged = (bool)e.NewValue;
}
private static void OnColumnResizeModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as RadDataGrid;

Просмотреть файл

@ -13,6 +13,8 @@ namespace Telerik.UI.Xaml.Controls.Grid.Tests
public string DisplayName { get; set; }
public Type RootClassType { get; }
public bool Equals(IDataFieldInfo info)
{
return false;