|
|
@ -10,130 +10,47 @@ using System.ComponentModel; |
|
|
using System.Globalization; |
|
|
using System.Globalization; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
|
using System.Reflection; |
|
|
using System.Reflection; |
|
|
|
|
|
using System.Reactive.Linq; |
|
|
|
|
|
|
|
|
namespace Avalonia.Markup.Data |
|
|
namespace Avalonia.Markup.Data |
|
|
{ |
|
|
{ |
|
|
internal class IndexerNode : ExpressionNode, |
|
|
internal class IndexerNode : ExpressionNode |
|
|
IWeakSubscriber<NotifyCollectionChangedEventArgs>, |
|
|
|
|
|
IWeakSubscriber<PropertyChangedEventArgs> |
|
|
|
|
|
{ |
|
|
{ |
|
|
public IndexerNode(IList<string> arguments) |
|
|
public IndexerNode(IList<string> arguments) |
|
|
{ |
|
|
{ |
|
|
Arguments = arguments; |
|
|
Arguments = arguments; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public IList<string> Arguments { get; } |
|
|
protected override IObservable<object> StartListening(WeakReference reference) |
|
|
|
|
|
|
|
|
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
var update = false; |
|
|
|
|
|
if (sender is IList) |
|
|
|
|
|
{ |
|
|
|
|
|
object indexObject; |
|
|
|
|
|
if (!TypeUtilities.TryConvert(typeof(int), Arguments[0], CultureInfo.InvariantCulture, out indexObject)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
var index = (int)indexObject; |
|
|
|
|
|
switch (e.Action) |
|
|
|
|
|
{ |
|
|
|
|
|
case NotifyCollectionChangedAction.Add: |
|
|
|
|
|
update = index >= e.NewStartingIndex; |
|
|
|
|
|
break; |
|
|
|
|
|
case NotifyCollectionChangedAction.Remove: |
|
|
|
|
|
update = index >= e.OldStartingIndex; |
|
|
|
|
|
break; |
|
|
|
|
|
case NotifyCollectionChangedAction.Replace: |
|
|
|
|
|
update = index >= e.NewStartingIndex && |
|
|
|
|
|
index < e.NewStartingIndex + e.NewItems.Count; |
|
|
|
|
|
break; |
|
|
|
|
|
case NotifyCollectionChangedAction.Move: |
|
|
|
|
|
update = (index >= e.NewStartingIndex && |
|
|
|
|
|
index < e.NewStartingIndex + e.NewItems.Count) || |
|
|
|
|
|
(index >= e.OldStartingIndex && |
|
|
|
|
|
index < e.OldStartingIndex + e.OldItems.Count); |
|
|
|
|
|
break; |
|
|
|
|
|
case NotifyCollectionChangedAction.Reset: |
|
|
|
|
|
update = true; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
update = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (update) |
|
|
|
|
|
{ |
|
|
|
|
|
CurrentValue = new WeakReference(GetValue(sender)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void IWeakSubscriber<PropertyChangedEventArgs>.OnEvent(object sender, PropertyChangedEventArgs e) |
|
|
|
|
|
{ |
|
|
{ |
|
|
var typeInfo = sender.GetType().GetTypeInfo(); |
|
|
var target = reference.Target; |
|
|
|
|
|
|
|
|
if (typeInfo.GetDeclaredProperty(e.PropertyName) == null) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (typeInfo.GetDeclaredProperty(e.PropertyName).GetIndexParameters().Any()) |
|
|
|
|
|
{ |
|
|
|
|
|
CurrentValue = new WeakReference(GetValue(sender)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected override void SubscribeAndUpdate(WeakReference reference) |
|
|
|
|
|
{ |
|
|
|
|
|
object target = reference.Target; |
|
|
|
|
|
|
|
|
|
|
|
CurrentValue = new WeakReference(GetValue(target)); |
|
|
|
|
|
|
|
|
|
|
|
var incc = target as INotifyCollectionChanged; |
|
|
var incc = target as INotifyCollectionChanged; |
|
|
|
|
|
|
|
|
if (incc != null) |
|
|
|
|
|
{ |
|
|
|
|
|
WeakSubscriptionManager.Subscribe<NotifyCollectionChangedEventArgs>( |
|
|
|
|
|
incc, |
|
|
|
|
|
nameof(incc.CollectionChanged), |
|
|
|
|
|
this); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var inpc = target as INotifyPropertyChanged; |
|
|
var inpc = target as INotifyPropertyChanged; |
|
|
|
|
|
var inputs = new List<IObservable<object>>(); |
|
|
if (inpc != null) |
|
|
|
|
|
{ |
|
|
|
|
|
WeakSubscriptionManager.Subscribe<PropertyChangedEventArgs>( |
|
|
|
|
|
inpc, |
|
|
|
|
|
nameof(inpc.PropertyChanged), |
|
|
|
|
|
this); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected override void Unsubscribe(object target) |
|
|
|
|
|
{ |
|
|
|
|
|
var incc = target as INotifyCollectionChanged; |
|
|
|
|
|
|
|
|
|
|
|
if (incc != null) |
|
|
if (incc != null) |
|
|
{ |
|
|
{ |
|
|
WeakSubscriptionManager.Unsubscribe<NotifyCollectionChangedEventArgs>( |
|
|
inputs.Add(WeakObservable.FromEventPattern<NotifyCollectionChangedEventArgs>( |
|
|
incc, |
|
|
target, |
|
|
nameof(incc.CollectionChanged), |
|
|
nameof(incc.CollectionChanged)) |
|
|
this); |
|
|
.Where(x => ShouldUpdate(x.Sender, x.EventArgs)) |
|
|
|
|
|
.Select(_ => GetValue(target))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var inpc = target as INotifyPropertyChanged; |
|
|
|
|
|
|
|
|
|
|
|
if (inpc != null) |
|
|
if (inpc != null) |
|
|
{ |
|
|
{ |
|
|
WeakSubscriptionManager.Unsubscribe<PropertyChangedEventArgs>( |
|
|
inputs.Add(WeakObservable.FromEventPattern<PropertyChangedEventArgs>( |
|
|
inpc, |
|
|
target, |
|
|
nameof(inpc.PropertyChanged), |
|
|
nameof(inpc.PropertyChanged)) |
|
|
this); |
|
|
.Where(x => ShouldUpdate(x.Sender, x.EventArgs)) |
|
|
|
|
|
.Select(_ => GetValue(target))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Observable.Merge(inputs).StartWith(GetValue(target)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public IList<string> Arguments { get; } |
|
|
|
|
|
|
|
|
private object GetValue(object target) |
|
|
private object GetValue(object target) |
|
|
{ |
|
|
{ |
|
|
var typeInfo = target.GetType().GetTypeInfo(); |
|
|
var typeInfo = target.GetType().GetTypeInfo(); |
|
|
@ -141,18 +58,23 @@ namespace Avalonia.Markup.Data |
|
|
var dictionary = target as IDictionary; |
|
|
var dictionary = target as IDictionary; |
|
|
var indexerProperty = GetIndexer(typeInfo); |
|
|
var indexerProperty = GetIndexer(typeInfo); |
|
|
var indexerParameters = indexerProperty?.GetIndexParameters(); |
|
|
var indexerParameters = indexerProperty?.GetIndexParameters(); |
|
|
|
|
|
|
|
|
if (indexerProperty != null && indexerParameters.Length == Arguments.Count) |
|
|
if (indexerProperty != null && indexerParameters.Length == Arguments.Count) |
|
|
{ |
|
|
{ |
|
|
var convertedObjectArray = new object[indexerParameters.Length]; |
|
|
var convertedObjectArray = new object[indexerParameters.Length]; |
|
|
|
|
|
|
|
|
for (int i = 0; i < Arguments.Count; i++) |
|
|
for (int i = 0; i < Arguments.Count; i++) |
|
|
{ |
|
|
{ |
|
|
object temp = null; |
|
|
object temp = null; |
|
|
|
|
|
|
|
|
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp)) |
|
|
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp)) |
|
|
{ |
|
|
{ |
|
|
return AvaloniaProperty.UnsetValue; |
|
|
return AvaloniaProperty.UnsetValue; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
convertedObjectArray[i] = temp; |
|
|
convertedObjectArray[i] = temp; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var intArgs = convertedObjectArray.OfType<int>().ToArray(); |
|
|
var intArgs = convertedObjectArray.OfType<int>().ToArray(); |
|
|
|
|
|
|
|
|
// Try special cases where we can validate indicies
|
|
|
// Try special cases where we can validate indicies
|
|
|
@ -166,16 +88,18 @@ namespace Avalonia.Markup.Data |
|
|
{ |
|
|
{ |
|
|
if (intArgs.Length == Arguments.Count && intArgs[0] >= 0 && intArgs[0] < list.Count) |
|
|
if (intArgs.Length == Arguments.Count && intArgs[0] >= 0 && intArgs[0] < list.Count) |
|
|
{ |
|
|
{ |
|
|
return list[intArgs[0]]; |
|
|
return list[intArgs[0]]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return AvaloniaProperty.UnsetValue; |
|
|
return AvaloniaProperty.UnsetValue; |
|
|
} |
|
|
} |
|
|
else if (dictionary != null) |
|
|
else if (dictionary != null) |
|
|
{ |
|
|
{ |
|
|
if (dictionary.Contains(convertedObjectArray[0])) |
|
|
if (dictionary.Contains(convertedObjectArray[0])) |
|
|
{ |
|
|
{ |
|
|
return dictionary[convertedObjectArray[0]]; |
|
|
return dictionary[convertedObjectArray[0]]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return AvaloniaProperty.UnsetValue; |
|
|
return AvaloniaProperty.UnsetValue; |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
@ -187,11 +111,11 @@ namespace Avalonia.Markup.Data |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
// Fallback to unchecked access
|
|
|
// Fallback to unchecked access
|
|
|
return indexerProperty.GetValue(target, convertedObjectArray); |
|
|
return indexerProperty.GetValue(target, convertedObjectArray); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
// Multidimensional arrays end up here because the indexer search picks up the IList indexer instead of the
|
|
|
// Multidimensional arrays end up here because the indexer search picks up the IList indexer instead of the
|
|
|
// multidimensional indexer, which doesn't take the same number of arguments
|
|
|
// multidimensional indexer, which doesn't take the same number of arguments
|
|
|
else if (typeInfo.IsArray) |
|
|
else if (typeInfo.IsArray) |
|
|
{ |
|
|
{ |
|
|
return GetValueFromArray((Array)target); |
|
|
return GetValueFromArray((Array)target); |
|
|
@ -220,13 +144,16 @@ namespace Avalonia.Markup.Data |
|
|
private bool ConvertArgumentsToInts(out int[] intArgs) |
|
|
private bool ConvertArgumentsToInts(out int[] intArgs) |
|
|
{ |
|
|
{ |
|
|
intArgs = new int[Arguments.Count]; |
|
|
intArgs = new int[Arguments.Count]; |
|
|
|
|
|
|
|
|
for (int i = 0; i < Arguments.Count; ++i) |
|
|
for (int i = 0; i < Arguments.Count; ++i) |
|
|
{ |
|
|
{ |
|
|
object value; |
|
|
object value; |
|
|
|
|
|
|
|
|
if (!TypeUtilities.TryConvert(typeof(int), Arguments[i], CultureInfo.InvariantCulture, out value)) |
|
|
if (!TypeUtilities.TryConvert(typeof(int), Arguments[i], CultureInfo.InvariantCulture, out value)) |
|
|
{ |
|
|
{ |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
intArgs[i] = (int)value; |
|
|
intArgs[i] = (int)value; |
|
|
} |
|
|
} |
|
|
return true; |
|
|
return true; |
|
|
@ -235,7 +162,8 @@ namespace Avalonia.Markup.Data |
|
|
private static PropertyInfo GetIndexer(TypeInfo typeInfo) |
|
|
private static PropertyInfo GetIndexer(TypeInfo typeInfo) |
|
|
{ |
|
|
{ |
|
|
PropertyInfo indexer; |
|
|
PropertyInfo indexer; |
|
|
for (;typeInfo != null; typeInfo = typeInfo.BaseType?.GetTypeInfo()) |
|
|
|
|
|
|
|
|
for (; typeInfo != null; typeInfo = typeInfo.BaseType?.GetTypeInfo()) |
|
|
{ |
|
|
{ |
|
|
// Check for the default indexer name first to make this faster.
|
|
|
// Check for the default indexer name first to make this faster.
|
|
|
// This will only be false when a class in VB has a custom indexer name.
|
|
|
// This will only be false when a class in VB has a custom indexer name.
|
|
|
@ -243,14 +171,16 @@ namespace Avalonia.Markup.Data |
|
|
{ |
|
|
{ |
|
|
return indexer; |
|
|
return indexer; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
foreach (var property in typeInfo.DeclaredProperties) |
|
|
foreach (var property in typeInfo.DeclaredProperties) |
|
|
{ |
|
|
{ |
|
|
if (property.GetIndexParameters().Any()) |
|
|
if (property.GetIndexParameters().Any()) |
|
|
{ |
|
|
{ |
|
|
return property; |
|
|
return property; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -273,5 +203,46 @@ namespace Avalonia.Markup.Data |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private bool ShouldUpdate(object sender, NotifyCollectionChangedEventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
if (sender is IList) |
|
|
|
|
|
{ |
|
|
|
|
|
object indexObject; |
|
|
|
|
|
|
|
|
|
|
|
if (!TypeUtilities.TryConvert(typeof(int), Arguments[0], CultureInfo.InvariantCulture, out indexObject)) |
|
|
|
|
|
{ |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var index = (int)indexObject; |
|
|
|
|
|
|
|
|
|
|
|
switch (e.Action) |
|
|
|
|
|
{ |
|
|
|
|
|
case NotifyCollectionChangedAction.Add: |
|
|
|
|
|
return index >= e.NewStartingIndex; |
|
|
|
|
|
case NotifyCollectionChangedAction.Remove: |
|
|
|
|
|
return index >= e.OldStartingIndex; |
|
|
|
|
|
case NotifyCollectionChangedAction.Replace: |
|
|
|
|
|
return index >= e.NewStartingIndex && |
|
|
|
|
|
index < e.NewStartingIndex + e.NewItems.Count; |
|
|
|
|
|
case NotifyCollectionChangedAction.Move: |
|
|
|
|
|
return (index >= e.NewStartingIndex && |
|
|
|
|
|
index < e.NewStartingIndex + e.NewItems.Count) || |
|
|
|
|
|
(index >= e.OldStartingIndex && |
|
|
|
|
|
index < e.OldStartingIndex + e.OldItems.Count); |
|
|
|
|
|
case NotifyCollectionChangedAction.Reset: |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private bool ShouldUpdate(object sender, PropertyChangedEventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
var typeInfo = sender.GetType().GetTypeInfo(); |
|
|
|
|
|
return typeInfo.GetDeclaredProperty(e.PropertyName)?.GetIndexParameters().Any() ?? false; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|