summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-09-08 20:39:05 +0200
committerJason Smith <jason.smith@xamarin.com>2016-09-08 11:39:05 -0700
commit85426c5d9495eb1d55b3128bf97e50c68a73b53f (patch)
tree2f81e5868ce61eb90d15c6c51a354603b8395627 /Xamarin.Forms.Core
parent11326e1c182b3ff5c3d82c6ef7d09c193bc19891 (diff)
downloadxamarin-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.cs11
-rw-r--r--Xamarin.Forms.Core/NativeBindingHelpers.cs191
-rw-r--r--Xamarin.Forms.Core/Xamarin.Forms.Core.csproj1
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>