A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

353 lines
12 KiB

// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.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;
using System.Reactive.Linq;
using Avalonia.Data;
namespace Avalonia.Data.Core
{
internal class IndexerNode : SettableNode
{
public IndexerNode(IList<string> arguments)
{
Arguments = arguments;
}
public override string Description => "[" + string.Join(",", Arguments) + "]";
protected override IObservable<object> StartListeningCore(WeakReference reference)
{
var target = reference.Target;
var incc = target as INotifyCollectionChanged;
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object>>();
if (incc != null)
{
inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
incc,
nameof(incc.CollectionChanged))
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
}
if (inpc != null)
{
inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>(
inpc,
nameof(inpc.PropertyChanged))
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
}
return Observable.Merge(inputs).StartWith(GetValue(target));
}
protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
var typeInfo = Target.Target.GetType().GetTypeInfo();
var list = Target.Target as IList;
var dictionary = Target.Target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
{
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 false;
}
convertedObjectArray[i] = temp;
}
var intArgs = convertedObjectArray.OfType<int>().ToArray();
// Try special cases where we can validate indicies
if (typeInfo.IsArray)
{
return SetValueInArray((Array)Target.Target, intArgs, value);
}
else if (Arguments.Count == 1)
{
if (list != null)
{
if (intArgs.Length == Arguments.Count && intArgs[0] >= 0 && intArgs[0] < list.Count)
{
list[intArgs[0]] = value;
return true;
}
return false;
}
else if (dictionary != null)
{
if (dictionary.Contains(convertedObjectArray[0]))
{
dictionary[convertedObjectArray[0]] = value;
return true;
}
else
{
dictionary.Add(convertedObjectArray[0], value);
return true;
}
}
else
{
// Fallback to unchecked access
indexerProperty.SetValue(Target.Target, value, convertedObjectArray);
return true;
}
}
else
{
// Fallback to unchecked access
indexerProperty.SetValue(Target.Target, value, convertedObjectArray);
return true;
}
}
// 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)
{
SetValueInArray((Array)Target.Target, value);
return true;
}
return false;
}
private bool SetValueInArray(Array array, object value)
{
int[] intArgs;
if (!ConvertArgumentsToInts(out intArgs))
return false;
return SetValueInArray(array, intArgs);
}
private bool SetValueInArray(Array array, int[] indicies, object value)
{
if (ValidBounds(indicies, array))
{
array.SetValue(value, indicies);
return true;
}
return false;
}
public IList<string> Arguments { get; }
public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
private object GetValue(object target)
{
var typeInfo = target.GetType().GetTypeInfo();
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
if (indexerProperty != null && indexerParameters.Length == Arguments.Count)
{
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 AvaloniaProperty.UnsetValue;
}
convertedObjectArray[i] = temp;
}
var intArgs = convertedObjectArray.OfType<int>().ToArray();
// 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 AvaloniaProperty.UnsetValue;
}
else if (dictionary != null)
{
if (dictionary.Contains(convertedObjectArray[0]))
{
return dictionary[convertedObjectArray[0]];
}
return AvaloniaProperty.UnsetValue;
}
else
{
// Fallback to unchecked access
return indexerProperty.GetValue(target, convertedObjectArray);
}
}
else
{
// Fallback to unchecked access
return indexerProperty.GetValue(target, convertedObjectArray);
}
}
// 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 AvaloniaProperty.UnsetValue;
}
private object GetValueFromArray(Array array)
{
int[] intArgs;
if (!ConvertArgumentsToInts(out intArgs))
return AvaloniaProperty.UnsetValue;
return GetValueFromArray(array, intArgs);
}
private object GetValueFromArray(Array array, int[] indicies)
{
if (ValidBounds(indicies, array))
{
return array.GetValue(indicies);
}
return AvaloniaProperty.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 false;
}
intArgs[i] = (int)value;
}
return true;
}
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 ValidBounds(int[] indicies, Array array)
{
if (indicies.Length == array.Rank)
{
for (var i = 0; i < indicies.Length; ++i)
{
if (indicies[i] >= array.GetLength(i))
{
return false;
}
}
return true;
}
else
{
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 true; // Implementation defined meaning for the index, so just try to update anyway
}
private bool ShouldUpdate(object sender, PropertyChangedEventArgs e)
{
var typeInfo = sender.GetType().GetTypeInfo();
return typeInfo.GetDeclaredProperty(e.PropertyName)?.GetIndexParameters().Any() ?? false;
}
}
}