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