summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-11-15 20:39:48 +0100
committerJason Smith <jason.smith@xamarin.com>2016-11-15 11:39:48 -0800
commita6bbed029c64d2d64b74eeb67e27a099abf70664 (patch)
tree551c3924c055e2d39592b3f1c726cca46924dd73 /Xamarin.Forms.Core
parent14e21dcebd4a706aaa5eed384b142957d84df002 (diff)
downloadxamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.tar.gz
xamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.tar.bz2
xamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.zip
[XamlC] TypedBindings, some tests, a compiler, ... (#489)
Diffstat (limited to 'Xamarin.Forms.Core')
-rw-r--r--Xamarin.Forms.Core/BindableObject.cs34
-rw-r--r--Xamarin.Forms.Core/BindableObjectExtensions.cs1
-rw-r--r--Xamarin.Forms.Core/Binding.cs2
-rw-r--r--Xamarin.Forms.Core/BindingBase.cs8
-rw-r--r--Xamarin.Forms.Core/BindingBaseExtensions.cs13
-rw-r--r--Xamarin.Forms.Core/BindingExpression.cs63
-rw-r--r--Xamarin.Forms.Core/TypedBinding.cs291
-rw-r--r--Xamarin.Forms.Core/WeakReferenceExtensions.cs4
-rw-r--r--Xamarin.Forms.Core/Xamarin.Forms.Core.csproj1
9 files changed, 350 insertions, 67 deletions
diff --git a/Xamarin.Forms.Core/BindableObject.cs b/Xamarin.Forms.Core/BindableObject.cs
index f3482004..fdf2afad 100644
--- a/Xamarin.Forms.Core/BindableObject.cs
+++ b/Xamarin.Forms.Core/BindableObject.cs
@@ -9,13 +9,13 @@ namespace Xamarin.Forms
{
public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
{
- public static readonly BindableProperty BindingContextProperty = BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object), BindingMode.OneWay, null,
- BindingContextPropertyBindingPropertyChanged, null, null, BindingContextPropertyBindingChanging);
+ public static readonly BindableProperty BindingContextProperty =
+ BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object),
+ BindingMode.OneWay, null, BindingContextPropertyChanged, null, null, BindingContextPropertyBindingChanging);
readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4);
bool _applying;
-
object _inheritedContext;
public object BindingContext
@@ -114,20 +114,18 @@ namespace Xamarin.Forms
bindable._inheritedContext = value;
}
- bindable.ApplyBindings(oldContext);
+ bindable.ApplyBindings();
bindable.OnBindingContextChanged();
}
- protected void ApplyBindings(object oldContext = null)
+ protected void ApplyBindings()
{
- ApplyBindings(oldContext, false);
+ ApplyBindings(false);
}
protected virtual void OnBindingContextChanged()
{
- EventHandler change = BindingContextChanged;
- if (change != null)
- change(this, EventArgs.Empty);
+ BindingContextChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
@@ -146,8 +144,8 @@ namespace Xamarin.Forms
protected void UnapplyBindings()
{
- foreach (BindablePropertyContext context in _properties)
- {
+ for (int i = 0, _propertiesCount = _properties.Count; i < _propertiesCount; i++) {
+ BindablePropertyContext context = _properties [i];
if (context.Binding == null)
continue;
@@ -393,15 +391,16 @@ namespace Xamarin.Forms
}
}
- void ApplyBindings(object oldContext, bool skipBindingContext)
+ void ApplyBindings(bool skipBindingContext)
{
- foreach (BindablePropertyContext context in _properties.ToArray())
- {
+ var prop = _properties.ToArray();
+ for (int i = 0, propLength = prop.Length; i < propLength; i++) {
+ BindablePropertyContext context = prop [i];
BindingBase binding = context.Binding;
if (binding == null)
continue;
- if (skipBindingContext && context.Property == BindingContextProperty)
+ if (skipBindingContext && ReferenceEquals(context.Property, BindingContextProperty))
continue;
binding.Unapply();
@@ -421,11 +420,10 @@ namespace Xamarin.Forms
newBinding.Context = context;
}
- static void BindingContextPropertyBindingPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ static void BindingContextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
- object oldInheritedContext = bindable._inheritedContext;
bindable._inheritedContext = null;
- bindable.ApplyBindings(oldInheritedContext ?? oldvalue, true);
+ bindable.ApplyBindings(true);
bindable.OnBindingContextChanged();
}
diff --git a/Xamarin.Forms.Core/BindableObjectExtensions.cs b/Xamarin.Forms.Core/BindableObjectExtensions.cs
index 2eab2380..fec2ad88 100644
--- a/Xamarin.Forms.Core/BindableObjectExtensions.cs
+++ b/Xamarin.Forms.Core/BindableObjectExtensions.cs
@@ -17,6 +17,7 @@ namespace Xamarin.Forms
self.SetBinding(targetProperty, binding);
}
+ [Obsolete]
public static void SetBinding<TSource>(this BindableObject self, BindableProperty targetProperty, Expression<Func<TSource, object>> sourceProperty, BindingMode mode = BindingMode.Default,
IValueConverter converter = null, string stringFormat = null)
{
diff --git a/Xamarin.Forms.Core/Binding.cs b/Xamarin.Forms.Core/Binding.cs
index b3baa0df..71a2996b 100644
--- a/Xamarin.Forms.Core/Binding.cs
+++ b/Xamarin.Forms.Core/Binding.cs
@@ -90,6 +90,7 @@ namespace Xamarin.Forms
}
}
+ [Obsolete]
public static Binding Create<TSource>(Expression<Func<TSource, object>> propertyGetter, BindingMode mode = BindingMode.Default, IValueConverter converter = null, object converterParameter = null,
string stringFormat = null)
{
@@ -151,6 +152,7 @@ namespace Xamarin.Forms
_expression.Unapply();
}
+ [Obsolete]
static string GetBindingPath<TSource>(Expression<Func<TSource, object>> propertyGetter)
{
Expression expr = propertyGetter.Body;
diff --git a/Xamarin.Forms.Core/BindingBase.cs b/Xamarin.Forms.Core/BindingBase.cs
index 0810cbcf..fd11ac64 100644
--- a/Xamarin.Forms.Core/BindingBase.cs
+++ b/Xamarin.Forms.Core/BindingBase.cs
@@ -49,7 +49,7 @@ namespace Xamarin.Forms
public static void DisableCollectionSynchronization(IEnumerable collection)
{
if (collection == null)
- throw new ArgumentNullException("collection");
+ throw new ArgumentNullException(nameof(collection));
SynchronizedCollections.Remove(collection);
}
@@ -57,9 +57,9 @@ namespace Xamarin.Forms
public static void EnableCollectionSynchronization(IEnumerable collection, object context, CollectionSynchronizationCallback callback)
{
if (collection == null)
- throw new ArgumentNullException("collection");
+ throw new ArgumentNullException(nameof(collection));
if (callback == null)
- throw new ArgumentNullException("callback");
+ throw new ArgumentNullException(nameof(callback));
SynchronizedCollections.Add(collection, new CollectionSynchronizationContext(context, callback));
}
@@ -98,7 +98,7 @@ namespace Xamarin.Forms
internal static bool TryGetSynchronizedCollection(IEnumerable collection, out CollectionSynchronizationContext synchronizationContext)
{
if (collection == null)
- throw new ArgumentNullException("collection");
+ throw new ArgumentNullException(nameof(collection));
return SynchronizedCollections.TryGetValue(collection, out synchronizationContext);
}
diff --git a/Xamarin.Forms.Core/BindingBaseExtensions.cs b/Xamarin.Forms.Core/BindingBaseExtensions.cs
index a52c5cb1..5da40f23 100644
--- a/Xamarin.Forms.Core/BindingBaseExtensions.cs
+++ b/Xamarin.Forms.Core/BindingBaseExtensions.cs
@@ -1,16 +1,9 @@
-using System;
-
-namespace Xamarin.Forms
+namespace Xamarin.Forms
{
- internal static class BindingBaseExtensions
+ static class BindingBaseExtensions
{
- internal static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property)
+ public static BindingMode GetRealizedMode(this BindingBase self, BindableProperty property)
{
- if (self == null)
- throw new ArgumentNullException("self");
- if (property == null)
- throw new ArgumentNullException("property");
-
return self.Mode != BindingMode.Default ? self.Mode : property.DefaultBindingMode;
}
}
diff --git a/Xamarin.Forms.Core/BindingExpression.cs b/Xamarin.Forms.Core/BindingExpression.cs
index 71ba0512..204b171d 100644
--- a/Xamarin.Forms.Core/BindingExpression.cs
+++ b/Xamarin.Forms.Core/BindingExpression.cs
@@ -406,50 +406,46 @@ namespace Xamarin.Forms
public object Source { get; private set; }
}
- class WeakPropertyChangedProxy
+ internal class WeakPropertyChangedProxy
{
- WeakReference _source, _listener;
- internal WeakReference Source => _source;
+ readonly WeakReference<INotifyPropertyChanged> _source = new WeakReference<INotifyPropertyChanged>(null);
+ readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
+ readonly PropertyChangedEventHandler _handler;
+ internal WeakReference<INotifyPropertyChanged> Source => _source;
- public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
+ public WeakPropertyChangedProxy()
{
- source.PropertyChanged += OnPropertyChanged;
- _source = new WeakReference(source);
- _listener = new WeakReference(listener);
+ _handler = new PropertyChangedEventHandler(OnPropertyChanged);
+ }
+
+ public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
+ {
+ SubscribeTo(source, listener);
+ }
+
+ public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
+ {
+ source.PropertyChanged += _handler;
+ _source.SetTarget(source);
+ _listener.SetTarget(listener);
}
public void Unsubscribe()
{
- if (_source != null)
- {
- var source = _source.Target as INotifyPropertyChanged;
- if (source != null)
- {
- source.PropertyChanged -= OnPropertyChanged;
- }
- _source = null;
- _listener = null;
- }
+ INotifyPropertyChanged source;
+ if (_source.TryGetTarget(out source) && source!=null)
+ source.PropertyChanged -= _handler;
+ _source.SetTarget(null);
+ _listener.SetTarget(null);
}
- private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (_listener != null)
- {
- var handler = _listener.Target as PropertyChangedEventHandler;
- if (handler != null)
- {
- handler(sender, e);
- }
- else
- {
- Unsubscribe();
- }
- }
+ PropertyChangedEventHandler handler;
+ if (_listener.TryGetTarget(out handler) && handler != null)
+ handler(sender, e);
else
- {
Unsubscribe();
- }
}
}
@@ -471,7 +467,8 @@ namespace Xamarin.Forms
public void Subscribe(INotifyPropertyChanged handler)
{
- if (ReferenceEquals(handler, _listener?.Source?.Target))
+ INotifyPropertyChanged source;
+ if (_listener != null && _listener.Source.TryGetTarget(out source) && ReferenceEquals(handler, source))
{
// Already subscribed
return;
diff --git a/Xamarin.Forms.Core/TypedBinding.cs b/Xamarin.Forms.Core/TypedBinding.cs
new file mode 100644
index 00000000..c98b39df
--- /dev/null
+++ b/Xamarin.Forms.Core/TypedBinding.cs
@@ -0,0 +1,291 @@
+#define DO_NOT_CHECK_FOR_BINDING_REUSE
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Internals
+{
+ //FIXME: need a better name for this, and share with Binding, so we can share more unittests
+ public abstract class TypedBindingBase : BindingBase
+ {
+ IValueConverter _converter;
+ object _converterParameter;
+ object _source;
+ string _updateSourceEventName;
+
+ public IValueConverter Converter {
+ get { return _converter; }
+ set {
+ ThrowIfApplied();
+ _converter = value;
+ }
+ }
+
+ public object ConverterParameter {
+ get { return _converterParameter; }
+ set {
+ ThrowIfApplied();
+ _converterParameter = value;
+ }
+ }
+
+ public object Source {
+ get { return _source; }
+ set {
+ ThrowIfApplied();
+ _source = value;
+ }
+ }
+
+ internal string UpdateSourceEventName {
+ get { return _updateSourceEventName; }
+ set {
+ ThrowIfApplied();
+ _updateSourceEventName = value;
+ }
+ }
+
+ internal TypedBindingBase()
+ {
+ }
+ }
+
+ public sealed class TypedBinding<TSource, TProperty> : TypedBindingBase
+ {
+ readonly Func<TSource, TProperty> _getter;
+ readonly Action<TSource, TProperty> _setter;
+ readonly PropertyChangedProxy [] _handlers;
+
+ public TypedBinding(Func<TSource, TProperty> getter, Action<TSource, TProperty> setter, Tuple<Func<TSource, object>, string> [] handlers)
+ {
+ if (getter == null)
+ throw new ArgumentNullException(nameof(getter));
+
+ _getter = getter;
+ _setter = setter;
+
+ if (handlers == null)
+ return;
+
+ _handlers = new PropertyChangedProxy [handlers.Length];
+ for (var i = 0; i < handlers.Length; i++)
+ _handlers [i] = new PropertyChangedProxy(handlers [i].Item1, handlers [i].Item2, this);
+ }
+
+ readonly WeakReference<object> _weakSource = new WeakReference<object>(null);
+ readonly WeakReference<BindableObject> _weakTarget = new WeakReference<BindableObject>(null);
+ BindableProperty _targetProperty;
+
+ // Applies the binding to a previously set source and target.
+ internal override void Apply(bool fromTarget = false)
+ {
+ base.Apply(fromTarget);
+
+ BindableObject target;
+#if DO_NOT_CHECK_FOR_BINDING_REUSE
+ if (!_weakTarget.TryGetTarget(out target))
+ throw new InvalidOperationException();
+#else
+ if (!_weakTarget.TryGetTarget(out target) || target == null) {
+ Unapply();
+ return;
+ }
+#endif
+ object source;
+ if (_weakSource.TryGetTarget(out source) && source != null)
+ ApplyCore(source, target, _targetProperty, fromTarget);
+ }
+
+ // Applies the binding to a new source or target.
+ internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty)
+ {
+ _targetProperty = targetProperty;
+ var source = Source ?? Context ?? context;
+
+#if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
+ base.Apply(source, bindObj, targetProperty);
+
+ BindableObject prevTarget;
+ if (_weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, bindObj))
+ throw new InvalidOperationException("Binding instances can not be reused");
+
+ object previousSource;
+ if (_weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, source))
+ throw new InvalidOperationException("Binding instances can not be reused");
+#endif
+ _weakSource.SetTarget(source);
+ _weakTarget.SetTarget(bindObj);
+
+ ApplyCore(source, bindObj, targetProperty);
+ }
+
+ internal override BindingBase Clone()
+ {
+ Tuple<Func<TSource, object>, string> [] handlers = _handlers == null ? null : new Tuple<Func<TSource, object>, string> [_handlers.Length];
+ if (handlers != null) {
+ for (var i = 0; i < _handlers.Length; i++)
+ handlers [i] = new Tuple<Func<TSource, object>, string>(_handlers [i].PartGetter, _handlers [i].PropertyName);
+ }
+ return new TypedBinding<TSource, TProperty>(_getter, _setter, handlers) {
+ Mode = Mode,
+ Converter = Converter,
+ ConverterParameter = ConverterParameter,
+ StringFormat = StringFormat,
+ Source = Source,
+ UpdateSourceEventName = UpdateSourceEventName,
+ };
+ }
+
+ internal override object GetSourceValue(object value, Type targetPropertyType)
+ {
+ if (Converter != null)
+ value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ //return base.GetSourceValue(value, targetPropertyType);
+ if (StringFormat != null)
+ return string.Format(StringFormat, value);
+
+ return value;
+ }
+
+ internal override object GetTargetValue(object value, Type sourcePropertyType)
+ {
+ if (Converter != null)
+ value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
+
+ //return base.GetTargetValue(value, sourcePropertyType);
+ return value;
+ }
+
+ internal override void Unapply()
+ {
+#if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
+ base.Unapply();
+#endif
+ if (_handlers != null)
+ Unsubscribe();
+
+#if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
+ _weakSource.SetTarget(null);
+ _weakTarget.SetTarget(null);
+#endif
+ }
+
+ // ApplyCore is as slim as it should be:
+ // Setting 100000 values : 17ms.
+ // ApplyCore 100000 (w/o INPC, w/o unnapply) : 20ms.
+ internal void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
+ {
+ var isTSource = sourceObject != null && sourceObject is TSource;
+ var mode = this.GetRealizedMode(property);
+ if (mode == BindingMode.OneWay && fromTarget)
+ return;
+
+ var needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay;
+
+ if (isTSource && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) && _handlers != null)
+ Subscribe((TSource)sourceObject);
+
+ if (needsGetter) {
+ var value = property.DefaultValue;
+ if (isTSource) {
+ try {
+ value = GetSourceValue(_getter((TSource)sourceObject), property.ReturnType);
+ } catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException) {
+ }
+ }
+ if (!TryConvert(ref value, property, property.ReturnType, true)) {
+ Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
+ return;
+ }
+ target.SetValueCore(property, value, BindableObject.SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted);
+ return;
+ }
+
+ var needsSetter = (mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource;
+ if (needsSetter && _setter != null && isTSource) {
+ var value = GetTargetValue(target.GetValue(property), typeof(TProperty));
+ if (!TryConvert(ref value, property, typeof(TProperty), false)) {
+ Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, typeof(TProperty));
+ return;
+ }
+ _setter((TSource)sourceObject, (TProperty)value);
+ }
+ }
+
+ static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
+ {
+ if (value == null)
+ return true;
+ if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
+ return true;
+
+ object original = value;
+ try {
+ value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
+ return true;
+ } catch (Exception ex ) when (ex is InvalidCastException || ex is FormatException||ex is OverflowException) {
+ value = original;
+ return false;
+ }
+ }
+
+ class PropertyChangedProxy
+ {
+ public Func<TSource, object> PartGetter { get; }
+ public string PropertyName { get; }
+ public BindingExpression.WeakPropertyChangedProxy Listener { get; }
+ WeakReference<INotifyPropertyChanged> _weakPart = new WeakReference<INotifyPropertyChanged>(null);
+ readonly BindingBase _binding;
+
+ public INotifyPropertyChanged Part {
+ get {
+ INotifyPropertyChanged target;
+ if (_weakPart.TryGetTarget(out target))
+ return target;
+ return null;
+ }
+ set {
+ _weakPart.SetTarget(value);
+ Listener.SubscribeTo(value, OnPropertyChanged);
+ }
+ }
+
+ public PropertyChangedProxy(Func<TSource, object> partGetter, string propertyName, BindingBase binding)
+ {
+ PartGetter = partGetter;
+ PropertyName = propertyName;
+ _binding = binding;
+ Listener = new BindingExpression.WeakPropertyChangedProxy();
+ }
+
+ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (!string.IsNullOrEmpty(e.PropertyName) && string.CompareOrdinal(e.PropertyName, PropertyName) != 0)
+ return;
+ Device.BeginInvokeOnMainThread(() => _binding.Apply(false));
+ }
+ }
+
+ void Subscribe(TSource sourceObject)
+ {
+ for (var i = 0; i < _handlers.Length; i++) {
+ var part = _handlers [i].PartGetter(sourceObject);
+ if (part == null)
+ break;
+ var inpc = part as INotifyPropertyChanged;
+ if (inpc == null)
+ continue;
+ _handlers [i].Part = (inpc);
+ }
+ }
+
+ void Unsubscribe()
+ {
+ for (var i = 0; i < _handlers.Length; i++)
+ _handlers [i].Listener.Unsubscribe();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core/WeakReferenceExtensions.cs b/Xamarin.Forms.Core/WeakReferenceExtensions.cs
index e4e883a4..a35331c4 100644
--- a/Xamarin.Forms.Core/WeakReferenceExtensions.cs
+++ b/Xamarin.Forms.Core/WeakReferenceExtensions.cs
@@ -2,12 +2,12 @@
namespace Xamarin.Forms
{
- internal static class WeakReferenceExtensions
+ static class WeakReferenceExtensions
{
internal static bool TryGetTarget<T>(this WeakReference self, out T target) where T : class
{
if (self == null)
- throw new ArgumentNullException("self");
+ throw new ArgumentNullException(nameof(self));
target = (T)self.Target;
return target != null;
diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
index 7de08e7e..48bed536 100644
--- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
+++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
@@ -439,6 +439,7 @@
<Compile Include="INativeValueConverterService.cs" />
<Compile Include="INativeBindingService.cs" />
<Compile Include="ProvideCompiledAttribute.cs" />
+ <Compile Include="TypedBinding.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<ItemGroup>