using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Xamarin.Forms.Internals; using static System.String; namespace Xamarin.Forms.Internals { [EditorBrowsable(EditorBrowsableState.Never)] public static class NativeBindingHelpers { public static void SetBinding(TNativeView target, string targetProperty, BindingBase bindingBase, string updateSourceEventName = null) where TNativeView : class { var binding = bindingBase as Binding; //This will allow setting bindings from Xaml by reusing the MarkupExtension if (IsNullOrEmpty(updateSourceEventName) && binding != null && !IsNullOrEmpty(binding.UpdateSourceEventName)) updateSourceEventName = binding.UpdateSourceEventName; INotifyPropertyChanged eventWrapper = null; if (!IsNullOrEmpty(updateSourceEventName)) eventWrapper = new EventWrapper(target, targetProperty, updateSourceEventName); SetBinding(target, targetProperty, bindingBase, eventWrapper); } public static void SetBinding(TNativeView target, string targetProperty, BindingBase bindingBase, INotifyPropertyChanged propertyChanged) where TNativeView : class { if (target == null) throw new ArgumentNullException(nameof(target)); if (IsNullOrEmpty(targetProperty)) throw new ArgumentNullException(nameof(targetProperty)); var binding = bindingBase as Binding; var proxy = BindableObjectProxy.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy(key)); BindableProperty bindableProperty = null; propertyChanged = propertyChanged ?? target as INotifyPropertyChanged; var propertyType = target.GetType().GetProperty(targetProperty)?.PropertyType; var defaultValue = target.GetType().GetProperty(targetProperty)?.GetMethod.Invoke(target, new object [] { }); bindableProperty = CreateBindableProperty(targetProperty, propertyType, defaultValue); if (binding != null && binding.Mode != BindingMode.OneWay && propertyChanged != null) propertyChanged.PropertyChanged += (sender, e) => { if (e.PropertyName != targetProperty) return; SetValueFromNative(sender as TNativeView, targetProperty, bindableProperty); //we need to keep the listener around he same time we have the proxy proxy.NativeINPCListener = propertyChanged; }; if (binding != null && binding.Mode != BindingMode.OneWay) SetValueFromNative(target, targetProperty, bindableProperty); proxy.SetBinding(bindableProperty, bindingBase); } static BindableProperty CreateBindableProperty(string targetProperty, Type propertyType = null, object defaultValue = null) where TNativeView : class { propertyType = propertyType ?? typeof(object); defaultValue = defaultValue ?? (propertyType.GetTypeInfo().IsValueType ? Activator.CreateInstance(propertyType) : null); return BindableProperty.Create( targetProperty, propertyType, typeof(BindableObjectProxy), defaultValue: defaultValue, defaultBindingMode: BindingMode.Default, propertyChanged: (bindable, oldValue, newValue) => { TNativeView nativeView; if ((bindable as BindableObjectProxy).TargetReference.TryGetTarget(out nativeView)) SetNativeValue(nativeView, targetProperty, newValue); } ); } static void SetNativeValue(TNativeView target, string targetProperty, object newValue) where TNativeView : class { var mi = target.GetType().GetProperty(targetProperty)?.SetMethod; if (mi == null) throw new InvalidOperationException(Format("Native Binding on {0}.{1} failed due to missing or inaccessible property", target.GetType(), targetProperty)); mi.Invoke(target, new[] { newValue }); } static void SetValueFromNative(TNativeView target, string targetProperty, BindableProperty bindableProperty) where TNativeView : class { BindableObjectProxy proxy; if (!BindableObjectProxy.BindableObjectProxies.TryGetValue(target, out proxy)) return; SetValueFromRenderer(proxy, bindableProperty, target.GetType().GetProperty(targetProperty)?.GetMethod.Invoke(target, new object [] { })); } static void SetValueFromRenderer(BindableObject bindable, BindableProperty property, object value) { bindable.SetValueCore(property, value); } public static void SetBinding(TNativeView target, BindableProperty targetProperty, BindingBase binding) where TNativeView : class { if (target == null) throw new ArgumentNullException(nameof(target)); if (targetProperty == null) throw new ArgumentNullException(nameof(targetProperty)); if (binding == null) throw new ArgumentNullException(nameof(binding)); var proxy = BindableObjectProxy.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy(key)); proxy.BindingsBackpack.Add(new KeyValuePair(targetProperty, binding)); } public static void SetValue(TNativeView target, BindableProperty targetProperty, object value) where TNativeView : class { if (target == null) throw new ArgumentNullException(nameof(target)); if (targetProperty == null) throw new ArgumentNullException(nameof(targetProperty)); var proxy = BindableObjectProxy.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy(key)); proxy.ValuesBackpack.Add(new KeyValuePair(targetProperty, value)); } public static void SetBindingContext(TNativeView target, object bindingContext, Func> getChild = null) where TNativeView : class { if (target == null) throw new ArgumentNullException(nameof(target)); var proxy = BindableObjectProxy.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy(key)); proxy.BindingContext = bindingContext; if (getChild == null) return; var children = getChild(target); if (children == null) return; foreach (var child in children) if (child != null) SetBindingContext(child, bindingContext, getChild); } public static void TransferBindablePropertiesToWrapper(TNativeView nativeView, TNativeWrapper wrapper) where TNativeView : class where TNativeWrapper : View { BindableObjectProxy proxy; if (!BindableObjectProxy.BindableObjectProxies.TryGetValue(nativeView, out proxy)) return; proxy.TransferAttachedPropertiesTo(wrapper); } class EventWrapper : INotifyPropertyChanged { string TargetProperty { get; set; } static readonly MethodInfo s_handlerinfo = typeof(EventWrapper).GetRuntimeMethods().Single(mi => mi.Name == "OnPropertyChanged" && mi.IsPublic == false); public EventWrapper(object target, string targetProperty, string updateSourceEventName) { TargetProperty = targetProperty; Delegate handlerDelegate = null; EventInfo updateSourceEvent=null; try { updateSourceEvent = target.GetType().GetRuntimeEvent(updateSourceEventName); handlerDelegate = s_handlerinfo.CreateDelegate(updateSourceEvent.EventHandlerType, this); } catch (Exception){ throw new ArgumentException(Format("No declared or accessible event {0} on {1}",updateSourceEventName,target.GetType()), nameof(updateSourceEventName)); } if (updateSourceEvent != null && handlerDelegate != null) updateSourceEvent.AddEventHandler(target, handlerDelegate); } [Preserve] void OnPropertyChanged(object sender, EventArgs e) { PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(TargetProperty)); } public event PropertyChangedEventHandler PropertyChanged; } //This needs to be internal for testing purposes internal class BindableObjectProxy : BindableObject where TNativeView : class { public static ConditionalWeakTable> BindableObjectProxies { get; } = new ConditionalWeakTable>(); public WeakReference TargetReference { get; set; } public IList> BindingsBackpack { get; } = new List>(); public IList> ValuesBackpack { get; } = new List>(); public INotifyPropertyChanged NativeINPCListener; public BindableObjectProxy(TNativeView target) { TargetReference = new WeakReference(target); } public void TransferAttachedPropertiesTo(View wrapper) { foreach (var kvp in BindingsBackpack) wrapper.SetBinding(kvp.Key, kvp.Value); foreach (var kvp in ValuesBackpack) wrapper.SetValue(kvp.Key, kvp.Value); } } } }