Release 10.0.0.16997
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / BindableObject.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using System.Reflection;
23 using System.Linq;
24 using System.Runtime.CompilerServices;
25 using Tizen.NUI.Binding.Internals;
26
27 namespace Tizen.NUI.Binding
28 {
29     /// <summary>
30     /// Provides a mechanism by which application developers can propagate changes that are made to data in one object to another.
31     /// </summary>
32     /// <since_tizen> 9 </since_tizen>
33     public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
34     {
35         /// <summary>
36         /// Implements the bound property whose interface is provided by the BindingContext property.
37         /// </summary>
38         [EditorBrowsable(EditorBrowsableState.Never)]
39         public static readonly BindableProperty BindingContextProperty =
40             BindableProperty.Create(nameof(BindingContext), typeof(object), typeof(BindableObject), null, propertyChanged: (BindableProperty.BindingPropertyChangedDelegate)((bindable, oldValue, newValue) =>
41             {
42                 var bindableObject = (BindableObject)bindable;
43                 if (newValue != null)
44                 {
45                     bindableObject.bindingContext = newValue;
46                     bindableObject.FlushBinding();
47
48                     if (newValue is BindableObject targetBindableObject)
49                     {
50                         targetBindableObject.IsBinded = true;
51                     }
52                 }
53             }),
54             defaultValueCreator: (BindableProperty.CreateDefaultValueDelegate)((bindable) =>
55             {
56                 if (null != bindable.bindingContext)
57                 {
58                     return bindable.bindingContext;
59                 }
60
61                 if (bindable is Container container)
62                 {
63                     return container.Parent?.BindingContext;
64                 }
65                 else
66                 {
67                     return null;
68                 }
69             }));
70
71         readonly Dictionary<BindableProperty, BindablePropertyContext> properties = new Dictionary<BindableProperty, BindablePropertyContext>(4);
72
73         bool applying;
74         object inheritedContext;
75
76         private object bindingContext;
77
78         /// <summary>
79         /// Gets or sets object that contains the properties that will be targeted by the bound properties that belong to this BindableObject.
80         /// </summary>
81         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
82         [EditorBrowsable(EditorBrowsableState.Never)]
83         public object BindingContext
84         {
85             get { return inheritedContext ?? GetValue(BindingContextProperty); }
86             set { SetValue(BindingContextProperty, value); }
87         }
88
89         [EditorBrowsable(EditorBrowsableState.Never)]
90         public int LineNumber { get; set; } = -1;
91
92         [EditorBrowsable(EditorBrowsableState.Never)]
93         public int LinePosition { get; set; } = -1;
94
95         void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key)
96         {
97             SetDynamicResource(property, key, false);
98         }
99
100         /// <summary>
101         /// Raised when a property has changed.
102         /// </summary>
103         /// <since_tizen> 9 </since_tizen>
104         public event PropertyChangedEventHandler PropertyChanged;
105
106         /// <summary>Copy properties of other ViewStyle to this.</summary>
107         /// <param name="other">The other BindableProperty merge to this.</param>
108         [EditorBrowsable(EditorBrowsableState.Never)]
109         public virtual void CopyFrom(BindableObject other)
110         {
111             if (null == other) return;
112
113             Type type1 = this.GetType();
114             BindableProperty.GetBindablePropertysOfType(type1, out var nameToBindableProperty1);
115
116             Type type2 = other.GetType();
117             BindableProperty.GetBindablePropertysOfType(type2, out var nameToBindableProperty2);
118
119             if (null != nameToBindableProperty1)
120             {
121                 foreach (KeyValuePair<string, BindableProperty> keyValuePair in nameToBindableProperty1)
122                 {
123                     nameToBindableProperty2.TryGetValue(keyValuePair.Key, out var bindableProperty);
124
125                     if (null != bindableProperty && (SettedPropeties.Contains(bindableProperty) || other.SettedPropeties.Contains(bindableProperty)))
126                     {
127                         object value = other.GetValue(bindableProperty);
128
129                         if (null != value)
130                         {
131                             SetValue(keyValuePair.Value, value);
132                         }
133                     }
134                 }
135             }
136         }
137
138         /// <summary>
139         /// Copy all binding from other object.
140         /// </summary>
141         /// <param name="other"></param>
142         [EditorBrowsable(EditorBrowsableState.Never)]
143         public void CopyBindingRelationShip(BindableObject other)
144         {
145             if (null == other)
146             {
147                 return;
148             }
149
150             foreach (var property in properties)
151             {
152                 RemoveBinding(property.Key);
153             }
154
155             foreach (var property in other.properties)
156             {
157                 if (null != property.Value.Binding)
158                 {
159                     var binding = property.Value.Binding;
160                     other.RemoveBinding(property.Key);
161                     SetBinding(property.Key, binding);
162                 }
163             }
164         }
165
166         /// <summary>
167         /// Raised whenever the BindingContext property changes.
168         /// </summary>
169         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
170         [EditorBrowsable(EditorBrowsableState.Never)]
171         public event EventHandler BindingContextChanged;
172
173         internal void ClearValue(BindableProperty property, bool fromStyle)
174         {
175             ClearValue(property, fromStyle: fromStyle, checkAccess: true);
176         }
177
178         /// <summary>
179         /// Clears any value set by Tizen.NUI.Xaml.BindableObject.SetValue.
180         /// </summary>
181         /// <param name="property">The BindableProperty to clear</param>
182         internal void ClearValue(BindableProperty property)
183         {
184             ClearValue(property, fromStyle: false, checkAccess: true);
185         }
186
187         /// <summary>
188         /// Clears any value set by Tizen.NUI.Xaml.BindableObject.SetValue for the property that is identified by propertyKey.
189         /// </summary>
190         /// <param name="propertyKey">The BindablePropertyKey that identifies the BindableProperty to clear.</param>
191         internal void ClearValue(BindablePropertyKey propertyKey)
192         {
193             if (propertyKey == null)
194                 throw new ArgumentNullException(nameof(propertyKey));
195
196             ClearValue(propertyKey.BindableProperty, fromStyle: false, checkAccess: false);
197         }
198
199         /// <summary>
200         /// Return true if the target property exists and has been set.
201         /// </summary>
202         /// <param name="targetProperty">The target property</param>
203         /// <returns>return true if the target property exists and has been set</returns>
204         internal bool IsSet(BindableProperty targetProperty)
205         {
206             if (targetProperty == null)
207                 throw new ArgumentNullException(nameof(targetProperty));
208
209             var bpcontext = GetContext(targetProperty);
210             return bpcontext != null
211                 && (bpcontext.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
212         }
213
214         /// <summary>
215         /// Returns the value that is contained the BindableProperty.
216         /// </summary>
217         /// <param name="property">The BindableProperty for which to get the value.</param>
218         /// <returns>The value that is contained the BindableProperty</returns>
219         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
220         [EditorBrowsable(EditorBrowsableState.Never)]
221         public object GetValue(BindableProperty property)
222         {
223             if (property == null)
224                 throw new ArgumentNullException(nameof(property));
225
226             if (!IsBinded && property.ValueGetter != null)
227             {
228                 return property.ValueGetter(this);
229             }
230
231             BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
232
233             if (context == null)
234                 return property.DefaultValue;
235
236             return context.Value;
237         }
238
239         /// <summary>
240         /// Raised when a property is about to change.
241         /// </summary>
242         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
243         [EditorBrowsable(EditorBrowsableState.Never)]
244         public event PropertyChangingEventHandler PropertyChanging;
245
246         /// <summary>
247         /// Removes a previously set binding.
248         /// </summary>
249         /// <param name="property">The BindableProperty from which to remove bindings.</param>
250         internal void RemoveBinding(BindableProperty property)
251         {
252             if (property == null)
253                 throw new ArgumentNullException(nameof(property));
254
255             BindablePropertyContext context = GetContext(property);
256             if (context == null || context.Binding == null)
257                 return;
258
259             RemoveBinding(property, context);
260         }
261
262         /// <summary>
263         /// Assigns a binding to a property.
264         /// </summary>
265         /// <param name="targetProperty">The BindableProperty on which to set a binding.</param>
266         /// <param name="binding">The binding to set.</param>
267         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
268         [EditorBrowsable(EditorBrowsableState.Never)]
269         public void SetBinding(BindableProperty targetProperty, BindingBase binding)
270         {
271             SetBinding(targetProperty, binding, false);
272         }
273
274         private bool isCreateByXaml = false;
275         /// Only used by the IL of xaml, will never changed to not hidden.
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public virtual bool IsCreateByXaml
278         {
279             get
280             {
281                 return isCreateByXaml;
282             }
283             set
284             {
285                 isCreateByXaml = value;
286             }
287         }
288
289         /// <summary>
290         /// Sets the value of the specified property.
291         /// </summary>
292         /// <param name="property">The BindableProperty on which to assign a value.</param>
293         /// <param name="value">The value to set.</param>
294         /// <exception cref="ArgumentNullException"> Thrown when property is null. </exception>
295         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
296         [EditorBrowsable(EditorBrowsableState.Never)]
297         public void SetValue(BindableProperty property, object value)
298         {
299             if (true == IsBinded)
300             {
301                 SetValue(property, value, false, true);
302             }
303             else
304             {
305                 if (null == property)
306                 {
307                     throw new ArgumentNullException(nameof(property));
308                 }
309
310                 object oldvalue = null;
311                 if (null == property.DefaultValueCreator)
312                 {
313                     BindablePropertyContext context = GetOrCreateContext(property);
314                     if (null != context)
315                     {
316                         oldvalue = context.Value;
317                         context.Value = value;
318                     }
319                 }
320                 else
321                 {
322                     oldvalue = property.DefaultValueCreator.Invoke(this);
323                 }
324
325                 property.PropertyChanged?.Invoke(this, oldvalue, value);
326
327                 OnPropertyChanged(property.PropertyName);
328                 OnPropertyChangedWithData(property);
329             }
330
331             SettedPropeties.Add(property);
332         }
333
334         private HashSet<BindableProperty> settedPropeties;
335         private HashSet<BindableProperty> SettedPropeties
336         {
337             get
338             {
339                 if (null == settedPropeties)
340                 {
341                     settedPropeties = new HashSet<BindableProperty>();
342                 }
343
344                 return settedPropeties;
345             }
346         }
347
348         internal void SetValueAndForceSendChangeSignal(BindableProperty property, object value)
349         {
350             if (property == null)
351                 throw new ArgumentNullException(nameof(property));
352
353             if (true == isCreateByXaml)
354             {
355                 if (property.IsReadOnly)
356                     throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
357
358                 SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
359                     SetValuePrivateFlags.ManuallySet | SetValuePrivateFlags.CheckAccess, true);
360             }
361             else
362             {
363                 property.PropertyChanged?.Invoke(this, null, value);
364             }
365         }
366
367         /// <summary>
368         /// Sets the value of the propertyKey.
369         /// </summary>
370         /// <param name="propertyKey">The BindablePropertyKey on which to assign a value.</param>
371         /// <param name="value">The value to set.</param>
372         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
373         [EditorBrowsable(EditorBrowsableState.Never)]
374         public void SetValue(BindablePropertyKey propertyKey, object value)
375         {
376             if (propertyKey == null)
377                 throw new ArgumentNullException(nameof(propertyKey));
378
379             SetValue(propertyKey.BindableProperty, value, false, false);
380         }
381
382         /// <summary>
383         /// Set the inherited context to a neated element.
384         /// </summary>
385         /// <param name="bindable">The object on which to set the inherited binding context.</param>
386         /// <param name="value">The inherited context to set.</param>
387         /// <exception cref="ArgumentNullException"> Thrown when bindable is null. </exception>
388         [EditorBrowsable(EditorBrowsableState.Never)]
389         public static void SetInheritedBindingContext(BindableObject bindable, object value)
390         {
391             if (null == bindable)
392             {
393                 throw new ArgumentNullException(nameof(bindable));
394             }
395
396             BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty);
397             if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0))
398                 return;
399
400             object oldContext = bindable.inheritedContext;
401
402             if (ReferenceEquals(oldContext, value))
403                 return;
404
405             if (bpContext != null && oldContext == null)
406                 oldContext = bpContext.Value;
407
408             if (bpContext != null && bpContext.Binding != null)
409             {
410                 bpContext.Binding.Context = value;
411                 bindable.inheritedContext = null;
412             }
413             else
414             {
415                 bindable.inheritedContext = value;
416             }
417
418             bindable.ApplyBindings(skipBindingContext: false, fromBindingContextChanged: true);
419             bindable.OnBindingContextChanged();
420         }
421
422         /// <summary>
423         /// Register the properties which can effect each other in Binding to same group.
424         /// </summary>
425         [EditorBrowsable(EditorBrowsableState.Never)]
426         public static void RegisterPropertyGroup(BindableProperty property, HashSet<BindableProperty> group)
427         {
428             if (!PropertyToGroup.ContainsKey(property))
429             {
430                 PropertyToGroup.Add(property, group);
431             }
432
433             if (null != group && !(group.Contains(property)))
434             {
435                 group.Add(property);
436             }
437         }
438         /// <summary>
439         /// Apply the bindings to BindingContext.
440         /// </summary>
441         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
442         [EditorBrowsable(EditorBrowsableState.Never)]
443         protected void ApplyBindings()
444         {
445             ApplyBindings(skipBindingContext: false, fromBindingContextChanged: false);
446         }
447
448         /// <summary>
449         /// Override this method to execute an action when the BindingContext changes.
450         /// </summary>
451         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
452         [EditorBrowsable(EditorBrowsableState.Never)]
453         protected virtual void OnBindingContextChanged()
454         {
455             BindingContextChanged?.Invoke(this, EventArgs.Empty);
456         }
457
458         /// <summary>
459         /// Call this method from a child class to notify that a change happened on a property.
460         /// </summary>
461         /// <param name="propertyName">The name of the property that changed.</param>
462         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
463         [EditorBrowsable(EditorBrowsableState.Never)]
464         protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
465             => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
466
467         /// <summary>
468         /// Call this method from a child class to notify that a change is going to happen on a property.
469         /// </summary>
470         /// <param name="propertyName">The name of the property that is changing.</param>
471         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
472         [EditorBrowsable(EditorBrowsableState.Never)]
473         protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
474             => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
475         
476         /// <summary>
477         /// Method that is called when a bound property is changed.
478         /// </summary>
479         [EditorBrowsable(EditorBrowsableState.Never)]
480         protected virtual void OnPropertyChangedWithData(BindableProperty prop) { }
481
482         /// <summary>
483         /// Unapplies all previously set bindings.
484         /// </summary>
485         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
486         [EditorBrowsable(EditorBrowsableState.Never)]
487         protected void UnapplyBindings()
488         {
489             foreach (var context in properties.Values)
490             {
491                 if (context.Binding == null)
492                     continue;
493
494                 context.Binding.Unapply();
495             }
496         }
497
498         internal bool GetIsBound(BindableProperty targetProperty)
499         {
500             if (targetProperty == null)
501                 throw new ArgumentNullException(nameof(targetProperty));
502
503             BindablePropertyContext bpcontext = GetContext(targetProperty);
504             return bpcontext != null && bpcontext.Binding != null;
505         }
506
507         /// <summary>
508         /// Returns the value that is contained the BindableProperty.
509         /// </summary>
510         /// <param name="property0">The BindableProperty instance.</param>
511         /// <param name="property1">The BindableProperty instance.</param>
512         /// <returns>The value that is contained the BindableProperty</returns>
513         internal object[] GetValues(BindableProperty property0, BindableProperty property1)
514         {
515             var values = new object[2];
516
517             foreach (var context in properties.Values)
518             {
519                 if (ReferenceEquals(context.Property, property0))
520                 {
521                     values[0] = context.Value;
522                     property0 = null;
523                 }
524                 else if (ReferenceEquals(context.Property, property1))
525                 {
526                     values[1] = context.Value;
527                     property1 = null;
528                 }
529
530                 if (property0 == null && property1 == null)
531                     return values;
532             }
533
534             if (!ReferenceEquals(property0, null))
535                 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
536             if (!ReferenceEquals(property1, null))
537                 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
538
539             return values;
540         }
541
542         /// <summary>
543         /// Returns the value that is contained the BindableProperty.
544         /// </summary>
545         /// <param name="property0">The BindableProperty instance.</param>
546         /// <param name="property1">The BindableProperty instance.</param>
547         /// <param name="property2">The BindableProperty instance.</param>
548         /// <returns>The value that is contained the BindableProperty</returns>
549         internal object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2)
550         {
551             var values = new object[3];
552
553             foreach (var context in properties.Values)
554             {
555                 if (ReferenceEquals(context.Property, property0))
556                 {
557                     values[0] = context.Value;
558                     property0 = null;
559                 }
560                 else if (ReferenceEquals(context.Property, property1))
561                 {
562                     values[1] = context.Value;
563                     property1 = null;
564                 }
565                 else if (ReferenceEquals(context.Property, property2))
566                 {
567                     values[2] = context.Value;
568                     property2 = null;
569                 }
570
571                 if (property0 == null && property1 == null && property2 == null)
572                     return values;
573             }
574
575             if (!ReferenceEquals(property0, null))
576                 values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
577             if (!ReferenceEquals(property1, null))
578                 values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
579             if (!ReferenceEquals(property2, null))
580                 values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value;
581
582             return values;
583         }
584
585         /// <summary>
586         /// Returns the value that is contained the BindableProperty.
587         /// </summary>
588         /// <param name="properties">The array of the BindableProperty instances</param>
589         /// <returns>The values that is contained the BindableProperty instances.</returns>
590         internal object[] GetValues(params BindableProperty[] properties)
591         {
592             var values = new object[properties.Length];
593             foreach (var context in this.properties.Values)
594             {
595                 var index = properties.IndexOf(context.Property);
596                 if (index < 0)
597                     continue;
598                 values[index] = context.Value;
599             }
600             for (var i = 0; i < values.Length; i++)
601             {
602                 if (!ReferenceEquals(values[i], null))
603                     continue;
604                 values[i] = properties[i].DefaultValueCreator == null ? properties[i].DefaultValue : CreateAndAddContext(properties[i]).Value;
605             }
606             return values;
607         }
608
609         internal virtual void OnRemoveDynamicResource(BindableProperty property)
610         {
611         }
612
613         internal virtual void OnSetDynamicResource(BindableProperty property, string key)
614         {
615         }
616
617         internal void RemoveDynamicResource(BindableProperty property)
618         {
619             if (property == null)
620                 throw new ArgumentNullException(nameof(property));
621
622             OnRemoveDynamicResource(property);
623             BindablePropertyContext context = GetOrCreateContext(property);
624             context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
625         }
626
627         internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
628         {
629             if (targetProperty == null)
630                 throw new ArgumentNullException(nameof(targetProperty));
631             if (binding == null)
632                 throw new ArgumentNullException(nameof(binding));
633
634             if (fromStyle && !CanBeSetFromStyle(targetProperty))
635                 return;
636
637             IsBinded = true;
638
639             var context = GetOrCreateContext(targetProperty);
640             if (fromStyle)
641                 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
642             else
643                 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
644
645             if (context.Binding != null)
646                 context.Binding.Unapply();
647
648             BindingBase oldBinding = context.Binding;
649             context.Binding = binding;
650
651             targetProperty.BindingChanging?.Invoke(this, oldBinding, binding);
652
653             binding.Apply(BindingContext, this, targetProperty);
654         }
655
656         bool CanBeSetFromStyle(BindableProperty property)
657         {
658             var context = GetContext(property);
659             if (context == null)
660                 return true;
661             if ((context.Attributes & BindableContextAttributes.IsSetFromStyle) == BindableContextAttributes.IsSetFromStyle)
662                 return true;
663             if ((context.Attributes & BindableContextAttributes.IsDefaultValue) == BindableContextAttributes.IsDefaultValue)
664                 return true;
665             if ((context.Attributes & BindableContextAttributes.IsDefaultValueCreated) == BindableContextAttributes.IsDefaultValueCreated)
666                 return true;
667             return false;
668         }
669
670         internal void SetDynamicResource(BindableProperty property, string key)
671         {
672             SetDynamicResource(property, key, false);
673         }
674
675         internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle)
676         {
677             if (property == null)
678                 throw new ArgumentNullException(nameof(property));
679             if (string.IsNullOrEmpty(key))
680                 throw new ArgumentNullException(nameof(key));
681             if (fromStyle && !CanBeSetFromStyle(property))
682                 return;
683
684             var context = GetOrCreateContext(property);
685
686             context.Attributes |= BindableContextAttributes.IsDynamicResource;
687             if (fromStyle)
688                 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
689             else
690                 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
691
692             OnSetDynamicResource(property, key);
693         }
694
695         internal void SetValue(BindableProperty property, object value, bool fromStyle)
696         {
697             SetValue(property, value, fromStyle, true);
698         }
699
700         internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None)
701         {
702             SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None, false);
703         }
704
705         /// <summary>
706         /// For internal use.
707         /// </summary>
708         /// <param name="property">The BindableProperty on which to assign a value.</param>
709         /// <param name="value">The value to set</param>
710         /// <param name="attributes">The set value flag</param>
711         [EditorBrowsable(EditorBrowsableState.Never)]
712         internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None)
713         {
714             SetValueCore(property, value, attributes, SetValuePrivateFlags.Default, false);
715         }
716
717         internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, bool forceSendChangeSignal)
718         {
719             bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0;
720             bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0;
721             bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0;
722             bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0;
723             bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0;
724
725             if (property == null)
726                 throw new ArgumentNullException(nameof(property));
727             if (checkAccess && property.IsReadOnly)
728             {
729                 Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName);
730                 return;
731             }
732
733             if (!converted && !property.TryConvert(ref value))
734             {
735                 Console.WriteLine($"SetValue : Can not convert {value} to type {property.ReturnType}");
736                 return;
737             }
738
739             if (property.ValidateValue != null && !property.ValidateValue(this, value))
740                 throw new ArgumentException("Value was an invalid value for " + property.PropertyName, nameof(value));
741
742             if (property.CoerceValue != null)
743                 value = property.CoerceValue(this, value);
744
745             BindablePropertyContext context = GetOrCreateContext(property);
746             if (manuallySet)
747             {
748                 context.Attributes |= BindableContextAttributes.IsManuallySet;
749                 context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
750             }
751             else
752             {
753                 context.Attributes &= ~BindableContextAttributes.IsManuallySet;
754             }
755
756             if (fromStyle)
757             {
758                 context.Attributes |= BindableContextAttributes.IsSetFromStyle;
759             }
760             // else omitted on purpose
761
762             bool currentlyApplying = applying;
763
764             if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0)
765             {
766                 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
767                 if (delayQueue == null)
768                     context.DelayedSetters = delayQueue = new Queue<SetValueArgs>();
769
770                 delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes));
771             }
772             else
773             {
774                 context.Attributes |= BindableContextAttributes.IsBeingSet;
775                 SetValueActual(property, context, value, currentlyApplying, forceSendChangeSignal, attributes, silent);
776
777                 Queue<SetValueArgs> delayQueue = context.DelayedSetters;
778                 if (delayQueue != null)
779                 {
780                     while (delayQueue.Count > 0)
781                     {
782                         SetValueArgs s = delayQueue.Dequeue();
783                         SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, forceSendChangeSignal, s.Attributes);
784                     }
785
786                     context.DelayedSetters = null;
787                 }
788
789                 context.Attributes &= ~BindableContextAttributes.IsBeingSet;
790             }
791         }
792
793         internal void ApplyBindings(bool skipBindingContext, bool fromBindingContextChanged)
794         {
795             var prop = properties.Values.ToArray();
796             for (int i = 0, propLength = prop.Length; i < propLength; i++)
797             {
798                 BindablePropertyContext context = prop[i];
799                 BindingBase binding = context.Binding;
800                 if (binding == null)
801                     continue;
802
803                 if (skipBindingContext && ReferenceEquals(context.Property, BindingContextProperty))
804                     continue;
805
806                 binding.Unapply(fromBindingContextChanged: fromBindingContextChanged);
807                 binding.Apply(BindingContext, this, context.Property, fromBindingContextChanged: fromBindingContextChanged);
808             }
809         }
810
811         internal bool IsBinded
812         {
813             get;
814             set;
815         } = false;
816
817         static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase)
818         {
819             object context = bindable.inheritedContext;
820             var oldBinding = oldBindingBase as Binding;
821             var newBinding = newBindingBase as Binding;
822
823             if (context == null && oldBinding != null)
824                 context = oldBinding.Context;
825             if (context != null && newBinding != null)
826                 newBinding.Context = context;
827         }
828
829         void ClearValue(BindableProperty property, bool fromStyle, bool checkAccess)
830         {
831             if (property == null)
832                 throw new ArgumentNullException(nameof(property));
833
834             if (checkAccess && property.IsReadOnly)
835                 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
836
837             BindablePropertyContext bpcontext = GetContext(property);
838             if (bpcontext == null)
839                 return;
840
841             if (fromStyle && !CanBeSetFromStyle(property))
842                 return;
843
844             object original = bpcontext.Value;
845
846             object newValue = property.GetDefaultValue(this);
847
848             bool same = Equals(original, newValue);
849             if (!same)
850             {
851                 property.PropertyChanging?.Invoke(this, original, newValue);
852
853                 OnPropertyChanging(property.PropertyName);
854             }
855
856             bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet;
857             bpcontext.Value = newValue;
858             if (property.DefaultValueCreator == null)
859                 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue;
860             else
861                 bpcontext.Attributes |= BindableContextAttributes.IsDefaultValueCreated;
862
863             if (!same)
864             {
865                 OnPropertyChanged(property.PropertyName);
866                 OnPropertyChangedWithData(property);
867                 property.PropertyChanged?.Invoke(this, original, newValue);
868             }
869         }
870
871         [MethodImpl(MethodImplOptions.AggressiveInlining)]
872         BindablePropertyContext CreateAndAddContext(BindableProperty property)
873         {
874             var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue };
875
876             if (property.DefaultValueCreator == null)
877                 context.Attributes = BindableContextAttributes.IsDefaultValue;
878             else
879                 context.Attributes = BindableContextAttributes.IsDefaultValueCreated;
880
881             properties.Add(property, context);
882             return context;
883         }
884
885         [MethodImpl(MethodImplOptions.AggressiveInlining)]
886         BindablePropertyContext GetContext(BindableProperty property) => properties.TryGetValue(property, out var result) ? result : null;
887
888         [MethodImpl(MethodImplOptions.AggressiveInlining)]
889         BindablePropertyContext GetOrCreateContext(BindableProperty property)
890         {
891             BindablePropertyContext context = GetContext(property);
892             if (context == null)
893             {
894                 context = CreateAndAddContext(property);
895             }
896             else if (property.ValueGetter != null)
897             {
898                 context.Value = property.ValueGetter(this); //Update Value from dali
899             }//added by xiaohui.fang
900             else if (property.DefaultValueCreator != null) //This will be removed in the future.
901             {
902                 context.Value = property.DefaultValueCreator(this); //Update Value from dali
903             }//added by xb.teng
904
905             return context;
906         }
907
908         void RemoveBinding(BindableProperty property, BindablePropertyContext context)
909         {
910             context.Binding.Unapply();
911
912             property.BindingChanging?.Invoke(this, context.Binding, null);
913
914             context.Binding = null;
915         }
916
917         internal void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
918         {
919             if (property == null)
920                 throw new ArgumentNullException(nameof(property));
921
922             if (checkAccess && property.IsReadOnly)
923                 throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
924
925             if (fromStyle && !CanBeSetFromStyle(property))
926                 return;
927
928             SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
929                 (fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0),
930                 false);
931         }
932
933         void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, bool forceSendChangeSignal, SetValueFlags attributes, bool silent = false)
934         {
935             object original = context.Value;
936             bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0;
937             bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0;
938             bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0;
939             bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0;
940
941             bool same = ReferenceEquals(context.Property, BindingContextProperty) ? ReferenceEquals(value, original) : Equals(value, original);
942             if (!silent && (!same || raiseOnEqual))
943             {
944                 property.PropertyChanging?.Invoke(this, original, value);
945
946                 OnPropertyChanging(property.PropertyName);
947             }
948
949             if (!same || raiseOnEqual)
950             {
951                 context.Value = value;
952             }
953
954             context.Attributes &= ~BindableContextAttributes.IsDefaultValue;
955             context.Attributes &= ~BindableContextAttributes.IsDefaultValueCreated;
956
957             if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources)
958                 RemoveDynamicResource(property);
959
960             BindingBase binding = context.Binding;
961             if (binding != null)
962             {
963                 if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay)
964                 {
965                     RemoveBinding(property, context);
966                     binding = null;
967                 }
968             }
969
970             PropertyToGroup.TryGetValue(property, out HashSet<BindableProperty> propertyGroup);
971
972             if (!silent)
973             {
974                 if ((!same || raiseOnEqual))
975                 {
976                     property.PropertyChanged?.Invoke(this, original, value);
977
978                     if (binding != null && !currentlyApplying)
979                     {
980                         applying = true;
981                         binding.Apply(true);
982                         applying = false;
983                     }
984
985                     OnPropertyChanged(property.PropertyName);
986
987                     if (null != propertyGroup)
988                     {
989                         foreach (var relativeProperty in propertyGroup)
990                         {
991                             if (relativeProperty != property)
992                             {
993                                 var relativeContext = GetOrCreateContext(relativeProperty);
994                                 var relativeBinding = relativeContext.Binding;
995
996                                 if (null != relativeBinding)
997                                 {
998                                     applying = true;
999                                     relativeBinding.Apply(true);
1000                                     applying = false;
1001                                 }
1002
1003                                 OnPropertyChanged(relativeProperty.PropertyName);
1004                             }
1005                         }
1006                     }
1007                 }
1008                 else if (true == same && true == forceSendChangeSignal)
1009                 {
1010                     if (binding != null && !currentlyApplying)
1011                     {
1012                         applying = true;
1013                         binding.Apply(true);
1014                         applying = false;
1015                     }
1016
1017                     OnPropertyChanged(property.PropertyName);
1018
1019                     if (null != propertyGroup)
1020                     {
1021                         foreach (var relativeProperty in propertyGroup)
1022                         {
1023                             if (relativeProperty != property)
1024                             {
1025                                 var relativeContext = GetOrCreateContext(relativeProperty);
1026                                 var relativeBinding = relativeContext.Binding;
1027
1028                                 if (null != relativeBinding)
1029                                 {
1030                                     applying = true;
1031                                     relativeBinding.Apply(true);
1032                                     applying = false;
1033                                 }
1034
1035                                 OnPropertyChanged(relativeProperty.PropertyName);
1036                             }
1037                         }
1038                     }
1039                 }
1040
1041                 OnPropertyChangedWithData(property);
1042             }
1043         }
1044
1045         private static Dictionary<BindableProperty, HashSet<BindableProperty>> PropertyToGroup { get; }
1046             = new Dictionary<BindableProperty, HashSet<BindableProperty>>();
1047
1048         [Flags]
1049         enum BindableContextAttributes
1050         {
1051             IsManuallySet = 1 << 0,
1052             IsBeingSet = 1 << 1,
1053             IsDynamicResource = 1 << 2,
1054             IsSetFromStyle = 1 << 3,
1055             IsDefaultValue = 1 << 4,
1056             IsDefaultValueCreated = 1 << 5,
1057         }
1058
1059         class BindablePropertyContext
1060         {
1061             public BindableContextAttributes Attributes;
1062             public BindingBase Binding;
1063             public Queue<SetValueArgs> DelayedSetters;
1064             public BindableProperty Property;
1065             public object Value;
1066         }
1067
1068         [Flags]
1069         internal enum SetValuePrivateFlags
1070         {
1071             None = 0,
1072             CheckAccess = 1 << 0,
1073             Silent = 1 << 1,
1074             ManuallySet = 1 << 2,
1075             FromStyle = 1 << 3,
1076             Converted = 1 << 4,
1077             Default = CheckAccess
1078         }
1079
1080         class SetValueArgs
1081         {
1082             public readonly SetValueFlags Attributes;
1083             public readonly BindablePropertyContext Context;
1084             public readonly bool CurrentlyApplying;
1085             public readonly BindableProperty Property;
1086             public readonly object Value;
1087
1088             public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes)
1089             {
1090                 Property = property;
1091                 Context = context;
1092                 Value = value;
1093                 CurrentlyApplying = currentlyApplying;
1094                 Attributes = attributes;
1095             }
1096         }
1097
1098         internal void AddChildBindableObject(BindableObject child)
1099         {
1100             if (null != child)
1101             {
1102                 children.Add(child);
1103                 child.FlushBinding();
1104             }
1105         }
1106
1107         internal void RemoveChildBindableObject(BindableObject child)
1108         {
1109             children.Remove(child);
1110         }
1111
1112         internal void ReplaceBindingElement(Dictionary<string, object> oldNameScope, Dictionary<string, object> newNameScope)
1113         {
1114             var xElementToNameOfOld = new Dictionary<object, string>();
1115
1116             foreach (var pair in oldNameScope)
1117             {
1118                 xElementToNameOfOld.Add(pair.Value, pair.Key);
1119             }
1120
1121             foreach (var property in properties)
1122             {
1123                 if (property.Value.Binding is Binding binding && null != binding.Source)
1124                 {
1125                     string xName;
1126                     xElementToNameOfOld.TryGetValue(binding.Source, out xName);
1127
1128                     if (null != xName)
1129                     {
1130                         var newObject = newNameScope[xName];
1131                         binding.Unapply();
1132                         binding.Source = newObject;
1133                         SetBinding(property.Key, binding);
1134                     }
1135                 }
1136             }
1137
1138             if (null != BindingContext)
1139             {
1140                 string xName;
1141                 xElementToNameOfOld.TryGetValue(BindingContext, out xName);
1142
1143                 if (null != xName)
1144                 {
1145                     var newObject = newNameScope[xName];
1146                     BindingContext = newObject;
1147                 }
1148             }
1149         }
1150
1151         internal void ClearBinding()
1152         {
1153             foreach (var property in properties)
1154             {
1155                 if (null != property.Value.Binding)
1156                 {
1157                     property.Value.Binding.Unapply();
1158                 }
1159             }
1160         }
1161
1162         private List<BindableObject> children = new List<BindableObject>();
1163
1164         private void FlushBinding()
1165         {
1166             ApplyBindings(skipBindingContext: true, fromBindingContextChanged: true);
1167             OnBindingContextChanged();
1168
1169             foreach (var child in children)
1170             {
1171                 child.FlushBinding();
1172             }
1173         }
1174     }
1175 }
1176
1177 namespace Tizen.NUI.Binding.Internals
1178 {
1179     /// <summary>
1180     /// SetValueFlags. For internal use.
1181     /// </summary>
1182     [Flags]
1183     [EditorBrowsable(EditorBrowsableState.Never)]
1184     public enum SetValueFlags
1185     {
1186         /// <summary>
1187         /// None.
1188         /// </summary>
1189         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
1190         [EditorBrowsable(EditorBrowsableState.Never)]
1191         None = 0,
1192
1193         /// <summary>
1194         /// Clear OneWay bindings.
1195         /// </summary>
1196         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
1197         [EditorBrowsable(EditorBrowsableState.Never)]
1198         ClearOneWayBindings = 1 << 0,
1199
1200         /// <summary>
1201         /// Clear TwoWay bindings.
1202         /// </summary>
1203         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
1204         [EditorBrowsable(EditorBrowsableState.Never)]
1205         ClearTwoWayBindings = 1 << 1,
1206
1207         /// <summary>
1208         /// Clear dynamic resource.
1209         /// </summary>
1210         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
1211         [EditorBrowsable(EditorBrowsableState.Never)]
1212         ClearDynamicResource = 1 << 2,
1213
1214         /// <summary>
1215         /// Raise or equal.
1216         /// </summary>
1217         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
1218         [EditorBrowsable(EditorBrowsableState.Never)]
1219         RaiseOnEqual = 1 << 3
1220     }
1221 }