|
|
|
@ -1,10 +1,13 @@ |
|
|
|
// Copyright (c) The Perspex Project. All rights reserved.
|
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
|
|
using Perspex.Utilities; |
|
|
|
using System; |
|
|
|
using System.Collections; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Collections.Specialized; |
|
|
|
using System.ComponentModel; |
|
|
|
using System.Globalization; |
|
|
|
using System.Linq; |
|
|
|
using System.Reflection; |
|
|
|
|
|
|
|
@ -12,21 +15,12 @@ namespace Perspex.Markup.Data |
|
|
|
{ |
|
|
|
internal class IndexerNode : ExpressionNode |
|
|
|
{ |
|
|
|
private readonly int[] _intArgs; |
|
|
|
|
|
|
|
public IndexerNode(IList<object> arguments) |
|
|
|
public IndexerNode(IList<string> arguments) |
|
|
|
{ |
|
|
|
Arguments = arguments; |
|
|
|
|
|
|
|
var intArgs = Arguments.OfType<int>().ToArray(); |
|
|
|
|
|
|
|
if (intArgs.Length == arguments.Count) |
|
|
|
{ |
|
|
|
_intArgs = intArgs; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public IList<object> Arguments { get; } |
|
|
|
public IList<string> Arguments { get; } |
|
|
|
|
|
|
|
protected override void SubscribeAndUpdate(object target) |
|
|
|
{ |
|
|
|
@ -38,6 +32,26 @@ namespace Perspex.Markup.Data |
|
|
|
{ |
|
|
|
incc.CollectionChanged += CollectionChanged; |
|
|
|
} |
|
|
|
|
|
|
|
var inpc = target as INotifyPropertyChanged; |
|
|
|
|
|
|
|
if(inpc != null) |
|
|
|
{ |
|
|
|
inpc.PropertyChanged += IndexerPropertyChanged; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void IndexerPropertyChanged(object sender, PropertyChangedEventArgs e) |
|
|
|
{ |
|
|
|
var typeInfo = sender.GetType().GetTypeInfo(); |
|
|
|
if (typeInfo.GetDeclaredProperty(e.PropertyName) == null) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
if (typeInfo.GetDeclaredProperty(e.PropertyName).GetIndexParameters().Any()) |
|
|
|
{ |
|
|
|
CurrentValue = GetValue(sender); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected override void Unsubscribe(object target) |
|
|
|
@ -52,29 +66,41 @@ namespace Perspex.Markup.Data |
|
|
|
|
|
|
|
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
|
|
|
{ |
|
|
|
bool update = false; |
|
|
|
|
|
|
|
switch (e.Action) |
|
|
|
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 |
|
|
|
{ |
|
|
|
case NotifyCollectionChangedAction.Add: |
|
|
|
update = _intArgs[0] >= e.NewStartingIndex; |
|
|
|
break; |
|
|
|
case NotifyCollectionChangedAction.Remove: |
|
|
|
update = _intArgs[0] >= e.OldStartingIndex; |
|
|
|
break; |
|
|
|
case NotifyCollectionChangedAction.Replace: |
|
|
|
update = _intArgs[0] >= e.NewStartingIndex && |
|
|
|
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count; |
|
|
|
break; |
|
|
|
case NotifyCollectionChangedAction.Move: |
|
|
|
update = (_intArgs[0] >= e.NewStartingIndex && |
|
|
|
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count) || |
|
|
|
(_intArgs[0] >= e.OldStartingIndex && |
|
|
|
_intArgs[0] < e.OldStartingIndex + e.OldItems.Count); |
|
|
|
break; |
|
|
|
case NotifyCollectionChangedAction.Reset: |
|
|
|
update = true; |
|
|
|
break; |
|
|
|
update = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (update) |
|
|
|
@ -87,34 +113,129 @@ namespace Perspex.Markup.Data |
|
|
|
{ |
|
|
|
var typeInfo = target.GetType().GetTypeInfo(); |
|
|
|
var list = target as IList; |
|
|
|
|
|
|
|
if (typeInfo.IsArray && _intArgs != null) |
|
|
|
var dictionary = target as IDictionary; |
|
|
|
var indexerProperty = GetIndexer(typeInfo); |
|
|
|
var indexerParameters = indexerProperty?.GetIndexParameters(); |
|
|
|
if (indexerProperty != null && indexerParameters.Length == Arguments.Count) |
|
|
|
{ |
|
|
|
var array = (Array)target; |
|
|
|
var convertedObjectArray = new object[indexerParameters.Length]; |
|
|
|
for (int i = 0; i < Arguments.Count; i++) |
|
|
|
{ |
|
|
|
object temp = null; |
|
|
|
if (!TypeUtilities.TryConvert(indexerParameters[i].ParameterType, Arguments[i], CultureInfo.InvariantCulture, out temp)) |
|
|
|
{ |
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
} |
|
|
|
convertedObjectArray[i] = temp; |
|
|
|
} |
|
|
|
var intArgs = convertedObjectArray.OfType<int>().ToArray(); |
|
|
|
|
|
|
|
if (InBounds(_intArgs, array)) |
|
|
|
// Try special cases where we can validate indicies
|
|
|
|
if (typeInfo.IsArray) |
|
|
|
{ |
|
|
|
return GetValueFromArray((Array)target, intArgs); |
|
|
|
} |
|
|
|
else if (Arguments.Count == 1) |
|
|
|
{ |
|
|
|
if (list != null) |
|
|
|
{ |
|
|
|
if (intArgs.Length == Arguments.Count && intArgs[0] >= 0 && intArgs[0] < list.Count) |
|
|
|
{ |
|
|
|
return list[intArgs[0]]; |
|
|
|
} |
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
} |
|
|
|
else if (dictionary != null) |
|
|
|
{ |
|
|
|
if (dictionary.Contains(convertedObjectArray[0])) |
|
|
|
{ |
|
|
|
return dictionary[convertedObjectArray[0]]; |
|
|
|
} |
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Fallback to unchecked access
|
|
|
|
return indexerProperty.GetValue(target, convertedObjectArray); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
return array.GetValue(_intArgs); |
|
|
|
// Fallback to unchecked access
|
|
|
|
return indexerProperty.GetValue(target, convertedObjectArray); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (target is IList && _intArgs?.Length == 1) |
|
|
|
// 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
|
|
|
|
else if (typeInfo.IsArray) |
|
|
|
{ |
|
|
|
return GetValueFromArray((Array)target); |
|
|
|
} |
|
|
|
|
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
} |
|
|
|
|
|
|
|
private object GetValueFromArray(Array array) |
|
|
|
{ |
|
|
|
int[] intArgs; |
|
|
|
if (!ConvertArgumentsToInts(out intArgs)) |
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
return GetValueFromArray(array, intArgs); |
|
|
|
} |
|
|
|
|
|
|
|
private object GetValueFromArray(Array array, int[] indicies) |
|
|
|
{ |
|
|
|
if (ValidBounds(indicies, array)) |
|
|
|
{ |
|
|
|
if (_intArgs[0] < list.Count) |
|
|
|
return array.GetValue(indicies); |
|
|
|
} |
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
} |
|
|
|
|
|
|
|
private bool ConvertArgumentsToInts(out int[] intArgs) |
|
|
|
{ |
|
|
|
intArgs = new int[Arguments.Count]; |
|
|
|
for (int i = 0; i < Arguments.Count; ++i) |
|
|
|
{ |
|
|
|
object value; |
|
|
|
if (!TypeUtilities.TryConvert(typeof(int), Arguments[i], CultureInfo.InvariantCulture, out value)) |
|
|
|
{ |
|
|
|
return list[_intArgs[0]]; |
|
|
|
return false; |
|
|
|
} |
|
|
|
intArgs[i] = (int)value; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
return PerspexProperty.UnsetValue; |
|
|
|
private static PropertyInfo GetIndexer(TypeInfo typeInfo) |
|
|
|
{ |
|
|
|
PropertyInfo indexer; |
|
|
|
for (;typeInfo != null; typeInfo = typeInfo.BaseType?.GetTypeInfo()) |
|
|
|
{ |
|
|
|
// 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.
|
|
|
|
if ((indexer = typeInfo.GetDeclaredProperty(CommonPropertyNames.IndexerName)) != null) |
|
|
|
{ |
|
|
|
return indexer; |
|
|
|
} |
|
|
|
foreach (var property in typeInfo.DeclaredProperties) |
|
|
|
{ |
|
|
|
if (property.GetIndexParameters().Any()) |
|
|
|
{ |
|
|
|
return property; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private bool InBounds(int[] args, Array array) |
|
|
|
private bool ValidBounds(int[] indicies, Array array) |
|
|
|
{ |
|
|
|
if (args.Length == array.Rank) |
|
|
|
if (indicies.Length == array.Rank) |
|
|
|
{ |
|
|
|
for (var i = 0; i < args.Length; ++i) |
|
|
|
for (var i = 0; i < indicies.Length; ++i) |
|
|
|
{ |
|
|
|
if (args[i] >= array.GetLength(i)) |
|
|
|
if (indicies[i] >= array.GetLength(i)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|