diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs index 7808987ba31..54285980c8d 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Item/RecyclerViewItem.Internal.cs @@ -227,6 +227,7 @@ protected override void OnEnabled(bool enabled) protected override void OnBindingContextChanged() { PropagateBindingContext(this); + base.OnBindingContextChanged(); } private void PropagateBindingContext(View parent) diff --git a/src/Tizen.NUI/src/devel/Binding/BindingExtensions.cs b/src/Tizen.NUI/src/devel/Binding/BindingExtensions.cs new file mode 100644 index 00000000000..8764109b279 --- /dev/null +++ b/src/Tizen.NUI/src/devel/Binding/BindingExtensions.cs @@ -0,0 +1,207 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using Tizen.NUI.BaseComponents; + +namespace Tizen.NUI.Binding +{ + /// + /// Provides extension methods for binding properties of a view model to a view. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class BindingExtensions + { + /// + /// Sets the binding for the specified view model property. + /// + /// The type of the view model. + /// The type of the view. + /// The view to bind to. + /// The view model to bind from. + /// The action to set the view property. + /// The path of the view model property. + /// The view. + public static TView SetBinding(this TView view, BindingSession vm, Action set, string path) where TView : View + { + _ = view ?? throw new ArgumentNullException(nameof(view)); + + var setter = new Action(vm => + { + set.Invoke(vm, view); + }); + vm.AddBinding(setter, path); + return view; + } + + /// + /// Sets the binding for the specified view model property. + /// + /// The type of the view model. + /// The view to bind to. + /// The view model to bind from. + /// The action to set the view property. + /// The path of the view model property. + /// The view. + public static View SetBinding(this View view, BindingSession vm, Action set, string path) + { + vm.AddBinding(set, path); + return view; + } + + /// + /// Sets the binding for the specified view model property. + /// + /// The type of the view model. + /// The view to bind to. + /// The binding session. + /// The path of the view property. + /// The path of the view model property. + /// The view. + public static View SetBinding(this View view, BindingSession session, string targetPath, string srcPath) + { + var setter = new Action(model => + { + if (view.Disposed) + { + return; + } + var prop = view.GetType().GetProperty(targetPath, BindingFlags.Public | BindingFlags.Instance); + prop.SetValue(view, session.GetValue(srcPath)); + }); + session.AddBinding(setter, srcPath); + return view; + } + + /// + /// Sets the binding for the specified view model property. + /// + /// The type of the view. + /// The type of the view model. + /// The type of the view property. + /// The view to bind to. + /// The binding session. + /// The view property. + /// The path of the view model property. + /// The view. + public static TView SetBinding(this TView view, BindingSession session, BindingProperty property, string path) where TView : View + { + var setter = new Action(model => + { + if (view.Disposed) + { + return; + } + + var value = session.GetValue(path); + // need to apply convertor if type was not mached + if (value != null && !typeof(TProperty).IsAssignableFrom(value.GetType())) + { + // only string type convert with ToString() + if (typeof(TProperty) == typeof(string)) + { + value = value.ToString(); + } + else + { + throw new InvalidCastException($"Cannot cast {value.GetType()} to {typeof(TProperty)}"); + } + } + property.Setter(view, (TProperty)value); + }); + session.AddBinding(setter, path); + return view; + } + + /// + /// Sets the two-way binding for the specified view model property. + /// + /// The type of the view model. + /// The type of the view property. + /// The view to bind to. + /// The binding session. + /// The view property. + /// The path of the view model property. + /// The view. + public static View SetTwoWayBinding(this View view, BindingSession session, TwoWayBindingProperty property, string path) + { + var regit = new Action(act => + { + property.AddObserver(view, act); + }); + var unregit = new Action(act => + { + property.RemoveObserver(view, act); + }); + var setter = new Action(model => + { + if (view.Disposed) + { + return; + } + property.Setter(view, (TProperty)session.GetValue(path)); + }); + var getter = new Func(() => property.Getter(view)); + session.AddTwoWayBinding(regit, unregit, setter, getter, path); + return view; + } + + /// + /// Sets the two-way binding for the specified view model property. + /// + /// The type of the view. + /// The type of the view model. + /// The type of the view property. + /// The to. + /// The binding session. + /// The view property. + /// The path of the view model property. + /// The view. + public static TView SetTwoWayBinding(this TView view, BindingSession session, TwoWayBindingProperty property, string path) where TView : View + { + var regit = new Action(act => + { + property.AddObserver(view, act); + }); + var unregit = new Action(act => + { + property.RemoveObserver(view, act); + }); + var setter = new Action(model => + { + if (view.Disposed) + { + return; + } + property.Setter(view, (TProperty)session.GetValue(path)); + }); + var getter = new Func(() => property.Getter(view)); + session.AddTwoWayBinding(regit, unregit, setter, getter, path); + return view; + } + + /// + /// Gets the binding session for the specified view. + /// + /// The type of the view model. + /// The view. + /// The binding session. + public static BindingSession BindingSession(this View view) + { + return view.GetAttached>(); + } + + /// + /// Sets the binding session for the specified view. + /// + /// The type of the view model. + /// The type of the view. + /// The view. + /// The binding session. + /// The view. + public static T BindingSession(this T view, BindingSession session) where T : View + { + view.SetAttached(session); + return view; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI/src/devel/Binding/BindingProperty.cs b/src/Tizen.NUI/src/devel/Binding/BindingProperty.cs new file mode 100644 index 00000000000..fba9b374f6e --- /dev/null +++ b/src/Tizen.NUI/src/devel/Binding/BindingProperty.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel; + +namespace Tizen.NUI.Binding +{ + /// + /// The BindingProperty class represents a binding property for a view. + /// + /// The type of the view. + /// The type of the value. + [EditorBrowsable(EditorBrowsableState.Never)] + public class BindingProperty + { + /// + /// Gets or sets the setter action for the binding property. + /// + public Action Setter { get; set; } + } +} diff --git a/src/Tizen.NUI/src/devel/Binding/BindingSession.cs b/src/Tizen.NUI/src/devel/Binding/BindingSession.cs new file mode 100644 index 00000000000..4c4c4cf78c1 --- /dev/null +++ b/src/Tizen.NUI/src/devel/Binding/BindingSession.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using Tizen.NUI.BaseComponents; + +namespace Tizen.NUI.Binding +{ + /// + /// BindingSession class provides a mechanism for binding properties of a view model to a view. + /// + /// The type of the view model. + [EditorBrowsable(EditorBrowsableState.Never)] + public class BindingSession : INotifyPropertyChanged, IDisposable + { + private List _bindingActions = new List(); + private List _changedAction = new List(); + private Dictionary> _observers = new Dictionary>(); + private TViewModel _viewmodel; + private bool _disposed; + + /// + /// Represents an event that is raised when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets or sets the view model. + /// + public TViewModel ViewModel + { + get => _viewmodel; + set + { + if (_viewmodel != null && _viewmodel is INotifyPropertyChanged noti) + { + noti.PropertyChanged -= OnPropertyChanged; + } + _viewmodel = value; + + if (_viewmodel != null && _viewmodel is INotifyPropertyChanged newobj) + { + newobj.PropertyChanged += OnPropertyChanged; + } + + UpdateViewModel(); + } + } + + /// + /// Updates the view model. + /// + /// The view model to update. + public void UpdateViewModel(TViewModel vm) + { + ViewModel = vm; + } + + /// + /// Updates the view model. + /// + public void UpdateViewModel() + { + foreach (var action in _bindingActions) + { + action.Invoke(); + } + } + + /// + /// Adds a binding between a property of the view model and a property of the view. + /// + /// The setter method of the view. + /// The path of the property to bind. + public void AddBinding(Action setter, string path) + { + var action = new Action(() => + { + if (ViewModel != null) + setter(ViewModel); + }); + _bindingActions.Add(action); + PropertyChangedEventHandler handler = (s, e) => + { + if (path == "*" || e.PropertyName == path || e.PropertyName == "*") + { + action(); + } + }; + _changedAction.Add(handler); + PropertyChanged += handler; + action.Invoke(); + } + + /// + /// Adds a two-way binding between a property of the view model and a property of the view. + /// + /// The type of the property to bind. + /// The registration method of the observer. + /// The unregistration method of the observer. + /// The setter method of the view. + /// The getter method of the view. + /// The path of the property to bind. + public void AddTwoWayBinding(Action register, Action unregister, Action setter, Func getter, string path) + { + var action = new Action(() => + { + if (ViewModel != null) + SetValue(getter(), path); + }); + _observers[action] = unregister; + register(action); + AddBinding(setter, path); + } + + /// + /// Clears all bindings. + /// + public void ClearBinding() + { + foreach (var evtHandler in _changedAction) + { + PropertyChanged -= evtHandler; + } + foreach (var observer in _observers) + { + observer.Value.Invoke(observer.Key); + } + _observers.Clear(); + _changedAction.Clear(); + _bindingActions.Clear(); + } + + /// + /// Gets the value of a property of the view model. + /// + /// The name of the property. + /// The value of the property. + public object GetValue(string name) + { + if (name == ".") + return ViewModel; + + var prop = ViewModel.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); + if (prop == null) + { + Log.Error("NUI", $"Binding : Property {name} not found"); + } + return prop?.GetValue(ViewModel) ?? null; + } + + /// + /// Sets the value of a property of the view model. + /// + /// The value to set. + /// The name of the property. + public void SetValue(object obj, string name) + { + var prop = ViewModel.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); + prop?.SetValue(ViewModel, obj); + } + + /// + /// Releases all resources used by the current instance of the BindingSession class. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Provides a mechanism for releasing unmanaged resources used by the BindingSession class. + /// + /// True if the method is called from Dispose, false if it is called from the finalizer. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + ClearBinding(); + } + _disposed = true; + } + } + + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI/src/devel/Binding/TwoWayBindingProperty.cs b/src/Tizen.NUI/src/devel/Binding/TwoWayBindingProperty.cs new file mode 100644 index 00000000000..c1c3d3a9cc7 --- /dev/null +++ b/src/Tizen.NUI/src/devel/Binding/TwoWayBindingProperty.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; + +namespace Tizen.NUI.Binding +{ + /// + /// This class represents a two-way binding property between a view and its value. + /// + /// The type of the view. + /// The type of the value. + [EditorBrowsable(EditorBrowsableState.Never)] + public class TwoWayBindingProperty : BindingProperty + { + /// + /// Gets or sets the function that retrieves the value from the view. + /// + public Func Getter { get; set; } + + /// + /// Gets or sets the action that adds an observer to the view. + /// + public Action AddObserver { get; set; } + + /// + /// Gets or sets the action that removes an observer from the view. + /// + public Action RemoveObserver { get; set; } + } +} diff --git a/src/Tizen.NUI/src/devel/Binding/ViewBindings.cs b/src/Tizen.NUI/src/devel/Binding/ViewBindings.cs new file mode 100644 index 00000000000..fa57b7c86e3 --- /dev/null +++ b/src/Tizen.NUI/src/devel/Binding/ViewBindings.cs @@ -0,0 +1,193 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using Tizen.NUI.BaseComponents; + +namespace Tizen.NUI.Binding +{ + /// + /// Provides a set of static properties that represent the binding properties of the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ViewBindings + { + /// + /// Gets the binding property for the width of a . + /// + public static BindingProperty WidthProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.SizeWidth = value, + }; + + /// + /// Gets the binding property for the height of a . + /// + public static BindingProperty HeightProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.SizeHeight = value, + }; + + /// + /// Gets the binding property for the background color of a . + /// + public static BindingProperty BackgroundColorProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.SetBackgroundColor(value), + }; + } + + /// + /// Provides a set of static properties that represent the data-binding capabilities of the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class TextLabelBindings + { + /// + /// Gets the binding property for the property. + /// + public static BindingProperty TextProperty { get; } = new BindingProperty + { + Setter = (v, value) => + { + v.Text = value; + } + }; + + /// + /// Gets the binding property for the property. + /// + public static BindingProperty TextColorProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.TextColor = value.ToReferenceType(), + }; + + /// + /// Gets the binding property for the property. + /// + public static BindingProperty FontSizeProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.PointSize = value, + }; + } + + /// + /// This class provides a set of static properties for binding with control. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class TextFieldBindings + { + /// + /// The TextColorProperty is a bindable property that indicates the color of the text in the control. + /// + public static BindingProperty TextColorProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.TextColor = value.ToReferenceType(), + }; + + /// + /// The FontSizeProperty is a bindable property that indicates the size of the font used to display the text in the control. + /// + public static BindingProperty FontSizeProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.PointSize = value, + }; + + /// + /// The TextProperty is a two-way bindable property that indicates the text displayed in the control. + /// + public static TwoWayBindingProperty TextProperty { get; } = new TwoWayBindingProperty + { + Setter = (v, value) => v.Text = value, + Getter = v => v.Text, + AddObserver = (v, action) => v.TextChanged += EventHandlerHelper.Set>((s, e) => { action.Invoke(); }, action), + RemoveObserver = (v, action) => v.TextChanged -= EventHandlerHelper.Get>(action), + }; + } + + /// + /// Provides a set of static properties for binding TextEditor controls. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class TextEditorBindings + { + /// + /// The TextColorProperty is a bindable property that indicates the color of the text in the TextEditor control. + /// + public static BindingProperty TextColorProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.TextColor = value.ToReferenceType(), + }; + + /// + /// The FontSizeProperty is a bindable property that indicates the size of the font used to display the text in the TextEditor control. + /// + public static BindingProperty FontSizeProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.PointSize = value, + }; + + /// + /// The TextProperty is a two-way bindable property that indicates the text displayed in the TextEditor control. + /// + public static TwoWayBindingProperty TextProperty { get; } = new TwoWayBindingProperty + { + Setter = (v, value) => v.Text = value, + Getter = v => v.Text, + AddObserver = (v, action) => v.TextChanged += EventHandlerHelper.Set>((s, e) => { action.Invoke(); }, action), + RemoveObserver = (v, action) => v.TextChanged -= EventHandlerHelper.Get>(action), + }; + } + + /// + /// Provides a set of static properties that represent the bindable properties of the class. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ImageViewBindings + { + /// + /// Represents the bindable property for the property. + /// + public static BindingProperty ResourceUrlProperty { get; } = new BindingProperty + { + Setter = (v, value) => v.ResourceUrl = value, + }; + } + + /// + /// EventHandlerHelper class provides a helper method to set and get event handlers using actions. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class EventHandlerHelper + { + private static Dictionary s_actionHandlerMap = new Dictionary(); + + /// + /// Sets the event handler for the given action. + /// + /// The type of the event handler. + /// The event handler to set. + /// The action to associate with the event handler. + /// The event handler that was set. + public static TEventHandler Set(TEventHandler handler, Action action) where TEventHandler : Delegate + { + s_actionHandlerMap[action] = handler; + return handler; + } + + /// + /// Gets the event handler associated with the given action and removes the association. + /// + /// The type of the event handler. + /// The action to get the event handler for. + /// The event handler associated with the given action, or null if no association exists. + public static TEventHandler Get(Action action) where TEventHandler : Delegate + { + if (!s_actionHandlerMap.ContainsKey(action)) + return null; + + var handler = (TEventHandler)s_actionHandlerMap[action]; + s_actionHandlerMap.Remove(action); + return handler; + } + } +} diff --git a/test/Tizen.NUI.StyleGuide/Tizen.NUI.StyleGuide.cs b/test/Tizen.NUI.StyleGuide/Tizen.NUI.StyleGuide.cs index da74df0e398..51e0615fe8e 100644 --- a/test/Tizen.NUI.StyleGuide/Tizen.NUI.StyleGuide.cs +++ b/test/Tizen.NUI.StyleGuide/Tizen.NUI.StyleGuide.cs @@ -22,6 +22,7 @@ using Tizen.NUI.Components; using Tizen.NUI.BaseComponents; using Tizen.NUI.Binding; +//using Tizen.NUI.Bindings; using System.Reflection; namespace Tizen.NUI.StyleGuide @@ -307,11 +308,16 @@ private void SetMainPage() ItemsLayouter = new LinearLayouter(), ItemTemplate = new DataTemplate(() => { + var session = new BindingSession(); DefaultLinearItem item = new DefaultLinearItem() { WidthSpecification = LayoutParamPolicies.MatchParent, }; - item.Label.SetBinding(TextLabel.TextProperty, "ViewLabel"); + item.BindingContextChanged += (sender, e) => + { + session.ViewModel = (ControlMenu)item.BindingContext; + }; + item.Label.SetBinding(session, TextLabelBindings.TextProperty, "ViewLabel"); item.Label.HorizontalAlignment = HorizontalAlignment.Begin; item.Focusable = true; //BaseComponents' Focusable is false as a default value, true should be set to navigate key focus. return item;