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