2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #define DO_NOT_CHECK_FOR_BINDING_REUSE
20 using System.ComponentModel;
21 using System.Globalization;
22 using System.Collections.Generic;
24 namespace Tizen.NUI.Binding.Internals
26 //FIXME: need a better name for this, and share with Binding, so we can share more unit tests
27 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28 [EditorBrowsable(EditorBrowsableState.Never)]
29 public abstract class TypedBindingBase : BindingBase
31 IValueConverter converter;
32 object converterParameter;
34 string updateSourceEventName;
36 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
37 [EditorBrowsable(EditorBrowsableState.Never)]
38 public IValueConverter Converter
40 get { return converter; }
48 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
49 [EditorBrowsable(EditorBrowsableState.Never)]
50 public object ConverterParameter
52 get { return converterParameter; }
56 converterParameter = value;
60 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
61 [EditorBrowsable(EditorBrowsableState.Never)]
64 get { return source; }
72 internal string UpdateSourceEventName
74 get { return updateSourceEventName; }
78 updateSourceEventName = value;
82 internal TypedBindingBase()
87 internal sealed class TypedBinding<TSource, TProperty> : TypedBindingBase
89 readonly Func<TSource, TProperty> getter;
90 readonly Action<TSource, TProperty> setter;
91 readonly PropertyChangedProxy[] proxyHandlers;
93 public TypedBinding(Func<TSource, TProperty> getter, Action<TSource, TProperty> setter, Tuple<Func<TSource, object>, string>[] handlers)
96 throw new ArgumentNullException(nameof(getter));
101 if (handlers == null)
104 proxyHandlers = new PropertyChangedProxy[handlers.Length];
105 for (var i = 0; i < handlers.Length; i++)
106 proxyHandlers[i] = new PropertyChangedProxy(handlers[i].Item1, handlers[i].Item2, this);
109 readonly WeakReference<object> weakSource = new WeakReference<object>(null);
110 readonly WeakReference<BindableObject> weakTarget = new WeakReference<BindableObject>(null);
111 BindableProperty targetProperty;
113 // Applies the binding to a previously set source and target.
114 internal override void Apply(bool fromTarget = false)
116 base.Apply(fromTarget);
118 BindableObject target;
119 #if DO_NOT_CHECK_FOR_BINDING_REUSE
120 if (!weakTarget.TryGetTarget(out target))
121 throw new InvalidOperationException();
123 if (!_weakTarget.TryGetTarget(out target) || target == null) {
129 if (weakSource.TryGetTarget(out source) && source != null)
130 ApplyCore(source, target, targetProperty, fromTarget);
133 // Applies the binding to a new source or target.
134 internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged = false)
136 this.targetProperty = targetProperty;
137 var source = Source ?? Context ?? context;
138 var isApplied = IsApplied;
140 if (Source != null && isApplied && fromBindingContextChanged)
143 base.Apply(source, bindObj, targetProperty, fromBindingContextChanged);
145 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
146 BindableObject prevTarget;
147 if (_weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, bindObj))
148 throw new InvalidOperationException("Binding instances can not be reused");
150 object previousSource;
151 if (_weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, source))
152 throw new InvalidOperationException("Binding instances can not be reused");
154 weakSource.SetTarget(source);
155 weakTarget.SetTarget(bindObj);
157 ApplyCore(source, bindObj, targetProperty);
160 internal override BindingBase Clone()
162 Tuple<Func<TSource, object>, string>[] handlers = proxyHandlers == null ? null : new Tuple<Func<TSource, object>, string>[proxyHandlers.Length];
163 if (handlers != null)
165 for (var i = 0; i < proxyHandlers.Length; i++)
166 handlers[i] = new Tuple<Func<TSource, object>, string>(proxyHandlers[i].PartGetter, proxyHandlers[i].PropertyName);
168 return new TypedBinding<TSource, TProperty>(getter, setter, handlers)
171 Converter = Converter,
172 ConverterParameter = ConverterParameter,
173 StringFormat = StringFormat,
175 UpdateSourceEventName = UpdateSourceEventName,
179 internal override object GetSourceValue(object value, Type targetPropertyType)
181 if (Converter != null)
182 value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
184 //return base.GetSourceValue(value, targetPropertyType);
185 if (StringFormat != null)
186 return string.Format(StringFormat, value);
191 internal override object GetTargetValue(object value, Type sourcePropertyType)
193 if (Converter != null)
194 value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
196 //return base.GetTargetValue(value, sourcePropertyType);
200 internal override void Unapply(bool fromBindingContextChanged = false)
202 if (Source != null && fromBindingContextChanged && IsApplied)
205 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
206 base.Unapply(fromBindingContextChanged:fromBindingContextChanged);
208 if (proxyHandlers != null)
211 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
212 _weakSource.SetTarget(null);
213 _weakTarget.SetTarget(null);
217 // ApplyCore is as slim as it should be:
218 // Setting 100000 values : 17ms.
219 // ApplyCore 100000 (w/o INPC, w/o unnapply) : 20ms.
220 internal void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
222 var isTSource = sourceObject != null && sourceObject is TSource;
223 var mode = this.GetRealizedMode(property);
224 if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
227 var needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
229 if (isTSource && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) && proxyHandlers != null)
230 Subscribe((TSource)sourceObject);
234 var value = property.DefaultValue;
239 value = GetSourceValue(getter((TSource)sourceObject), property.ReturnType);
241 catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException)
245 if (!TryConvert(ref value, property, property.ReturnType, true))
247 // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
250 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted, false);
254 var needsSetter = (mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource;
255 if (needsSetter && setter != null && isTSource)
257 var value = GetTargetValue(target.GetValue(property), typeof(TProperty));
258 if (!TryConvert(ref value, property, typeof(TProperty), false))
260 // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, typeof(TProperty));
263 setter((TSource)sourceObject, (TProperty)value);
267 static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
271 if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
274 object original = value;
277 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
280 catch (Exception ex) when (ex is InvalidCastException || ex is FormatException || ex is OverflowException)
287 class PropertyChangedProxy
289 public Func<TSource, object> PartGetter { get; }
290 public string PropertyName { get; }
291 public BindingExpression.WeakPropertyChangedProxy Listener { get; }
292 WeakReference<INotifyPropertyChanged> weakPart = new WeakReference<INotifyPropertyChanged>(null);
293 readonly BindingBase binding;
295 public INotifyPropertyChanged Part
299 INotifyPropertyChanged target;
300 if (weakPart.TryGetTarget(out target))
306 weakPart.SetTarget(value);
307 Listener.SubscribeTo(value, OnPropertyChanged);
311 public PropertyChangedProxy(Func<TSource, object> partGetter, string propertyName, BindingBase binding)
313 PartGetter = partGetter;
314 PropertyName = propertyName;
315 this.binding = binding;
316 Listener = new BindingExpression.WeakPropertyChangedProxy();
319 void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
321 if (!string.IsNullOrEmpty(e.PropertyName) && string.CompareOrdinal(e.PropertyName, PropertyName) != 0)
323 Device.BeginInvokeOnMainThread(() => binding.Apply(false));
327 void Subscribe(TSource sourceObject)
329 for (var i = 0; i < proxyHandlers.Length; i++)
331 var part = proxyHandlers[i].PartGetter(sourceObject);
334 var inpc = part as INotifyPropertyChanged;
337 proxyHandlers[i].Part = (inpc);
343 for (var i = 0; i < proxyHandlers.Length; i++)
344 proxyHandlers[i].Listener.Unsubscribe();