2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Diagnostics;
5 using System.Globalization;
7 using System.Reflection;
8 using Tizen.NUI.Internals;
9 using System.Runtime.CompilerServices;
11 namespace Tizen.NUI.Binding
13 internal class BindingExpression
15 internal const string PropertyNotFoundErrorMessage = "'{0}' property not found on '{1}', target property: '{2}.{3}'";
17 readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>();
19 BindableProperty _targetProperty;
20 WeakReference<object> _weakSource;
21 WeakReference<BindableObject> _weakTarget;
23 internal BindingExpression(BindingBase binding, string path)
26 throw new ArgumentNullException(nameof(binding));
28 throw new ArgumentNullException(nameof(path));
36 internal BindingBase Binding { get; }
38 internal string Path { get; }
41 /// Applies the binding expression to a previously set source and target.
43 internal void Apply(bool fromTarget = false)
45 if (_weakSource == null || _weakTarget == null)
48 BindableObject target;
49 if (!_weakTarget.TryGetTarget(out target))
56 if (_weakSource.TryGetTarget(out source) && _targetProperty != null)
57 ApplyCore(source, target, _targetProperty, fromTarget);
61 /// Applies the binding expression to a new source or target.
63 internal void Apply(object sourceObject, BindableObject target, BindableProperty property)
65 _targetProperty = property;
67 BindableObject prevTarget;
68 if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target))
69 throw new InvalidOperationException("Binding instances can not be reused");
71 object previousSource;
72 if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject))
73 throw new InvalidOperationException("Binding instances can not be reused");
75 _weakSource = new WeakReference<object>(sourceObject);
76 _weakTarget = new WeakReference<BindableObject>(target);
78 ApplyCore(sourceObject, target, property);
81 internal void Unapply()
84 if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject))
86 for (var i = 0; i < _parts.Count - 1; i++)
88 BindingExpressionPart part = _parts[i];
92 part.TryGetValue(sourceObject, out sourceObject);
104 /// Applies the binding expression to a previously set source or target.
106 void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
108 BindingMode mode = Binding.GetRealizedMode(_targetProperty);
109 if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
112 bool needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
113 bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
115 object current = sourceObject;
116 object previous = null;
117 BindingExpressionPart part = null;
119 for (var i = 0; i < _parts.Count; i++)
122 bool isLast = i + 1 == _parts.Count;
124 if (!part.IsSelf && current != null)
126 // Allow the object instance itself to provide its own TypeInfo
127 var reflectable = current as IReflectableType;
128 System.Reflection.TypeInfo currentType = reflectable != null ? reflectable.GetTypeInfo() : current.GetType().GetTypeInfo();
129 if (part.LastGetter == null || !part.LastGetter.DeclaringType.GetTypeInfo().IsAssignableFrom(currentType))
130 SetupPart(currentType, part);
133 part.TryGetValue(current, out current);
136 if (!part.IsSelf && current != null)
138 if ((needsGetter && part.LastGetter == null) || (needsSetter && part.NextPart == null && part.LastSetter == null))
140 Console.WriteLine("Binding", PropertyNotFoundErrorMessage, part.Content, current, target.GetType(), property.PropertyName);
145 if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
147 var inpc = current as INotifyPropertyChanged;
148 if (inpc != null && !ReferenceEquals(current, previous))
149 part.Subscribe(inpc);
155 Debug.Assert(part != null, "There should always be at least the self part in the expression.");
159 object value = property.DefaultValue;
160 if (part.TryGetValue(current, out value) || part.IsSelf)
162 value = Binding.GetSourceValue(value, property.ReturnType);
165 value = property.DefaultValue;
167 if (!TryConvert(part, ref value, property.ReturnType, true))
169 Console.WriteLine("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
173 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
175 else if (needsSetter && part.LastSetter != null && current != null)
177 object value = Binding.GetTargetValue(target.GetValue(property), part.SetterType);
179 if (!TryConvert(part, ref value, part.SetterType, false))
181 Console.WriteLine("Binding", "{0} can not be converted to type '{1}'", value, part.SetterType);
188 args = new object[part.Arguments.Length + 1];
189 part.Arguments.CopyTo(args, 0);
190 args[args.Length - 1] = value;
192 else if (part.IsBindablePropertySetter)
194 args = new[] { part.BindablePropertyField, value };
198 args = new[] { value };
201 part.LastSetter.Invoke(current, args);
205 IEnumerable<BindingExpressionPart> GetPart(string part)
208 if (part == string.Empty)
209 throw new FormatException("Path contains an empty part");
211 BindingExpressionPart indexer = null;
213 int lbIndex = part.IndexOf('[');
216 int rbIndex = part.LastIndexOf(']');
218 throw new FormatException("Indexer did not contain closing bracket");
220 int argLength = rbIndex - lbIndex - 1;
222 throw new FormatException("Indexer did not contain arguments");
224 string argString = part.Substring(lbIndex + 1, argLength);
225 indexer = new BindingExpressionPart(this, argString, true);
227 part = part.Substring(0, lbIndex);
232 yield return new BindingExpressionPart(this, part);
234 yield return indexer;
239 string p = Path.Trim();
241 var last = new BindingExpressionPart(this, ".");
252 string[] pathParts = p.Split('.');
253 for (var i = 0; i < pathParts.Length; i++)
255 foreach (BindingExpressionPart part in GetPart(pathParts[i]))
257 last.NextPart = part;
264 void SetupPart(System.Reflection.TypeInfo sourceType, BindingExpressionPart part)
266 part.Arguments = null;
267 part.LastGetter = null;
268 part.LastSetter = null;
270 PropertyInfo property = null;
273 if (sourceType.IsArray)
276 if (!int.TryParse(part.Content, out index))
277 Console.WriteLine("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType);
279 part.Arguments = new object[] { index };
281 part.LastGetter = sourceType.GetDeclaredMethod("Get");
282 part.LastSetter = sourceType.GetDeclaredMethod("Set");
283 part.SetterType = sourceType.GetElementType();
286 DefaultMemberAttribute defaultMember = sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true).OfType<DefaultMemberAttribute>().FirstOrDefault();
287 string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
289 part.IndexerName = indexerName;
293 property = sourceType.GetDeclaredProperty(indexerName);
295 catch (AmbiguousMatchException) {
296 // Get most derived instance of property
297 foreach (var p in sourceType.GetProperties().Where(prop => prop.Name == indexerName)) {
298 if (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType))
303 property = sourceType.GetDeclaredProperty(indexerName);
306 if (property == null) //is the indexer defined on the base class?
307 property = sourceType.BaseType.GetProperty(indexerName);
308 if (property == null) //is the indexer defined on implemented interface ?
310 foreach (var implementedInterface in sourceType.ImplementedInterfaces)
312 property = implementedInterface.GetProperty(indexerName);
313 if (property != null)
318 if (property != null)
320 ParameterInfo parameter = property.GetIndexParameters().FirstOrDefault();
321 if (parameter != null)
325 object arg = Convert.ChangeType(part.Content, parameter.ParameterType, CultureInfo.InvariantCulture);
326 part.Arguments = new[] { arg };
328 catch (FormatException)
331 catch (InvalidCastException)
334 catch (OverflowException)
341 property = sourceType.GetDeclaredProperty(part.Content) ?? sourceType.BaseType?.GetProperty(part.Content);
343 if (property != null)
345 if (property.CanRead && property.GetMethod.IsPublic && !property.GetMethod.IsStatic)
346 part.LastGetter = property.GetMethod;
347 if (property.CanWrite && property.SetMethod.IsPublic && !property.SetMethod.IsStatic)
349 part.LastSetter = property.SetMethod;
350 part.SetterType = part.LastSetter.GetParameters().Last().ParameterType;
352 if (Binding.AllowChaining)
354 FieldInfo bindablePropertyField = sourceType.GetDeclaredField(part.Content + "Property");
355 if (bindablePropertyField != null && bindablePropertyField.FieldType == typeof(BindableProperty) && sourceType.ImplementedInterfaces.Contains(typeof(IElementController)))
357 MethodInfo setValueMethod = null;
359 foreach (MethodInfo m in sourceType.AsType().GetRuntimeMethods())
361 if (m.Name.EndsWith("IElementController.SetValueFromRenderer"))
363 ParameterInfo[] parameters = m.GetParameters();
364 if (parameters.Length == 2 && parameters[0].ParameterType == typeof(BindableProperty))
372 setValueMethod = typeof(IElementController).GetMethod("SetValueFromRenderer", new[] { typeof(BindableProperty), typeof(object) });
374 if (setValueMethod != null)
376 part.LastSetter = setValueMethod;
377 part.IsBindablePropertySetter = true;
378 part.BindablePropertyField = bindablePropertyField.GetValue(null);
384 TupleElementNamesAttribute tupleEltNames;
385 if ( property != null
386 && part.NextPart != null
387 && property.PropertyType.IsGenericType
388 && ( property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<>)
389 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,>)
390 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,>)
391 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,>)
392 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,>)
393 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>)
394 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>)
395 || property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>))
396 && (tupleEltNames = property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) as TupleElementNamesAttribute) != null)
398 //modify the nextPart to access the tuple item via the ITuple indexer
399 var nextPart = part.NextPart;
400 var name = nextPart.Content;
401 var index = tupleEltNames.TransformNames.IndexOf(name);
404 nextPart.IsIndexer = true;
405 nextPart.Content = index.ToString();
412 static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) };
414 bool TryConvert(BindingExpressionPart part, ref object value, Type convertTo, bool toTarget)
418 if ((toTarget && _targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
421 object original = value;
424 var stringValue = value as string ?? string.Empty;
425 // see: https://bugzilla.xamarin.com/show_bug.cgi?id=32871
426 // do not canonicalize "*.[.]"; "1." should not update bound BindableProperty
427 if (stringValue.EndsWith(".") && DecimalTypes.Contains(convertTo))
428 throw new FormatException();
430 // do not canonicalize "-0"; user will likely enter a period after "-0"
431 if (stringValue == "-0" && DecimalTypes.Contains(convertTo))
432 throw new FormatException();
434 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
437 catch (InvalidCastException)
442 catch (FormatException)
447 catch (OverflowException)
456 public BindingPair(BindingExpressionPart part, object source, bool isLast)
463 public bool IsLast { get; set; }
465 public BindingExpressionPart Part { get; private set; }
467 public object Source { get; private set; }
470 internal class WeakPropertyChangedProxy
472 readonly WeakReference<INotifyPropertyChanged> _source = new WeakReference<INotifyPropertyChanged>(null);
473 readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
474 readonly PropertyChangedEventHandler _handler;
475 readonly EventHandler _bchandler;
476 internal WeakReference<INotifyPropertyChanged> Source => _source;
478 public WeakPropertyChangedProxy()
480 _handler = new PropertyChangedEventHandler(OnPropertyChanged);
481 _bchandler = new EventHandler(OnBCChanged);
484 public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
486 SubscribeTo(source, listener);
489 public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
491 source.PropertyChanged += _handler;
492 var bo = source as BindableObject;
494 bo.BindingContextChanged += _bchandler;
495 _source.SetTarget(source);
496 _listener.SetTarget(listener);
499 public void Unsubscribe()
501 INotifyPropertyChanged source;
502 if (_source.TryGetTarget(out source) && source != null)
503 source.PropertyChanged -= _handler;
504 var bo = source as BindableObject;
506 bo.BindingContextChanged -= _bchandler;
508 _source.SetTarget(null);
509 _listener.SetTarget(null);
512 void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
514 PropertyChangedEventHandler handler;
515 if (_listener.TryGetTarget(out handler) && handler != null)
521 void OnBCChanged(object sender, EventArgs e)
523 OnPropertyChanged(sender, new PropertyChangedEventArgs("BindingContext"));
527 class BindingExpressionPart
529 readonly BindingExpression _expression;
530 readonly PropertyChangedEventHandler _changeHandler;
531 WeakPropertyChangedProxy _listener;
533 public BindingExpressionPart(BindingExpression expression, string content, bool isIndexer = false)
535 _expression = expression;
536 IsSelf = content == Tizen.NUI.Binding.Binding.SelfPath;
538 IsIndexer = isIndexer;
540 _changeHandler = PropertyChanged;
543 public void Subscribe(INotifyPropertyChanged handler)
545 INotifyPropertyChanged source;
546 if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source))
547 // Already subscribed
550 // Clear out the old subscription if necessary
553 _listener = new WeakPropertyChangedProxy(handler, _changeHandler);
556 public void Unsubscribe()
558 var listener = _listener;
559 if (listener != null)
561 listener.Unsubscribe();
566 public object[] Arguments { get; set; }
568 public object BindablePropertyField { get; set; }
570 public string Content { get; internal set; }
572 public string IndexerName { get; set; }
574 public bool IsBindablePropertySetter { get; set; }
576 public bool IsIndexer { get; internal set; }
578 public bool IsSelf { get; }
580 public MethodInfo LastGetter { get; set; }
582 public MethodInfo LastSetter { get; set; }
584 public BindingExpressionPart NextPart { get; set; }
586 public Type SetterType { get; set; }
588 public void PropertyChanged(object sender, PropertyChangedEventArgs args)
590 BindingExpressionPart part = NextPart ?? this;
592 string name = args.PropertyName;
594 if (!string.IsNullOrEmpty(name))
598 if (name.Contains("["))
600 if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
603 else if (name != part.IndexerName)
606 else if (name != part.Content)
612 Device.BeginInvokeOnMainThread(() => _expression.Apply());
615 public bool TryGetValue(object source, out object value)
619 if (LastGetter != null && value != null)
625 value = LastGetter.Invoke(value, Arguments);
627 catch (TargetInvocationException ex)
629 if (!(ex.InnerException is KeyNotFoundException))
635 value = LastGetter.Invoke(value, Arguments);