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.
19 using System.Collections.Generic;
20 using System.Diagnostics;
21 using System.Linq.Expressions;
22 using System.Reflection;
23 using System.ComponentModel;
24 using Tizen.NUI.Binding.Internals;
27 namespace Tizen.NUI.Binding
30 /// A BindableProperty is a backing store for properties allowing bindings on BindableObject.
32 [DebuggerDisplay("{PropertyName}")]
33 [TypeConverter(typeof(BindablePropertyConverter))]
34 [EditorBrowsable(EditorBrowsableState.Never)]
35 public sealed class BindableProperty
38 /// Delegate for BindableProperty.PropertyChanged.
40 /// <param name="bindable">The bindable object that contains the property.</param>
41 /// <param name="oldValue">The old property value.</param>
42 /// <param name="newValue">The new property value.</param>
43 public delegate void BindingPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue);
46 /// Strongly-typed delegate for BindableProperty.PropertyChanged.
48 /// <typeparam name="TPropertyType">The type of the bound property.</typeparam>
49 /// <param name="bindable">The bindable object that contains the property.</param>
50 /// <param name="oldValue">The old property value.</param>
51 /// <param name="newValue">The new property value.</param>
52 public delegate void BindingPropertyChangedDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
55 /// Delegate for BindableProperty.PropertyChanging.
57 /// <param name="bindable">The bindable object that contains the property.</param>
58 /// <param name="oldValue">The old property value.</param>
59 /// <param name="newValue">The new property value.</param>
60 public delegate void BindingPropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue);
63 /// Strongly-typed delegate for BindableProperty.PropertyChanging.
65 /// <typeparam name="TPropertyType">The type of the bound property.</typeparam>
66 /// <param name="bindable">The bindable object that contains the property.</param>
67 /// <param name="oldValue">The old property value.</param>
68 /// <param name="newValue">The new property value.</param>
69 public delegate void BindingPropertyChangingDelegate<in TPropertyType>(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
72 /// Delegate for BindableProperty.CoerceValue.
74 /// <param name="bindable">The bindable object that contains the property.</param>
75 /// <param name="value">The value to coerce.</param>
76 /// <returns>System.Object</returns>
77 public delegate object CoerceValueDelegate(BindableObject bindable, object value);
80 /// Strongly-typed delegate for BindableProperty.CoerceValue.
82 /// <typeparam name="TPropertyType">The type of the bound property.</typeparam>
83 /// <param name="bindable">The bindable object that contains the property.</param>
84 /// <param name="value">The value to coerce.</param>
85 /// <returns>TPropertyType</returns>
86 public delegate TPropertyType CoerceValueDelegate<TPropertyType>(BindableObject bindable, TPropertyType value);
89 /// Delegate for BindableProperty.DefaultValueCreator.
91 /// <param name="bindable">The bindable object that contains the property.</param>
92 /// <returns>System.Object</returns>
93 public delegate object CreateDefaultValueDelegate(BindableObject bindable);
96 /// Strongly-typed delegate for BindableProperty.DefaultValueCreator.
98 /// <typeparam name="TDeclarer">The type of the object that delared the property.</typeparam>
99 /// <typeparam name="TPropertyType">The type of the bound property.</typeparam>
100 /// <param name="bindable">The bindable object that contains the property.</param>
101 /// <returns>TPropertyType</returns>
102 public delegate TPropertyType CreateDefaultValueDelegate<in TDeclarer, out TPropertyType>(TDeclarer bindable);
105 /// Delegate for BindableProperty.ValidateValue.
107 /// <param name="bindable">The bindable object that contains the property.</param>
108 /// <param name="value">The default value.</param>
109 /// <returns>System.Boolean</returns>
110 public delegate bool ValidateValueDelegate(BindableObject bindable, object value);
113 /// Strongly-typed delegate for BindableProperty.ValidateValue.
115 /// <typeparam name="TPropertyType">The type of the bound property.</typeparam>
116 /// <param name="bindable">The bindable object that contains the property.</param>
117 /// <param name="value">The default value.</param>
118 /// <returns>System.Boolean</returns>
119 public delegate bool ValidateValueDelegate<in TPropertyType>(BindableObject bindable, TPropertyType value);
121 //To confirm the static dictionary will be created before the constructor is called.
122 static BindableProperty()
126 static readonly Dictionary<Type, TypeConverter> WellKnownConvertTypes = new Dictionary<Type, TypeConverter>
128 { typeof(Uri), new UriTypeConverter() },
129 { typeof(Color), new ColorTypeConverter() },
130 { typeof(Size2D), new Size2DTypeConverter() },
131 { typeof(Position2D), new Position2DTypeConverter() },
132 { typeof(Size), new SizeTypeConverter() },
133 { typeof(Position), new PositionTypeConverter() },
134 { typeof(Rectangle), new RectangleTypeConverter() },
135 { typeof(Rotation), new RotationTypeConverter() },
136 { typeof(Vector2), new Vector2TypeConverter() },
137 { typeof(Vector3), new Vector3TypeConverter() },
138 { typeof(Vector4), new Vector4TypeConverter() },
139 { typeof(RelativeVector2), new RelativeVector2TypeConverter() },
140 { typeof(RelativeVector3), new RelativeVector3TypeConverter() },
141 { typeof(RelativeVector4), new RelativeVector4TypeConverter() },
144 //Modification for NUI XAML : user defined converter for DynamicResource can be added
145 static internal Dictionary<Type, TypeConverter> UserCustomConvertTypes = new Dictionary<Type, TypeConverter>
149 // more or less the encoding of this, without the need to reflect
150 // http://msdn.microsoft.com/en-us/library/y5b434w4.aspx
151 static readonly Dictionary<Type, Type[]> SimpleConvertTypes = new Dictionary<Type, Type[]>
153 { typeof(sbyte), new[] { typeof(string), typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
154 { typeof(byte), new[] { typeof(string), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
155 { typeof(short), new[] { typeof(string), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
156 { typeof(ushort), new[] { typeof(string), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
157 { typeof(int), new[] { typeof(string), typeof(long), typeof(float), typeof(double), typeof(decimal) } },
158 { typeof(uint), new[] { typeof(string), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
159 { typeof(long), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } },
160 { typeof(char), new[] { typeof(string), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) } },
161 { typeof(float), new[] { typeof(string), typeof(double) } },
162 { typeof(ulong), new[] { typeof(string), typeof(float), typeof(double), typeof(decimal) } }
165 BindableProperty(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
166 ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
167 CoerceValueDelegate coerceValue = null, BindablePropertyBindingChanging bindingChanging = null, bool isReadOnly = false, CreateDefaultValueDelegate defaultValueCreator = null)
169 if (propertyName == null)
170 throw new ArgumentNullException(nameof(propertyName));
171 if (ReferenceEquals(returnType, null))
172 throw new ArgumentNullException(nameof(returnType));
173 if (ReferenceEquals(declaringType, null))
174 throw new ArgumentNullException(nameof(declaringType));
176 // don't use Enum.IsDefined as its redonkulously expensive for what it does
177 if (defaultBindingMode != BindingMode.Default && defaultBindingMode != BindingMode.OneWay && defaultBindingMode != BindingMode.OneWayToSource && defaultBindingMode != BindingMode.TwoWay && defaultBindingMode != BindingMode.OneTime)
178 throw new ArgumentException("Not a valid type of BindingMode", nameof(defaultBindingMode));
179 if (defaultValue == null && Nullable.GetUnderlyingType(returnType) == null && returnType.GetTypeInfo().IsValueType)
180 throw new ArgumentException("Not a valid default value", nameof(defaultValue));
181 if (defaultValue != null && !returnType.IsInstanceOfType(defaultValue))
182 throw new ArgumentException("Default value did not match return type", nameof(defaultValue));
183 if (defaultBindingMode == BindingMode.Default)
184 defaultBindingMode = BindingMode.OneWay;
186 PropertyName = propertyName;
187 ReturnType = returnType;
188 ReturnTypeInfo = returnType.GetTypeInfo();
189 DeclaringType = declaringType;
190 DefaultValue = defaultValue;
191 DefaultBindingMode = defaultBindingMode;
192 PropertyChanged = propertyChanged;
193 PropertyChanging = propertyChanging;
194 ValidateValue = validateValue;
195 CoerceValue = coerceValue;
196 BindingChanging = bindingChanging;
197 IsReadOnly = isReadOnly;
198 DefaultValueCreator = defaultValueCreator;
200 Dictionary<string, BindableProperty> nameToBindableProperty;
201 bindablePropertyOfType.TryGetValue(declaringType, out nameToBindableProperty);
202 if (null == nameToBindableProperty)
204 nameToBindableProperty = new Dictionary<string, BindableProperty>();
205 bindablePropertyOfType.Add(declaringType, nameToBindableProperty);
208 if (!nameToBindableProperty.ContainsKey(propertyName))
210 nameToBindableProperty.Add(propertyName, this);
214 nameToBindableProperty[propertyName] = this;
218 private static bool AddParentTypeProperty(Type type, Dictionary<string, BindableProperty> propertyDict)
224 Dictionary<string, BindableProperty> nameToBindableProperty;
225 bindablePropertyOfType.TryGetValue(type, out nameToBindableProperty);
227 if (null != nameToBindableProperty)
231 foreach (KeyValuePair<string, BindableProperty> keyValuePair in nameToBindableProperty)
233 if (!propertyDict.ContainsKey(keyValuePair.Key))
235 propertyDict.Add(keyValuePair.Key, keyValuePair.Value);
240 if (true == AddParentTypeProperty(type.BaseType, propertyDict))
249 static internal Dictionary<Type, Dictionary<string, BindableProperty>> bindablePropertyOfType = new Dictionary<Type, Dictionary<string, BindableProperty>>();
250 static private HashSet<Type> baseTypePropertyHasBeenAdded = new HashSet<Type>();
252 static internal void GetBindablePropertysOfType(Type type, out Dictionary<string, BindableProperty> dictionary)
256 bindablePropertyOfType.TryGetValue(type, out dictionary);
258 if (!baseTypePropertyHasBeenAdded.Contains(type))
260 bool isCurDictNull = false;
262 if (null == dictionary)
264 isCurDictNull = true;
265 dictionary = new Dictionary<string, BindableProperty>();
268 if (true == AddParentTypeProperty(type.BaseType, dictionary) && true == isCurDictNull)
270 bindablePropertyOfType.Add(type, dictionary);
273 baseTypePropertyHasBeenAdded.Add(type);
278 /// Gets the type declaring the BindableProperty.
280 public Type DeclaringType { get; private set; }
283 /// Gets the default BindingMode.
285 public BindingMode DefaultBindingMode { get; private set; }
288 /// Gets the default value for the BindableProperty.
290 public object DefaultValue { get; }
293 /// Gets a value indicating if the BindableProperty is created form a BindablePropertyKey.
295 public bool IsReadOnly { get; private set; }
298 /// Gets the property name.
300 public string PropertyName { get; }
303 /// Gets the type of the BindableProperty.
305 public Type ReturnType { get; }
307 internal BindablePropertyBindingChanging BindingChanging { get; private set; }
309 internal CoerceValueDelegate CoerceValue { get; private set; }
311 internal CreateDefaultValueDelegate DefaultValueCreator { get; }
313 internal BindingPropertyChangedDelegate PropertyChanged { get; private set; }
315 internal BindingPropertyChangingDelegate PropertyChanging { get; private set; }
317 internal System.Reflection.TypeInfo ReturnTypeInfo { get; }
319 internal ValidateValueDelegate ValidateValue { get; private set; }
322 /// Creates a new instance of the BindableProperty class.
324 /// <param name="propertyName">The name of the BindableProperty.</param>
325 /// <param name="returnType">The type of the property.</param>
326 /// <param name="declaringType">The type of the declaring object.</param>
327 /// <param name="defaultValue">The default value for the property.</param>
328 /// <param name="defaultBindingMode">The BindingMode to use on SetBinding() if no BindingMode is given. This parameter is optional. Default is BindingMode.OneWay.</param>
329 /// <param name="validateValue">A delegate to be run when a value is set. This parameter is optional. Default is null.</param>
330 /// <param name="propertyChanged">A delegate to be run when the value has changed. This parameter is optional. Default is null.</param>
331 /// <param name="propertyChanging">A delegate to be run when the value will change. This parameter is optional. Default is null.</param>
332 /// <param name="coerceValue">A delegate used to coerce the range of a value. This parameter is optional. Default is null.</param>
333 /// <param name="defaultValueCreator">A Func used to initialize default value for reference types.</param>
334 /// <returns>A newly created BindableProperty.</returns>
335 public static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue = null, BindingMode defaultBindingMode = BindingMode.OneWay,
336 ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
337 CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
339 return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue,
340 defaultValueCreator: defaultValueCreator);
344 /// Creates a new instance of the BindableProperty class for an attached property.
346 /// <param name="propertyName">The name of the BindableProperty.</param>
347 /// <param name="returnType">The type of the property.</param>
348 /// <param name="declaringType">The type of the declaring object.</param>
349 /// <param name="defaultValue">The default value for the property.</param>
350 /// <param name="defaultBindingMode">The BindingMode to use on SetBinding() if no BindingMode is given. This parameter is optional. Default is BindingMode.OneWay.</param>
351 /// <param name="validateValue">A delegate to be run when a value is set. This parameter is optional. Default is null.</param>
352 /// <param name="propertyChanged">A delegate to be run when the value has changed. This parameter is optional. Default is null.</param>
353 /// <param name="propertyChanging">A delegate to be run when the value will change. This parameter is optional. Default is null.</param>
354 /// <param name="coerceValue">A delegate used to coerce the range of a value. This parameter is optional. Default is null.</param>
355 /// <param name="defaultValueCreator">A Func used to initialize default value for reference types.</param>
356 /// <returns>A newly created BindableProperty.</returns>
357 public static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
358 ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
359 CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
361 return CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, false, defaultValueCreator);
365 /// Creates a new instance of the BindableProperty class for attached read-only properties.
367 /// <param name="propertyName">The name of the BindableProperty.</param>
368 /// <param name="returnType">The type of the property.</param>
369 /// <param name="declaringType">The type of the declaring object.</param>
370 /// <param name="defaultValue">The default value for the property.</param>
371 /// <param name="defaultBindingMode">The BindingMode to use on SetBinding() if no BindingMode is given. This parameter is optional. Default is BindingMode.OneWay.</param>
372 /// <param name="validateValue">A delegate to be run when a value is set. This parameter is optional. Default is null.</param>
373 /// <param name="propertyChanged">A delegate to be run when the value has changed. This parameter is optional. Default is null.</param>
374 /// <param name="propertyChanging">A delegate to be run when the value will change. This parameter is optional. Default is null.</param>
375 /// <param name="coerceValue">A delegate used to coerce the range of a value. This parameter is optional. Default is null.</param>
376 /// <param name="defaultValueCreator">A Func used to initialize default value for reference types.</param>
377 /// <returns>A newly created attached read-only BindablePropertyKey.</returns>
378 public static BindablePropertyKey CreateAttachedReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
379 ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
380 CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
383 new BindablePropertyKey(CreateAttached(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, null, true,
384 defaultValueCreator));
388 /// Creates a new instance of the BindablePropertyKey class.
390 /// <param name="propertyName">The name of the BindableProperty.</param>
391 /// <param name="returnType">The type of the property.</param>
392 /// <param name="declaringType">The type of the declaring object.</param>
393 /// <param name="defaultValue">The default value for the property.</param>
394 /// <param name="defaultBindingMode">The BindingMode to use on SetBinding() if no BindingMode is given. This parameter is optional. Default is BindingMode.OneWay.</param>
395 /// <param name="validateValue">A delegate to be run when a value is set. This parameter is optional. Default is null.</param>
396 /// <param name="propertyChanged">A delegate to be run when the value has changed. This parameter is optional. Default is null.</param>
397 /// <param name="propertyChanging">A delegate to be run when the value will change. This parameter is optional. Default is null.</param>
398 /// <param name="coerceValue">A delegate used to coerce the range of a value. This parameter is optional. Default is null.</param>
399 /// <param name="defaultValueCreator">A Func used to initialize default value for reference types.</param>
400 /// <returns>A newly created BindablePropertyKey.</returns>
401 public static BindablePropertyKey CreateReadOnly(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
402 ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
403 CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
406 new BindablePropertyKey(new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue,
407 isReadOnly: true, defaultValueCreator: defaultValueCreator));
409 internal static BindableProperty Create(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
410 BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
411 CreateDefaultValueDelegate defaultValueCreator = null)
413 return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging,
414 defaultValueCreator: defaultValueCreator);
417 internal static BindableProperty CreateAttached(string propertyName, Type returnType, Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
418 BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
419 bool isReadOnly, CreateDefaultValueDelegate defaultValueCreator = null)
421 return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, isReadOnly,
422 defaultValueCreator);
425 internal object GetDefaultValue(BindableObject bindable)
427 if (DefaultValueCreator != null)
428 return DefaultValueCreator(bindable);
433 internal bool TryConvert(ref object value)
437 return !ReturnTypeInfo.IsValueType || ReturnTypeInfo.IsGenericType && ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
440 Type valueType = value.GetType();
441 Type type = ReturnType;
443 // TODO This is temporary fix before deleting CreateByXaml flag in BindableProperty.
444 if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Tizen.NUI.BaseComponents.Selector<>) && valueType.GetGenericArguments()[0] == ReturnType)
449 // Dont support arbitrary IConvertible by limiting which types can use this
450 Type[] convertableTo;
451 TypeConverter typeConverterTo;
452 if (SimpleConvertTypes.TryGetValue(valueType, out convertableTo) && Array.IndexOf(convertableTo, type) != -1)
454 value = Convert.ChangeType(value, type);
456 else if (WellKnownConvertTypes.TryGetValue(type, out typeConverterTo) && typeConverterTo.CanConvertFrom(valueType))
458 value = typeConverterTo.ConvertFromInvariantString(value.ToString());
460 else if (UserCustomConvertTypes.TryGetValue(type, out typeConverterTo) && typeConverterTo.CanConvertFrom(valueType))
462 //Modification for NUI XAML : user defined converter for DynamicResource can be added
463 value = typeConverterTo.ConvertFromInvariantString(value.ToString());
465 else if (!ReturnTypeInfo.IsAssignableFrom(valueType.GetTypeInfo()))
467 var cast = type.GetImplicitConversionOperator(fromType: valueType, toType: type)
468 ?? valueType.GetImplicitConversionOperator(fromType: valueType, toType: type);
473 value = cast.Invoke(null, new[] { value });
479 internal delegate void BindablePropertyBindingChanging(BindableObject bindable, BindingBase oldValue, BindingBase newValue);