diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-11-15 20:39:48 +0100 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-11-15 11:39:48 -0800 |
commit | a6bbed029c64d2d64b74eeb67e27a099abf70664 (patch) | |
tree | 551c3924c055e2d39592b3f1c726cca46924dd73 /Xamarin.Forms.Core | |
parent | 14e21dcebd4a706aaa5eed384b142957d84df002 (diff) | |
download | xamarin-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.cs | 34 | ||||
-rw-r--r-- | Xamarin.Forms.Core/BindableObjectExtensions.cs | 1 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Binding.cs | 2 | ||||
-rw-r--r-- | Xamarin.Forms.Core/BindingBase.cs | 8 | ||||
-rw-r--r-- | Xamarin.Forms.Core/BindingBaseExtensions.cs | 13 | ||||
-rw-r--r-- | Xamarin.Forms.Core/BindingExpression.cs | 63 | ||||
-rw-r--r-- | Xamarin.Forms.Core/TypedBinding.cs | 291 | ||||
-rw-r--r-- | Xamarin.Forms.Core/WeakReferenceExtensions.cs | 4 | ||||
-rw-r--r-- | Xamarin.Forms.Core/Xamarin.Forms.Core.csproj | 1 |
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> |