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