diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-09-08 20:39:05 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-09-08 11:39:05 -0700 |
commit | 85426c5d9495eb1d55b3128bf97e50c68a73b53f (patch) | |
tree | 2f81e5868ce61eb90d15c6c51a354603b8395627 /Xamarin.Forms.Core | |
parent | 11326e1c182b3ff5c3d82c6ef7d09c193bc19891 (diff) | |
download | xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.gz xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.bz2 xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.zip |
Native Bindings (#278)
* [C, I, A, W] Support Native Bindings
* fix tabs
Diffstat (limited to 'Xamarin.Forms.Core')
-rw-r--r-- | Xamarin.Forms.Core/Binding.cs | 11 | ||||
-rw-r--r-- | Xamarin.Forms.Core/NativeBindingHelpers.cs | 191 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Xamarin.Forms.Core.csproj | 1 |
3 files changed, 202 insertions, 1 deletions
diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs index 5fa1dd65..b3baa0df 100644 --- a/Xamarin.Forms.Core/Binding.cs +++ b/Xamarin.Forms.Core/Binding.cs @@ -17,6 +17,7 @@ namespace Xamarin.Forms BindingExpression _expression; string _path; object _source; + string _updateSourceEventName; public Binding() { @@ -81,6 +82,14 @@ namespace Xamarin.Forms } } + internal string UpdateSourceEventName { + get { return _updateSourceEventName; } + set { + ThrowIfApplied(); + _updateSourceEventName = value; + } + } + public static Binding Create<TSource>(Expression<Func<TSource, object>> propertyGetter, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null, string stringFormat = null) { @@ -115,7 +124,7 @@ namespace Xamarin.Forms internal override BindingBase Clone() { - return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source }; + return new Binding(Path, Mode) { Converter = Converter, ConverterParameter = ConverterParameter, StringFormat = StringFormat, Source = Source, UpdateSourceEventName = UpdateSourceEventName }; } internal override object GetSourceValue(object value, Type targetPropertyType) diff --git a/Xamarin.Forms.Core/NativeBindingHelpers.cs b/Xamarin.Forms.Core/NativeBindingHelpers.cs new file mode 100644 index 00000000..c732efd5 --- /dev/null +++ b/Xamarin.Forms.Core/NativeBindingHelpers.cs @@ -0,0 +1,191 @@ +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 +{ + static class NativeBindingHelpers + { + public static void SetBinding<TNativeView>(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); + } + + internal static void SetBinding<TNativeView>(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<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key)); + BindableProperty bindableProperty = null; + propertyChanged = propertyChanged ?? target as INotifyPropertyChanged; + bindableProperty = CreateBindableProperty<TNativeView>(targetProperty); + if (binding != null && binding.Mode != BindingMode.OneWay && propertyChanged != null) + propertyChanged.PropertyChanged += (sender, e) => { + if (e.PropertyName != targetProperty) + return; + SetValueFromNative<TNativeView>(sender as TNativeView, targetProperty, bindableProperty); + }; + + if (binding != null && binding.Mode != BindingMode.OneWay) + SetValueFromNative(target, targetProperty, bindableProperty); + + proxy.SetBinding(bindableProperty, bindingBase); + } + + static BindableProperty CreateBindableProperty<TNativeView>(string targetProperty) where TNativeView : class + { + return BindableProperty.Create( + targetProperty, + typeof(object), + typeof(BindableObjectProxy<TNativeView>), + defaultBindingMode: BindingMode.Default, + propertyChanged: (bindable, oldValue, newValue) => { + TNativeView nativeView; + if ((bindable as BindableObjectProxy<TNativeView>).TargetReference.TryGetTarget(out nativeView)) + SetNativeValue(nativeView, targetProperty, newValue); + } + ); + } + + static void SetNativeValue<TNativeView>(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>(TNativeView target, string targetProperty, BindableProperty bindableProperty) where TNativeView : class + { + BindableObjectProxy<TNativeView> proxy; + if (!BindableObjectProxy<TNativeView>.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>(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<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key)); + proxy.BindingsBackpack.Add(new KeyValuePair<BindableProperty, BindingBase>(targetProperty, binding)); + } + + public static void SetValue<TNativeView>(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<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(key)); + proxy.ValuesBackpack.Add(new KeyValuePair<BindableProperty, object>(targetProperty, value)); + } + + public static void SetBindingContext<TNativeView>(TNativeView target, object bindingContext, Func<TNativeView, IEnumerable<TNativeView>> getChild = null) where TNativeView : class + { + if (target == null) + throw new ArgumentNullException(nameof(target)); + + var proxy = BindableObjectProxy<TNativeView>.BindableObjectProxies.GetValue(target, (TNativeView key) => new BindableObjectProxy<TNativeView>(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); + } + + internal static void TransferBindablePropertiesToWrapper<TNativeView, TNativeWrapper>(TNativeView nativeView, TNativeWrapper wrapper) + where TNativeView : class + where TNativeWrapper : View + { + BindableObjectProxy<TNativeView> proxy; + if (!BindableObjectProxy<TNativeView>.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<TNativeView> : BindableObject where TNativeView : class + { + public static ConditionalWeakTable<TNativeView, BindableObjectProxy<TNativeView>> BindableObjectProxies { get; } = new ConditionalWeakTable<TNativeView, BindableObjectProxy<TNativeView>>(); + public WeakReference<TNativeView> TargetReference { get; set; } + public IList<KeyValuePair<BindableProperty, BindingBase>> BindingsBackpack { get; } = new List<KeyValuePair<BindableProperty, BindingBase>>(); + public IList<KeyValuePair<BindableProperty, object>> ValuesBackpack { get; } = new List<KeyValuePair<BindableProperty, object>>(); + + public BindableObjectProxy(TNativeView target) + { + TargetReference = new WeakReference<TNativeView>(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); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj index 4fa202aa..9e961521 100644 --- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -432,6 +432,7 @@ <Compile Include="IAppIndexingProvider.cs" /> <Compile Include="ListStringTypeConverter.cs" /> <Compile Include="PoppedToRootEventArgs.cs" /> + <Compile Include="NativeBindingHelpers.cs" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <ItemGroup> |