Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Peppermint Data Binding

Discussion in 'Assets and Asset Store' started by super-peppermint, Jun 28, 2017.

  1. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Peppermint Data Binding is a lightweight data binding framework for Unity. It provides a simple and easy way for Unity games to utilize data binding.

    DataBinding_Large.png
    Overview

    Clean code
    Peppermint data binding is based on property and reflection. The class does not need to inherit from specified class or interface, any object that has properties can be used as a binding source or target. Existing code can support data binding with only minimal changes.

    *To detect source changes, the source must implement INotifyPropertyChanged interface, or inherits the Bindable base class.

    Easy to setup
    Making the UI support data binding is very easy. You only need to add three types of components: DataContext, DataContextRegister and Binder. Most components have very few parameters to setup.

    The built-in binders include binders for all uGUI controls, ImageBinder, AnimatorBinder, CustomBinder, Selector, Setter, Getter, etc. You can easily create your own binder class to support new features.

    Model-View-ViewModel ready
    Peppermint data binding was designed to make it easy to build game UI using the MVVM pattern. A clean separation between application logic and the UI will make your game easier to test, maintain, and evolve.

    Performance
    Extensively optimized C# code, e.g. type cache, object pool, custom event, fast delegate, etc.

    Features
    • Support OneWay, TwoWay and OneWayToSource binding modes.
    • Support data conversion.
    • Support binding to nested properties.
    • Support binding to collections.
    • Support collection view.
    • Support command.
    • Built-in binders for uGUI controls.
    • Editor tools to make data binding development easier.
    • Optimized for performance.
    • Support JIT/AOT compilation (iOS, Android, webGL).
    • Model-View-ViewModel ready.
    • Full source code included.

    Editor support

    Peppermint data binding includes some useful editor utilities, which can make data binding development more easily.

    -Bindable Property Code Builder
    Generates code snippet for all bindable properties.

    -Implicit Converter Code Builder
    Generates implicit operator type list.

    -AOT Code Builder
    Generate type registration code for AOT compilation.

    -Code Check Tool
    Verifies all property name strings.

    -Data Binding Graph
    A viewer which displays data binding components within a transform node.

    -SpriteSet Builder
    Builds the SpriteSet from specified directory.

    -BindingManager Debug
    Shows the runtime status of the BindingManager.

    Includes 30+ tutorials and examples.

    Links:
    Demo
    Documentation
    Example source code
    Asset Store
     
    Last edited: Feb 28, 2019
    TeagansDad likes this.
  2. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Hi guys, I uploaded the source code of examples. If you have any questions, or need more information, do not hesitate to ask questions. :D

    Links: example_src
     
  3. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    New features coming in version 1.1.0.
    - Added ListDynamicBinder.
    - Added TextMultiBinder.
    - Added CollectionMultiViewBinder.
    - Added new examples for ListDynamicBinder and CollectionMultiViewBinder.
    - Added new examples to demonstrate the difference between MVC and MVVM design pattern.

    ListDynamicBinder is a collection binder which binds to IList object.
    Unlike the CollectionBinder, it only creates and binds the visible items of the list. It requires a dynamic controller to calculate the visible items and handle the layout. It's very useful for scroll views with a large amount of items, e.g. leaderboard.

    How to update
    1. Backup your project first.
    2. Delete the old "Peppermint DataBinding" folder (except the "Peppermint DataBinding/Settings" folder).
    3. Import the new package.

    NewFeatures_v110_0.png

    NewFeatures_v110_1.png

    NewFeatures_v110_2.png
     
    Last edited: Mar 8, 2018
  4. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Very interesting. I'll definitely have a look into your work! :)
     
  5. madbeagle

    madbeagle

    Joined:
    Jan 16, 2014
    Posts:
    10
    This looks great, exactly what I'm looking for. Just need to investigate whether it will work with my Model-to-SQLLite binding library.
     
  6. Sunriser

    Sunriser

    Joined:
    Oct 22, 2012
    Posts:
    11
    Does this work with Unity 2017.x?

    Thanks!
     
  7. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Peppermint data binding is submitted with Unity 5.2 for maximum compatibility. I just tested it with 2017.x, some editor features has compatibility issues. I will submit a new version to fix these issues. Here are some quick fixes for different 2017.x versions.:)

    • For 2017.1.0f3
    ImplicitConverterCodeBuilder will get a generic type, which will cause a compile error. It can be fixed by modifying the BindingEditorUtility.GetImplicitOperatorTypes method.

    ...

    if (!type.IsPublic)
    {
    continue;

    }

    // ++++++
    if (type.IsGenericTypeDefinition)
    {

    continue;

    }
    // ++++++

    var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static);

    ...​

    • For 2017.2.0f3 and 2017.3.0f3
    Unity renamed Mono.Cecil.dll to Unity.Cecil.dll, which will cause an error when using AOT builder and Code Check. The CodeTool.dll of Peppermint DataBinding need reference Mono.Cecil. It can be fixed by copying the old version of Mono.Cecil.dll to Assets/Peppermint DataBinding/Editor/Libs.​


    The new version of Peppermint DataBinding will also contains new features, such as support CallerMemberNameAttribute in .Net 4.6.
     
  8. greay

    greay

    Joined:
    Mar 23, 2011
    Posts:
    88
    Has this been tested with NGUI?
    ... also, how would one go about implementing a mocking solution in this? Something like switching out the normal DataContext with a special mock context?
     
    Last edited: Jan 11, 2018
  9. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24

    Peppermint data binding is a general data binding framework, it can be worked with any UI framework. Currently, I only added components for UGUI. I may add NGUI support in future version.

    Data binding is very easy to extend. I did a quick implementation test for NGUI. It’s very easy to write binders for simple controls, such as UILabel, UISlider. You can use the built-in UGUI binder as a template, the following code is the implementation for UIToggleBinder.

    Code (CSharp):
    1.     public class UIToggleBinder : MonoBehaviour
    2.     {
    3.         public string path;
    4.         private UIToggle target;
    5.         private Binding binding;
    6.         private IDataContext dataContext;
    7.  
    8.         void Start()
    9.         {
    10.             target = GetComponent<UIToggle>();
    11.             if (target == null)
    12.             {
    13.                 Debug.LogError("Require UIToggle Component", gameObject);
    14.                 return;
    15.             }
    16.  
    17.             // create binding
    18.             binding = new Binding(path, target, "value");
    19.             BindingUtility.AddBinding(binding, transform, out dataContext);
    20.  
    21.             // add listener
    22.             EventDelegate.Add(target.onChange, OnToggleValueChanged);
    23.         }
    24.  
    25.         void OnDestroy()
    26.         {
    27.             // remove listener
    28.             EventDelegate.Remove(target.onChange, OnToggleValueChanged);
    29.  
    30.             // remove binding
    31.             BindingUtility.RemoveBinding(binding, dataContext);
    32.         }
    33.  
    34.         private void OnToggleValueChanged()
    35.         {
    36.             if (binding.IsBound)
    37.             {
    38.                 binding.UpdateSource();
    39.             }
    40.         }
    41.     }

    By adding a helper component, the CollectionBinder also works in NGUI. :)

    1.png




    Switching data context is very simple. In peppermint data binding, the DataContext is just a component which is a holder for the binding source. The only thing you need to do is switching the binding source, no need to modify the view.

    Here is a simple example:

    Code (CSharp):
    1.     public class MockingExample : MonoBehaviour
    2.     {
    3.         public interface IPlayerViewModel
    4.         {
    5.             string Name { get; }
    6.             long Gold { get; }
    7.         }
    8.  
    9.         public class RealPlayerViewModel : IPlayerViewModel
    10.         {
    11.             public string Name
    12.             {
    13.                 get { return PlayerPrefs.GetString("Player.Name", "User"); }
    14.             }
    15.  
    16.             public long Gold
    17.             {
    18.                 get { return PlayerPrefs.GetInt("Player.Gold", 0); }
    19.             }
    20.         }
    21.  
    22.         public class DummyPlayerViewModel : IPlayerViewModel
    23.         {
    24.             public string Name { get; set; }
    25.             public long Gold { get; set; }
    26.         }
    27.  
    28.         public bool useMockObject;
    29.         private IPlayerViewModel playerViewModel;
    30.  
    31.         void Start()
    32.         {
    33.             if (useMockObject)
    34.             {
    35.                 // create mock object
    36.                 playerViewModel = new DummyPlayerViewModel { Name = "Dummy", Gold = 9999999 };
    37.             }
    38.             else
    39.             {
    40.                 // get real player view model
    41.                 playerViewModel = new RealPlayerViewModel();
    42.             }
    43.  
    44.             // add source
    45.             BindingManager.Instance.AddSource(playerViewModel, "PlayerViewModel");
    46.         }
    47.  
    48.         private void OnDestroy()
    49.         {
    50.             // remove source
    51.             BindingManager.Instance.RemoveSource(playerViewModel);
    52.         }
    53.     }
     
    greay and skullthug like this.
  10. greay

    greay

    Joined:
    Mar 23, 2011
    Posts:
    88
    Thank you very much! Looks like we're gonna give this a shot.
     
  11. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Hi guys, I just submitted version 1.2.0. The new version is now available.

    changelog
    - Added CanvasPrinter.
    - Added CallerMemberNameAttribute support for .NET 4.6.
    - Fixed Unity 2017.x compatibility issues.

    In this version, I added a new example to demonstrate how to do the dynamic binding manually for a custom view. This example is a simplified level selection UI. Usually, the game contains many levels, the level map will be very large and contains tons of objects. Load or create the entire map at runtime is impractical, because it consumes lots of memory and CPU time. So we need two helper components: CanvasScanner and CanvasPrinter.

    The CanvasScanner works like a scanner, it "scans" the map and generates a simplified blueprint called CanvasData. The CanvasPrinter works like a printer, it loads generated CanvasData and "prints" the visible portion of the entire map.

    You can check the Demo and example source code for more information.

    NewFeatures_v120_0.png
     
    Last edited: Jan 25, 2018
  12. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    The next version will add binders for NGUI. It includes binders to support NGUI’s built-in controls, such as UILabelBinder, UIToggleBinder, etc. It also includes new collection binders for NGUI. :D
     
    Last edited: Mar 10, 2018
  13. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Version 1.3.0 is now available. :D

    In this version, peppermint data binding added NGUI support. To enable this feature, just double-click the NGUI.unitypackage in “Peppermint DataBinding/Extensions” folder and import it into your project.

    Changelog
    - Added NGUI support.
    - Added new examples for NGUI binders.
    - Code refactoring.

    Image07.png

    Image06.png

    Image03.png

    Image02.png
     
    Last edited: Mar 10, 2018
  14. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Hi guys, version 1.4.0 is now available.

    This version added TextMesh Pro support, including binders for TMP_Text, TMP_Dropdown, TMP_InputField. I also added a new EnumSelector which is more easy to setup for enum type.

    This version also added new examples:
    • The Localization example demonstrates how to implement localization with data binding.
    • The MultiView example demonstrates how to switch multiple views.

    Change log
    - Added TextMesh Pro support.
    - Added EnumSelector.
    - Added new examples to demonstrate localization and multiple views.
    - Updated code to support assembly definition files.
    - Minor improvements and bug fixes.

    TextMeshPro.png
    MultiView_0.png
    MultiView_1.png
    Localization_english.png
    Localization_french.png
     
  15. greay

    greay

    Joined:
    Mar 23, 2011
    Posts:
    88
    How would one go about implementing some sort of drag/drop solution using Peppermint data bindings?

    I've made a custom binder that handles the first half, triggering an ICommand when a drag starts, but I'm stumped on the drop happens – since ICommands don't have any parameters, I'm not sure how to tell one model object that another's been dragged onto it. (I'm using NGUI, but I don't know how much it matters – once I get past the conceptual roadblock, I think I'll be fine)
     
  16. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Drag and drop is a view operation, you need to create a view controller to handle it. Basically, the controller will raise two events: start dragging and end dragging. It's very easy to add data binding support for this controller, all you need to do is bind these events. The following is a simple implementation, you can download the full example in the attachment.

    This example uses NGUI's UIDragDropItem, it only implements the basic drag and drop between two list and the target item is always added to the end of the list. You can use this example as a reference and create your own controller.

    DragDropController Binder
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. namespace Peppermint.DataBinding.Example
    6. {
    7.     // Binder for DragDropController. It contains two action bindings, these actions will be called
    8.     // by target DragDropController.
    9.     [Binder]
    10.     [RequireComponent(typeof(DragDropController))]
    11.     public class DragDropControllerBinder : MonoBehaviour
    12.     {
    13.         public string beginDragPath;
    14.         public string endDragPath;
    15.  
    16.         private DragDropController target;
    17.         private IDataContext dataContext;
    18.         private List<IBinding> bindingList;
    19.         private Action beginDrag;
    20.         private Action<Transform> endDrag;
    21.  
    22.         public Action BeginDrag
    23.         {
    24.             set
    25.             {
    26.                 if (beginDrag != null)
    27.                 {
    28.                     target.onBeginDrag -= beginDrag;
    29.                 }
    30.  
    31.                 beginDrag = value;
    32.  
    33.                 if (beginDrag != null)
    34.                 {
    35.                     target.onBeginDrag += beginDrag;
    36.                 }
    37.             }
    38.         }
    39.  
    40.         public Action<Transform> EndDrag
    41.         {
    42.             set
    43.             {
    44.                 if (endDrag != null)
    45.                 {
    46.                     target.onEndDrag -= endDrag;
    47.                 }
    48.  
    49.                 endDrag = value;
    50.  
    51.                 if (endDrag != null)
    52.                 {
    53.                     target.onEndDrag += endDrag;
    54.                 }
    55.             }
    56.         }
    57.  
    58.         void Start()
    59.         {
    60.             target = GetComponent<DragDropController>();
    61.             if (target == null)
    62.             {
    63.                 Debug.LogError("Require DragDropController Component", gameObject);
    64.                 return;
    65.             }
    66.  
    67.             CreateBinding();
    68.         }
    69.  
    70.         void OnDestroy()
    71.         {
    72.             BindingUtility.RemoveBinding(bindingList, dataContext);
    73.         }
    74.  
    75.         private void CreateBinding()
    76.         {
    77.             bindingList = new List<IBinding>();
    78.  
    79.             if (!string.IsNullOrEmpty(beginDragPath))
    80.             {
    81.                 var binding = new Binding(beginDragPath, this, "BeginDrag");
    82.                 binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    83.                 bindingList.Add(binding);
    84.             }
    85.  
    86.             if (!string.IsNullOrEmpty(endDragPath))
    87.             {
    88.                 var binding = new Binding(endDragPath, this, "EndDrag");
    89.                 binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    90.                 bindingList.Add(binding);
    91.             }
    92.  
    93.             BindingUtility.AddBinding(bindingList, transform, out dataContext);
    94.         }
    95.     }
    96. }
    97.  

    DragDropExample
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. namespace Peppermint.DataBinding.Example
    5. {
    6.     // A simple drag and drop example, it demonstrates how to handle drag events.
    7.     public class DragDropExample : BindableMonoBehaviour
    8.     {
    9.         public class Item : BindableObject
    10.         {
    11.             private string name;
    12.             private ColorTag colorTag;
    13.             private ICommand clickCommand;
    14.             private bool isDragging;
    15.             private Action beginDragAction;
    16.             private Action<Transform> endDragAction;
    17.  
    18.             #region Bindable Properties
    19.  
    20.             public string Name
    21.             {
    22.                 get { return name; }
    23.                 set { SetProperty(ref name, value, "Name"); }
    24.             }
    25.  
    26.             public ColorTag ColorTag
    27.             {
    28.                 get { return colorTag; }
    29.                 set { SetProperty(ref colorTag, value, "ColorTag"); }
    30.             }
    31.  
    32.             public ICommand ClickCommand
    33.             {
    34.                 get { return clickCommand; }
    35.                 set { SetProperty(ref clickCommand, value, "ClickCommand"); }
    36.             }
    37.  
    38.             public bool IsDragging
    39.             {
    40.                 get { return isDragging; }
    41.                 set { SetProperty(ref isDragging, value, "IsDragging"); }
    42.             }
    43.  
    44.             public Action BeginDragAction
    45.             {
    46.                 get { return beginDragAction; }
    47.                 set { SetProperty(ref beginDragAction, value, "BeginDragAction"); }
    48.             }
    49.  
    50.             public Action<Transform> EndDragAction
    51.             {
    52.                 get { return endDragAction; }
    53.                 set { SetProperty(ref endDragAction, value, "EndDragAction"); }
    54.             }
    55.  
    56.             #endregion
    57.         }
    58.  
    59.         public int listACount = 5;
    60.         public int listBCount = 5;
    61.  
    62.         private Transform containerA;
    63.         private Transform containerB;
    64.         private ObservableList<Item> itemListA;
    65.         private ObservableList<Item> itemListB;
    66.         private ICommand resetListCommand;
    67.         private ICommand shuffleListCommand;
    68.  
    69.         #region Bindable Properties
    70.  
    71.         public Transform ContainerA
    72.         {
    73.             set { containerA = value; }
    74.         }
    75.  
    76.         public Transform ContainerB
    77.         {
    78.             set { containerB = value; }
    79.         }
    80.  
    81.         public ObservableList<Item> ItemListA
    82.         {
    83.             get { return itemListA; }
    84.             set { SetProperty(ref itemListA, value, "ItemListA"); }
    85.         }
    86.  
    87.         public ObservableList<Item> ItemListB
    88.         {
    89.             get { return itemListB; }
    90.             set { SetProperty(ref itemListB, value, "ItemListB"); }
    91.         }
    92.  
    93.         public ICommand ResetListCommand
    94.         {
    95.             get { return resetListCommand; }
    96.             set { SetProperty(ref resetListCommand, value, "ResetListCommand"); }
    97.         }
    98.  
    99.         public ICommand ShuffleListCommand
    100.         {
    101.             get { return shuffleListCommand; }
    102.             set { SetProperty(ref shuffleListCommand, value, "ShuffleListCommand"); }
    103.         }
    104.  
    105.         #endregion
    106.  
    107.         void Start()
    108.         {
    109.             itemListA = new ObservableList<Item>();
    110.             itemListB = new ObservableList<Item>();
    111.  
    112.             ResetList();
    113.  
    114.             // create commands
    115.             resetListCommand = new DelegateCommand(ResetList);
    116.             shuffleListCommand = new DelegateCommand(ShuffleList);
    117.  
    118.             BindingManager.Instance.AddSource(this, typeof(DragDropExample).Name);
    119.         }
    120.  
    121.         void OnDestroy()
    122.         {
    123.             BindingManager.Instance.RemoveSource(this);
    124.         }
    125.  
    126.         public void ResetList()
    127.         {
    128.             Debug.LogFormat("ResetList");
    129.  
    130.             itemListA.Clear();
    131.             ItemListB.Clear();
    132.  
    133.             for (int i = 0; i < listACount; i++)
    134.             {
    135.                 var item = new Item();
    136.                 item.Name = string.Format("A{0}", i);
    137.                 item.ColorTag = ColorTag.Red;
    138.                 item.ClickCommand = new DelegateCommand(() => ClickItem(item));
    139.  
    140.                 // create action with extra parameters
    141.                 item.BeginDragAction = () => BeginDragItem(item);
    142.                 item.EndDragAction = x => EndDragItem(item, x);
    143.  
    144.                 itemListA.Add(item);
    145.             }
    146.  
    147.             for (int i = 0; i < listBCount; i++)
    148.             {
    149.                 var item = new Item();
    150.                 item.Name = string.Format("B{0}", i);
    151.                 item.ColorTag = ColorTag.Blue;
    152.                 item.ClickCommand = new DelegateCommand(() => ClickItem(item));
    153.  
    154.                 // create action with extra parameters
    155.                 item.BeginDragAction = () => BeginDragItem(item);
    156.                 item.EndDragAction = x => EndDragItem(item, x);
    157.  
    158.                 itemListB.Add(item);
    159.             }
    160.         }
    161.  
    162.         public void ShuffleList()
    163.         {
    164.             Debug.LogFormat("ShuffleList");
    165.  
    166.             itemListA.Shuffle();
    167.             itemListB.Shuffle();
    168.         }
    169.  
    170.         public void ClickItem(Item item)
    171.         {
    172.             Debug.LogFormat("Click item {0}", item.Name);
    173.         }
    174.  
    175.         public void BeginDragItem(Item item)
    176.         {
    177.             Debug.LogFormat("Begin drag item {0}", item.Name);
    178.  
    179.             // set drag flag, it will disable button binder to make it works with UIDragDropItem
    180.             item.IsDragging = true;
    181.         }
    182.  
    183.         public void EndDragItem(Item item, Transform container)
    184.         {
    185.             Debug.Log(string.Format("End drag item {0}, container {1}", item.Name, container), container);
    186.  
    187.             // reset flag
    188.             item.IsDragging = false;
    189.  
    190.             if (container == containerB)
    191.             {
    192.                 // drop to list B
    193.                 if (itemListA.Contains(item))
    194.                 {
    195.                     // item belongs to list A, just move item from A to B
    196.                     Debug.Log("Move item from list A to list B");
    197.                     itemListA.Remove(item);
    198.                     itemListB.Add(item);
    199.                 }
    200.                 else
    201.                 {
    202.                     Debug.Log("Ignore drop to list B");
    203.                 }
    204.             }
    205.             else if (container == containerA)
    206.             {
    207.                 // drop to list A
    208.                 if (itemListB.Contains(item))
    209.                 {
    210.                     // item belongs to list B, just move item from B to A
    211.                     Debug.Log("Move item from list B to list A");
    212.                     itemListB.Remove(item);
    213.                     itemListA.Add(item);
    214.                 }
    215.                 else
    216.                 {
    217.                     Debug.Log("Ignore drop to list A");
    218.                 }
    219.             }
    220.             else
    221.             {
    222.                 Debug.Log("Ignore unknown target");
    223.             }
    224.         }
    225.     }
    226. }
    227.  
    20180526054237_result.png
     

    Attached Files:

    Last edited: May 25, 2018
  17. greay

    greay

    Joined:
    Mar 23, 2011
    Posts:
    88
    Thank you so much! I'm still relatively new to Peppermint (and, well, using data bindings in general). I didn't even realize that Actions were a bindable property! This helps immensely.
     
  18. miptpatriot

    miptpatriot

    Joined:
    Feb 19, 2013
    Posts:
    4
    Hello

    Bug when trying to bind KeyValueStorage<string, CollectionView>:

    Code (CSharp):
    1. Call CreateGetterImpl<System.Collections.Generic.KeyValuePair`2[System.String,Peppermint.DataBinding.Example.CollectionItem], System.String>() failed, property=System.Collections.Generic.KeyValuePair`2[System.String,Peppermint.DataBinding.Example.CollectionItem].Key
    2. UnityEngine.Debug:LogErrorFormat(String, Object[])
    3. Peppermint.DataBinding.DelegateUtility:CreateGetter(PropertyInfo) (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Support/DelegateUtility.cs:52)
    4. Peppermint.DataBinding.PropertyAccessor:GetValue(Object) (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Support/PropertyAccessor.cs:74)
    5. Peppermint.DataBinding.Binding:UpdateTarget() (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Binding/Binding.cs:246)
    6. Peppermint.DataBinding.Binding:InitValue() (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Binding/Binding.cs:314)
    7. Peppermint.DataBinding.Binding:Bind(Object) (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Binding/Binding.cs:196)
    8. Peppermint.DataBinding.DataContext:AddBinding(IBinding) (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/DataContext/DataContext.cs:69)
    9. Peppermint.DataBinding.BindingUtility:AddBinding(IBinding, Transform, IDataContext&) (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/Support/BindingUtility.cs:151)
    10. Peppermint.DataBinding.TextBinder:CreateBinding() (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/UI/Binder/TextBinder.cs:54)
    11. Peppermint.DataBinding.TextBinder:Start() (at Assets/Plugins/Peppermint.DataBinding/Scripts/DataBinding/UI/Binder/TextBinder.cs:41)

    UPD: After some investigation - it may be Unity 2018.1 internal bug..


    And some more suggestions:
    1) Unity 2018.1 has support for .Net 4.5 and has build-in ObservableCollection. I suggest you to move from your own INotifyCollectionChanged to standard one.
    2) Your INotifyCollectionChanged is very memory unefficient. And I'm not sure it will be faster in real world usage.
    3) One can use Fody plugin (https://github.com/Fody/PropertyChanged) to transparently generate code for getters/setters.
    4) You may find a implementation for ObservableDictionary<> on stackoverflow, which is pretty must-have.
     
    Last edited: Jul 11, 2018
  19. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Thanks for your feedback, I did a quick memory profile for ObservableList and .Net ObservableCollection with the following code.
    Code (CSharp):
    1. var collection = new ObservableList<object>();
    2. // or var collection = new System.Collections.ObjectModel.ObservableCollection<object>();
    3.  
    4. collection.CollectionChanged += (sender, arg) => { };
    5.  
    6. // add
    7. for (int i = 0; i < collectionTestCount; i++)
    8. {
    9.     collection.Add(i);
    10. }
    11.  
    12. // indexer
    13. for (int i = 0; i < collectionTestCount; i++)
    14. {
    15.     collection[i] = 2 * i;
    16. }
    17.  
    18. // remove
    19. for (int i = 0; i < collectionTestCount; i++)
    20. {
    21.     collection.RemoveAt(0);
    22. }
    23.  
    I set collectionTestCount to 10000 and run this test several times with .Net 4.x runtime. Here is the test result:

    Test, GC Calls, GC Alloc, Time ms
    ObservableList, 130016, 5.7MB, 7.05
    ObservableCollection, 180017, 5.6MB, 10.14
    ObservableList(Optimized), 90016, 2.7MB, 4.86

    The memory usage of ObservableList is almost identical to.Net ObservableCollection, and it uses fewer GC calls and runs faster. Usually, games do not change collections frequently, so the GC won't be a problem. If you need to manipulate the collection with lots of items, you can use "batch mode" to optimize performance and memory usage. For more information you can check CollectionExtensions.Shuffle method.

    I also optimize the NotifyCollectionChangedEventArgs class to reduce memory allocation. Here is the modified code, you can replace the built-in NotifyCollectionChangedEventArgs class.
    Code (CSharp):
    1.     public class NotifyCollectionChangedEventArgs
    2.     {
    3.         private class SingleElementList : IList
    4.         {
    5.             private object item;
    6.  
    7.             public int Count { get { return 1; } }
    8.             public bool IsReadOnly { get { return true; } }
    9.             public bool IsFixedSize { get { return true; } }
    10.             public bool IsSynchronized { get { return false; } }
    11.             public object SyncRoot { get { throw new NotSupportedException(); } }
    12.  
    13.             public object this[int index]
    14.             {
    15.                 get
    16.                 {
    17.                     if (index != 0)
    18.                     {
    19.                         throw new IndexOutOfRangeException();
    20.                     }
    21.  
    22.                     return item;
    23.                 }
    24.  
    25.                 set
    26.                 {
    27.                     throw new NotSupportedException();
    28.                 }
    29.             }
    30.  
    31.             public SingleElementList(object item)
    32.             {
    33.                 this.item = item;
    34.             }
    35.  
    36.             public bool Contains(object value)
    37.             {
    38.                 return value == item;
    39.             }
    40.  
    41.             public void CopyTo(Array array, int index)
    42.             {
    43.                 array.SetValue(item, index);
    44.             }
    45.  
    46.             public IEnumerator GetEnumerator()
    47.             {
    48.                 yield return item;
    49.             }
    50.  
    51.             public int IndexOf(object value)
    52.             {
    53.                 return (value == item) ? 0 : -1;
    54.             }
    55.  
    56.             public int Add(object value)
    57.             {
    58.                 throw new NotSupportedException();
    59.             }
    60.  
    61.             public void Clear()
    62.             {
    63.                 throw new NotSupportedException();
    64.             }
    65.  
    66.             public void Insert(int index, object value)
    67.             {
    68.                 throw new NotSupportedException();
    69.             }
    70.  
    71.             public void Remove(object value)
    72.             {
    73.                 throw new NotSupportedException();
    74.             }
    75.  
    76.             public void RemoveAt(int index)
    77.             {
    78.                 throw new NotSupportedException();
    79.             }
    80.         }
    81.  
    82.         public NotifyCollectionChangedAction Action { get; private set; }
    83.         public IList NewItems { get; private set; }
    84.         public IList OldItems { get; private set; }
    85.  
    86.         public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action)
    87.         {
    88.             if (action != NotifyCollectionChangedAction.Reset)
    89.             {
    90.                 throw new ArgumentException("Only support Reset");
    91.             }
    92.  
    93.             Action = action;
    94.         }
    95.  
    96.         public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object changedItem)
    97.         {
    98.             Action = action;
    99.  
    100.             var list = new SingleElementList(changedItem);
    101.             if (action == NotifyCollectionChangedAction.Add)
    102.             {
    103.                 NewItems = list;
    104.             }
    105.             else if (action == NotifyCollectionChangedAction.Remove)
    106.             {
    107.                 OldItems = list;
    108.             }
    109.             else
    110.             {
    111.                 throw new ArgumentException("Unhandled action " + action);
    112.             }
    113.         }
    114.  
    115.         public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, IList items)
    116.         {
    117.             Action = action;
    118.             if (action == NotifyCollectionChangedAction.Add)
    119.             {
    120.                 NewItems = items;
    121.             }
    122.             else if (action == NotifyCollectionChangedAction.Remove)
    123.             {
    124.                 OldItems = items;
    125.             }
    126.             else if (action == NotifyCollectionChangedAction.Move)
    127.             {
    128.                 NewItems = items;
    129.             }
    130.             else
    131.             {
    132.                 throw new ArgumentException("Unhandled action " + action);
    133.             }
    134.         }
    135.  
    136.         public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem)
    137.         {
    138.             if (action != NotifyCollectionChangedAction.Replace)
    139.             {
    140.                 throw new ArgumentException("Only support Replace action");
    141.             }
    142.  
    143.             Action = action;
    144.             NewItems = new SingleElementList(newItem);
    145.             OldItems = new SingleElementList(oldItem);
    146.         }
    147.     }
    Peppermint data binding implements its own INotifyCollectionChanged and ObservableList for the following reasons:
    • Minimize dependencies, for .Net 3.5 runtime, these types are inside WindowsBase.dll.
    • More features. ObservableList implements IList<T> instead of ICollection<T>, which contains more methods, such as Sort, AddRange, etc.
    The built-in Code Builder contains tools to generate bindable properties code. In most cases, the generated code is ready for use, but in some cases, you need to modify the property to support more features, such as notify associated properties, etc. Once it's done, you don't need to generate these properties again.

    Peppermint data binding only supports non-associative collections and it does not support associated collections. It can handle generic type such as ObservableList<Tuple<string, Item>>, to bind to Item.Name, you only need to set the path to "Item2.Name".

    If you need to bind to Dictionary in your model, you can use the following steps:
    1. Create an ObservableList<Item> in its view model.
    2. Handle dictionary changes in this view model, you can manually sync the ObservableDictionary<int, Item> to the ObservableList<Item>, such as added items, removed items, and ordering.
    3. In your UI, you bind to this ObservableList<Item> instead.
    If you need more information, you can check the MVVM example, it demonstrates how to sync collection from model to view-model.
     
  20. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    New version 1.5.0 is now available. :D

    Changelog:
    - Added ObservableDictionary.
    - Added ListSyncController and DictionarySyncController.
    - Added new examples to demonstrate sync controller.
    - Updated code generation for ImplicitConverter to support assembly definition files.
    - Updated collection event to avoid GC.

    The new SyncController can be used to synchronize content from source collection to target collection. In Peppermint data binding, collection bindings do not support associated container, such as dictionary, etc. If you need to bind to dictionary, you can use the DictionarySyncController as an adaptor to make your dictionary support data binding.

    I also updated the NotifyCollectionChangedEventArgs to eliminate GC and some minor enumerator optimization.

    ListSyncControllerExample.png
    DictionarySyncControllerExample.png
     
  21. Tropobor

    Tropobor

    Joined:
    Mar 24, 2014
    Posts:
    73
    Just discover this and almost a customer cause it looks awesome. Just one question (probably stupid) how would you go to jump (scroll) to specific items in canvas printer example?
     
  22. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    First, you need to add a scrollbar binder in the view and bind it to a float property "scrollbarPosition". Next, you need to add a float property "scrollbarValue" to the LevelNode class and calculate the value for each level node. You can calculate it using the information stored in the CanvasData. To scroll the view to the specified level node, simply assign "scrollbarValue" to the "scrollbarPosition".

    The following is the code snippet for calculating the "scrollbarValue" and updating the scrollbar, the full example is in the attachment.

    Code (CSharp):
    1. private void InitScrollbarPosition()
    2. {
    3.     var data = canvasPrinter.data;
    4.     var seperators = new char[] { ',' };
    5.  
    6.     // get max scrollbar position
    7.     float maxPosition = data.width - data.sectionWidth;
    8.  
    9.     // get position from canvas data
    10.     foreach (var item in data.nodeList)
    11.     {
    12.         if (string.IsNullOrEmpty(item.metadata))
    13.         {
    14.             continue;
    15.         }
    16.  
    17.         // extract LevelMapNode from metadata
    18.         var tokens = item.metadata.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
    19.         var nodeType = (LevelMapNode.NodeType)Enum.Parse(typeof(LevelMapNode.NodeType), tokens[0]);
    20.         var levelIndex = Int32.Parse(tokens[1]);
    21.  
    22.         // calculate scrollbar position (centered)
    23.         var pos = (data.width / 2f + item.anchoredPosition.x - data.sectionWidth / 2) / maxPosition;
    24.         pos = Mathf.Clamp01(pos);
    25.  
    26.         // set to node
    27.         if (nodeType == LevelMapNode.NodeType.Level)
    28.         {
    29.             levelNodes[levelIndex].scrollbarValue = pos;
    30.         }
    31.     }
    32. }
    Code (CSharp):
    1. private void GoToNode(int nodeIndex)
    2. {
    3.     currentNodeIndex = Mathf.Clamp(nodeIndex, 0, levelNodes.Count - 1);
    4.     var node = levelNodes[currentNodeIndex];
    5.  
    6.     Debug.LogFormat("GoToNode {0}", node.Name);
    7.  
    8.     // update scrollbar position
    9.     ScrollbarPosition = node.scrollbarValue;
    10. }
    CanvasPrinter_ScrollbarExample.png
     

    Attached Files:

    Last edited: Jul 26, 2018
  23. Tropobor

    Tropobor

    Joined:
    Mar 24, 2014
    Posts:
    73
    Got it, cool, it helps. Thanks for quick answer.
     
  24. Rakesh6720

    Rakesh6720

    Joined:
    Jun 14, 2018
    Posts:
    5
    Hm, this seems like it could be exactly what I've been looking for since July. I've been trying to find something to play the role Razor syntax does in Entity Framework -- pass manipulated model to view. If I wanted to instantiate prefab objects for every element in a dictionary, for example, would I be able to use this binding framework to have a tool-tip UI populate with dictionary value object properties when user hovers over an instantiated prefab?
     
  25. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    It's easy to implement a tool tip UI using data binding. You can simply add current hovered item as binding source and bind the tool tip UI to it.

    To handle the pointer hovering, you can create a custom binder by implementing the IPointerEnterHandler and IPointerExitHandler. This binder contains two action bindings: pointerEnter and pointerExit, these actions will be called when the pointer hover over item's UI.

    Next, you need to add two actions in item's view model, and handle the pointer hover event. The handler will add current hovered item as binding source when pointer enter, and remove it when pointer exit.

    Here is a simple example:

    PointerBinder
    Code (CSharp):
    1. public class PointerBinder : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
    2. {
    3.     public string pointerEnterPath;
    4.     public string pointerExitPath;
    5.  
    6.     private IDataContext dataContext;
    7.     private List<IBinding> bindingList;
    8.     private Action enter;
    9.     private Action exit;
    10.  
    11.     public Action Enter
    12.     {
    13.         set
    14.         {
    15.             enter = value;
    16.         }
    17.     }
    18.  
    19.     public Action Exit
    20.     {
    21.         set
    22.         {
    23.             exit = value;
    24.         }
    25.     }
    26.  
    27.     void Start()
    28.     {
    29.         bindingList = new List<IBinding>();
    30.  
    31.         if (!string.IsNullOrEmpty(pointerEnterPath))
    32.         {
    33.             var binding = new Binding(pointerEnterPath, this, "Enter");
    34.             binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    35.             bindingList.Add(binding);
    36.         }
    37.  
    38.         if (!string.IsNullOrEmpty(pointerExitPath))
    39.         {
    40.             var binding = new Binding(pointerExitPath, this, "Exit");
    41.             binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    42.             bindingList.Add(binding);
    43.         }
    44.  
    45.         BindingUtility.AddBinding(bindingList, transform, out dataContext);
    46.     }
    47.  
    48.     void OnDestroy()
    49.     {
    50.         BindingUtility.RemoveBinding(bindingList, dataContext);
    51.     }
    52.  
    53.     public void OnPointerEnter(PointerEventData pointerEventData)
    54.     {
    55.         if (enter != null)
    56.         {
    57.             enter.Invoke();
    58.         }
    59.     }
    60.  
    61.     public void OnPointerExit(PointerEventData pointerEventData)
    62.     {
    63.         if (exit != null)
    64.         {
    65.             exit.Invoke();
    66.         }
    67.     }
    68. }

    ToolTipExample
    Code (CSharp):
    1. public class ToolTipExample : BindableMonoBehaviour
    2. {
    3.     public class Item : BindableObject
    4.     {
    5.         private string name;
    6.         private Action pointerEnter;
    7.         private Action pointerExit;
    8.  
    9.         #region Bindable Properties
    10.  
    11.         public string Name
    12.         {
    13.             get { return name; }
    14.             set { SetProperty(ref name, value, "Name"); }
    15.         }
    16.  
    17.         public Action PointerEnter
    18.         {
    19.             get { return pointerEnter; }
    20.             set { SetProperty(ref pointerEnter, value, "PointerEnter"); }
    21.         }
    22.  
    23.         public Action PointerExit
    24.         {
    25.             get { return pointerExit; }
    26.             set { SetProperty(ref pointerExit, value, "PointerExit"); }
    27.         }
    28.  
    29.         #endregion
    30.     }
    31.  
    32.     public int itemCount = 5;
    33.  
    34.     private ObservableList<Item> itemList;
    35.     private bool showToolTip;
    36.     private Item currentItem;
    37.  
    38.     #region Bindable Properties
    39.  
    40.     public ObservableList<Item> ItemList
    41.     {
    42.         get { return itemList; }
    43.         set { SetProperty(ref itemList, value, "ItemList"); }
    44.     }
    45.  
    46.     public bool ShowToolTip
    47.     {
    48.         get { return showToolTip; }
    49.         set { SetProperty(ref showToolTip, value, "ShowToolTip"); }
    50.     }
    51.  
    52.     #endregion
    53.  
    54.     void Start()
    55.     {
    56.         itemList = new ObservableList<Item>();
    57.  
    58.         // create items
    59.         for (int index = 0; index < itemCount; index++)
    60.         {
    61.             var item = new Item()
    62.             {
    63.                 Name = string.Format("Item {0}", (char)(index + 'A')),
    64.             };
    65.  
    66.             // create actions
    67.             item.PointerEnter = () => OnPointerEnter(item);
    68.             item.PointerExit = () => OnPointerExit(item);
    69.  
    70.             itemList.Add(item);
    71.         }
    72.  
    73.         BindingManager.Instance.AddSource(this, typeof(ToolTipExample).Name);
    74.     }
    75.  
    76.     void OnDestroy()
    77.     {
    78.         BindingManager.Instance.RemoveSource(this);
    79.     }
    80.  
    81.     public void OnPointerEnter(Item target)
    82.     {
    83.         Debug.Log("OnPointerEnter: " + target.Name);
    84.         SetToolTip(target);
    85.     }
    86.  
    87.     public void OnPointerExit(Item target)
    88.     {
    89.         Debug.Log("OnPointerExit: " + target.Name);
    90.         SetToolTip(null);
    91.     }
    92.  
    93.     private void SetToolTip(Item target)
    94.     {
    95.         if (currentItem != null)
    96.         {
    97.             // remove old source
    98.             BindingManager.Instance.RemoveSource(currentItem);
    99.         }
    100.  
    101.         // update current item
    102.         currentItem = target;
    103.  
    104.         if (currentItem != null)
    105.         {
    106.             // add new source
    107.             BindingManager.Instance.AddSource(currentItem, "CurrentItem");
    108.         }
    109.  
    110.         // show tool tip
    111.         ShowToolTip = (currentItem != null);
    112.     }
    113. }
     
    Rakesh6720 likes this.
  26. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    New version 1.5.1 is now available.

    Changelog:
    - Added DataContextBinder and ProxyDataContext.
    - Added new examples to demonstrate DataContextBinder.
    - Added new examples for handling nested source changes.
    - Minor improvements and bug fixes.
     
    LudiKha and one_one like this.
  27. jotaserna

    jotaserna

    Joined:
    Oct 9, 2019
    Posts:
    2
    Hello,

    I'm currently evaluating Peppermint Data Binding for a project. I have to say that I'm pretty happy with it and I'm really considering buying the license.

    However, I have a question on how to achieve something; I would like to get notified (maybe through a Command?) when an animation clip inside an Animator ends. Is there any way to achieve this right now, or would I need to implement it myself as a new type of Binder?

    Thanks for your help and for your fantastic contribution!

    Cheers!
     
  28. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Currently, there are no built-in binders to handle animation events, but you can easily create a custom binder to handle it. The basic idea is to create an action binding and call the action when the animation state changes. Here is a quick implementation by checking the animator state tag hash.

    To run this example, you need to set a "Popup" tag for the popup animation state. When you play/trigger a popup animation, the binder will call the enter action. Once the popup animation ends, the exit action will be called.

    AnimatorStateBinder
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. namespace Peppermint.DataBinding.Example
    5. {
    6.     // Bind animator state changes to action property.
    7.     [Binder]
    8.     [RequireComponent(typeof(Animator))]
    9.     public class AnimatorStateBinder : MonoBehaviour
    10.     {
    11.         public enum TriggerMode
    12.         {
    13.             Enter,
    14.             Exit,
    15.         }
    16.  
    17.         public string path;
    18.         public string stateTag;
    19.         public int layer;
    20.         public TriggerMode triggerMode;
    21.  
    22.         private int targetTagHash;
    23.         private int currentStateTagHash;
    24.         private int previousStateTagHash;
    25.  
    26.         private Animator animator;
    27.         private Binding binding;
    28.         private IDataContext dataContext;
    29.         private Action stateChanged;
    30.  
    31.         public Action StateChanged
    32.         {
    33.             set
    34.             {
    35.                 stateChanged = value;
    36.             }
    37.         }
    38.  
    39.         void Start()
    40.         {
    41.             // get animator
    42.             animator = GetComponent<Animator>();
    43.             if (animator == null)
    44.             {
    45.                 Debug.LogError("Require Animator Component", gameObject);
    46.                 return;
    47.             }
    48.  
    49.             CreateBinding();
    50.  
    51.             // initialize tag hash
    52.             targetTagHash = Animator.StringToHash(stateTag);
    53.             currentStateTagHash = animator.GetCurrentAnimatorStateInfo(layer).tagHash;
    54.             previousStateTagHash = currentStateTagHash;
    55.         }
    56.  
    57.         void OnDestroy()
    58.         {
    59.             BindingUtility.RemoveBinding(binding, dataContext);
    60.         }
    61.  
    62.         void Update()
    63.         {
    64.             // save current tag hash
    65.             previousStateTagHash = currentStateTagHash;
    66.  
    67.             // update layer data
    68.             AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layer);
    69.             currentStateTagHash = info.tagHash;
    70.  
    71.             // check tag hash changes
    72.             if (triggerMode == TriggerMode.Exit)
    73.             {
    74.                 if (currentStateTagHash != targetTagHash && previousStateTagHash == targetTagHash)
    75.                 {
    76.                     OnStateChanged();
    77.                 }
    78.             }
    79.             else if (triggerMode == TriggerMode.Enter)
    80.             {
    81.                 if (currentStateTagHash == targetTagHash && previousStateTagHash != targetTagHash)
    82.                 {
    83.                     OnStateChanged();
    84.                 }
    85.             }
    86.         }
    87.  
    88.         void CreateBinding()
    89.         {
    90.             binding = new Binding(path, this, "StateChanged");
    91.             binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    92.  
    93.             BindingUtility.AddBinding(binding, transform, out dataContext);
    94.         }
    95.  
    96.         void OnStateChanged()
    97.         {
    98.             if (binding.IsBound && stateChanged != null)
    99.             {
    100.                 stateChanged.Invoke();
    101.             }
    102.         }
    103.     }
    104. }
    AnimatorStateBinderExample
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. namespace Peppermint.DataBinding.Example
    5. {
    6.     public class AnimatorStateBinderExample : BindableMonoBehaviour
    7.     {
    8.         private Action enterPopup;
    9.         private Action exitPopup;
    10.  
    11.         private ICommand selectCommand;
    12.         private AnimatorTrigger selectTrigger;
    13.  
    14.         #region Bindable Properties
    15.  
    16.         public Action EnterPopup
    17.         {
    18.             get { return enterPopup; }
    19.             set { SetProperty(ref enterPopup, value, "EnterPopup"); }
    20.         }
    21.  
    22.         public Action ExitPopup
    23.         {
    24.             get { return exitPopup; }
    25.             set { SetProperty(ref exitPopup, value, "ExitPopup"); }
    26.         }
    27.  
    28.         public ICommand SelectCommand
    29.         {
    30.             get { return selectCommand; }
    31.             set { SetProperty(ref selectCommand, value, "SelectCommand"); }
    32.         }
    33.  
    34.         public AnimatorTrigger SelectTrigger
    35.         {
    36.             get { return selectTrigger; }
    37.             set { SetProperty(ref selectTrigger, value, "SelectTrigger"); }
    38.         }
    39.  
    40.         #endregion
    41.  
    42.         void Start()
    43.         {
    44.             exitPopup = () => Debug.Log("Exit Popup State");
    45.             enterPopup = () => Debug.Log("Enter Popup State");
    46.             selectTrigger = new AnimatorTrigger();
    47.             selectCommand = new DelegateCommand(() => selectTrigger.SetTrigger());
    48.  
    49.             BindingManager.Instance.AddSource(this, typeof(AnimatorStateBinderExample).Name);
    50.         }
    51.  
    52.         void OnDestroy()
    53.         {
    54.             BindingManager.Instance.RemoveSource(this);
    55.         }
    56.     }
    57. }
     
  29. jotaserna

    jotaserna

    Joined:
    Oct 9, 2019
    Posts:
    2
    Thanks. It worked like a charm. I changed your code to use a Command as the callback to the ViewModel instead of a regular Action. I think this could be a very nice addition to the framework, so I'll leave it here:

    Code (CSharp):
    1. using Peppermint.DataBinding;
    2. using UnityEngine;
    3.  
    4. // Bind animator state changes to action property.
    5. [Binder]
    6. [RequireComponent(typeof(Animator))]
    7. public class AnimatorStateBinder : MonoBehaviour
    8. {
    9.     public enum TriggerMode
    10.     {
    11.         Enter,
    12.         Exit,
    13.     }
    14.  
    15.     public string path;
    16.     public string stateTag;
    17.     public int layer;
    18.     public TriggerMode triggerMode;
    19.  
    20.     private int targetTagHash;
    21.     private int currentStateTagHash;
    22.     private int previousStateTagHash;
    23.  
    24.     private Animator animator;
    25.     private Binding binding;
    26.     private IDataContext dataContext;
    27.     private ICommand command;
    28.  
    29.     public ICommand Command
    30.     {
    31.         set
    32.         {
    33.             command = value;
    34.         }
    35.     }
    36.  
    37.     void Start()
    38.     {
    39.         // get animator
    40.         animator = GetComponent<Animator>();
    41.         if (animator == null)
    42.         {
    43.             Debug.LogError("Require Animator Component", gameObject);
    44.             return;
    45.         }
    46.  
    47.         CreateBinding();
    48.  
    49.         // initialize tag hash
    50.         targetTagHash = Animator.StringToHash(stateTag);
    51.         currentStateTagHash = animator.GetCurrentAnimatorStateInfo(layer).tagHash;
    52.         previousStateTagHash = currentStateTagHash;
    53.     }
    54.  
    55.     void OnDestroy()
    56.     {
    57.         BindingUtility.RemoveBinding(binding, dataContext);
    58.     }
    59.  
    60.     void Update()
    61.     {
    62.         // save current tag hash
    63.         previousStateTagHash = currentStateTagHash;
    64.  
    65.         // update layer data
    66.         AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layer);
    67.         currentStateTagHash = info.tagHash;
    68.        
    69.         // check tag hash changes
    70.         if (triggerMode == TriggerMode.Exit)
    71.         {
    72.             if (currentStateTagHash != targetTagHash && previousStateTagHash == targetTagHash)
    73.             {
    74.                 OnStateChanged();
    75.             }
    76.         }
    77.         else if (triggerMode == TriggerMode.Enter)
    78.         {
    79.             if (currentStateTagHash == targetTagHash && previousStateTagHash != targetTagHash)
    80.             {
    81.                 OnStateChanged();
    82.             }
    83.         }
    84.     }
    85.  
    86.     void CreateBinding()
    87.     {
    88.         binding = new Binding(path, this, "Command", Binding.BindingMode.OneWay, Binding.ConversionMode.None, null);
    89.         binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
    90.  
    91.         BindingUtility.AddBinding(binding, transform, out dataContext);
    92.     }
    93.  
    94.     void OnStateChanged()
    95.     {
    96.         if (binding.IsBound && command != null)
    97.         {
    98.             command.Execute();
    99.         }
    100.     }
    101. }
    102.  
     
  30. Barritico

    Barritico

    Joined:
    Jun 9, 2017
    Posts:
    372
    Hi.

    Error on web page demos, web page documentation.

    Thanks.
     
  31. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Sorry for the inconvenience, the web server is now restored and all links are available.
     
    Barritico likes this.
  32. Barritico

    Barritico

    Joined:
    Jun 9, 2017
    Posts:
    372
    Thanks!!!!

    No updates on the asset since November / 2018
    Is it compatible with Unity 2019.2 versions, despite this?
    Thank you
     
  33. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Peppermint data binding supports various versions of Unity, including Unity 2019.2 and Unity 2019.3 beta.
     
    Barritico likes this.
  34. Barritico

    Barritico

    Joined:
    Jun 9, 2017
    Posts:
    372
    Thanks!!!
     
  35. CraigWW

    CraigWW

    Joined:
    Jun 10, 2019
    Posts:
    6
    Hi,
    I've been looking at the CollectionBinder, I notice in the method CreateItemView() the item passed into the method isn't used, and the method always instantiates the viewTemplate. Possibly this is intentional, to show as an example.

    I want to instantiate different prefabs (all have the same characteristics, ie prefabs with animations), preferably from a DataSet of them, similar to the SpriteSet. I could use the CollectionMultiViewBinder or maybe I need to make a custom CollectionBinder.

    I assume I'm not fully understanding it. but it essense I want to instantiate a group of gameObjects that have the same signature as the viewTemplate I pass in, using a collection. Also just curious if the CreateItemView ignoring the parameter passed in is a bug or intentional.

    thanks for the great work on the binding framework.
     
  36. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    The CreateItemView(object item) method inherits from IViewFactory and the item parameter specifies the associated source item object. This parameter is only used in CollectionMultiViewBinder and is ignored in other collection binders. That’s because the general collection binders only support single view template and always create the same type of view GO.

    If you need to specify different view templates you can use CollectionMultiViewBinder, the CollectionMultiViewBinderExample demonstrates how to use it in detail. Alternatively, you can easily create your own collection binder to meet your requirements.
     
  37. CraigWW

    CraigWW

    Joined:
    Jun 10, 2019
    Posts:
    6
    thanks, appreciate the response, I created a couple of collection binders myself, that was quite easy, in the framework
     
  38. CraigWW

    CraigWW

    Joined:
    Jun 10, 2019
    Posts:
    6
    Hi super-peppermint, I have a feature request, not necessary, just a "nice to have", a way to export the Data Binding Graph, ie a way to save it as say a pdf or html or just a plain text file would be ok to.

    Thanks again, not a necessary feature just a suggestion.

    Cheers
     
  39. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Here is a quick example to demonstrate how to dump the graph to a custom text format.

    First, open the GraphEditroWindow.cs file and add the following code:
    Code (CSharp):
    1. private static void DumpTreeNode(TreeNode node)
    2. {
    3.     var writer = new CodeWriter();
    4.  
    5.     WriteTreeNode(node, writer, 0);
    6.  
    7.     // copy to system pasteboard
    8.     GUIUtility.systemCopyBuffer = writer.GetText();
    9. }
    10.  
    11. private static void WriteTreeNode(TreeNode node, CodeWriter writer, int depth)
    12. {
    13.     writer.IndentLevel = depth;
    14.  
    15.     foreach (var item in node.nodeList)
    16.     {
    17.         writer.WriteLine($"{item.name} ({item.NodeType})");
    18.  
    19.         if (!string.IsNullOrEmpty(item.ExtraInfo))
    20.         {
    21.             // split extra info
    22.             var tokens = item.ExtraInfo.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
    23.  
    24.             foreach (var token in tokens)
    25.             {
    26.                 // write info text
    27.                 writer.WriteLine($"  {token}");
    28.             }
    29.         }
    30.  
    31.         writer.WriteLine();
    32.     }
    33.  
    34.     foreach (var child in node.children)
    35.     {
    36.         WriteTreeNode(child, writer, depth + 1);
    37.     }
    38. }
    Next modify the CreateGraph() method like this:
    Code (CSharp):
    1. SetDataContextInfo(selectedTransform, root);
    2.  
    3. // ++++++
    4. DumpTreeNode(root);
    5. // ++++++
    Now you can simply click the "Create Graph", and the dumped text will be copied to clipboard.

    NodeTreeDump.png
     
  40. CraigWW

    CraigWW

    Joined:
    Jun 10, 2019
    Posts:
    6
    thanks that's amazingly quick of you to do that, much appreciated.

    thanks
     
  41. viktorkadza

    viktorkadza

    Joined:
    Sep 4, 2018
    Posts:
    46
    Hi, does this stuff supports Two-way collection binding?
     
  42. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    In peppermint data binding, there are two kinds of binding objects: Binding and CollectionBinding. The Binding object directly binds the source and the target property, and it supports two-way binding. But the CollectionBinding object is different, it binds the IEnumerable source to the IViewFactory object, which handles the creation and destruction of view objects.

    Therefore, the CollectionBinding object itself doesn't support two-way binding, but the created collection item object supports two-way binding.

    If you need more information about the collection binding and its usage, you could check the document and example code.
     
  43. Barritico

    Barritico

    Joined:
    Jun 9, 2017
    Posts:
    372
    First of all, I have to say that I want to thank and congratulate you on your asset. It is not something that developers dare to do. Thank you
    .
    That said, I would encourage you guys to update the product a bit. It's a shame that it hasn't been updated for so long. This may mean that you have no error, but you have to adapt a bit to the times.

    In my case, I'm trying to make a car racing timestamp chart. The data is sent by the players to a server that collects it and generates a JSON file. The data in this file is loaded into an observablecollection so that, if one is updated, it will appear updated at the time. But I can't make it to work. I keep investigating.

    This could also apply to databases like firebase or mysql and I think that's where they could make their product bigger and update it with more examples and tools.

    I would be willing to pay for it, of course.

    Thanks for reading and thanks again for the development.
     
  44. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24

    Before setting up the first collection binding, it's recommended that you read the document and try the built-in examples. This prevents some common problems, such as forgetting to add the source or using the wrong collection type, etc.

    Collection binding is easy if you manage the items yourself, but it will become difficult if the items are handled by external managers. To solve this problem, you can use the built-in ListSyncController to handle collection synchronization. If you need more information, please check ListSyncControllerExample.

    Peppermint data binding is easy to extend, you can easily hook it to other event-based modules. For example, you can use data binding to build a complete event chain like this:

    Client click button -> Execute command -> Send RPC to server -> Server processed RPC and push delta state sync -> Sync state to client -> Update client model -> Update client view-model -> Update client UI

    After years of development and updates, peppermint data binding is now stable and feature complete. I will continue to maintain compatibility with latest Unity version. If you have any questions or feature requests, do not hesitate to send me messages or mails.
     
    Barritico and LittleDreamerGames like this.
  45. Barritico

    Barritico

    Joined:
    Jun 9, 2017
    Posts:
    372
    Ok. Thanks
     
  46. revanaii

    revanaii

    Joined:
    Jun 13, 2017
    Posts:
    11
    Does Peppermint have a value dropdown in the inspector for properties? Manual typing is cumbersome and error-prone...
    (screenshot from "Data Bind for Unity")
     
  47. pazzicze

    pazzicze

    Joined:
    Aug 3, 2018
    Posts:
    4
    Hello, I really like the asset.

    Is there a way how to bind to entire context? I have collection of string and want to create a collection view. CollectionBinder works fine but I am not sure how to set the item itself. The string value from collection is DataContext for the Item. WPF supports this by binding to empty string but it is not working here. Am I missing something? Is there any proper way or workaround?

    Code (CSharp):
    1. public class MyViewModel : BindableMonoBehaviour
    2. {
    3.     private ObservableCollection<string> myCollection;
    4.     public ObservableCollection<string> MyCollection
    5.     {
    6.         get { return myCollection; }
    7.         set { SetProperty(ref myCollection, value); }
    8.     }
    9. }
     
  48. super-peppermint

    super-peppermint

    Joined:
    May 16, 2017
    Posts:
    24
    Peppermint data binding only supports properties, and the source type must be a reference type and must have properties. If the source type is a C# built-in type or does not have proper property, you could create a simple wrapper to encapsulate it, and bind to these properties instead.

    Code (CSharp):
    1. public class MyItem
    2. {
    3.     public string StringValue { get; set; }
    4. }
    5.  
    6. private ObservableList<MyItem> myCollection;
    7. private ObservableList<MyItem> MyCollection
    8. {
    9.     get { return myCollection; }
    10. }
     
  49. pazzicze

    pazzicze

    Joined:
    Aug 3, 2018
    Posts:
    4
    Yeah I thought so. It would be nice to have this feature in future :)
    Thank you for quick response.
     
  50. twice7713

    twice7713

    Joined:
    Apr 24, 2018
    Posts:
    24
    ComponentGetter alway slowly then other bind how to fix.
    it show has not been assigned,when i use that component Before Start()