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?
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.
private void InitScrollbarPosition()
{
var data = canvasPrinter.data;
var seperators = new char[] { ',' };
// get max scrollbar position
float maxPosition = data.width - data.sectionWidth;
// get position from canvas data
foreach (var item in data.nodeList)
{
if (string.IsNullOrEmpty(item.metadata))
{
continue;
}
// extract LevelMapNode from metadata
var tokens = item.metadata.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
var nodeType = (LevelMapNode.NodeType)Enum.Parse(typeof(LevelMapNode.NodeType), tokens[0]);
var levelIndex = Int32.Parse(tokens[1]);
// calculate scrollbar position (centered)
var pos = (data.width / 2f + item.anchoredPosition.x - data.sectionWidth / 2) / maxPosition;
pos = Mathf.Clamp01(pos);
// set to node
if (nodeType == LevelMapNode.NodeType.Level)
{
levelNodes[levelIndex].scrollbarValue = pos;
}
}
}
private void GoToNode(int nodeIndex)
{
currentNodeIndex = Mathf.Clamp(nodeIndex, 0, levelNodes.Count - 1);
var node = levelNodes[currentNodeIndex];
Debug.LogFormat("GoToNode {0}", node.Name);
// update scrollbar position
ScrollbarPosition = node.scrollbarValue;
}

3576380–288814–CanvasPrinter_ScrollbarExample.unitypackage (124 KB)
Got it, cool, it helps. Thanks for quick answer.
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?
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
public class PointerBinder : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public string pointerEnterPath;
public string pointerExitPath;
private IDataContext dataContext;
private List<IBinding> bindingList;
private Action enter;
private Action exit;
public Action Enter
{
set
{
enter = value;
}
}
public Action Exit
{
set
{
exit = value;
}
}
void Start()
{
bindingList = new List<IBinding>();
if (!string.IsNullOrEmpty(pointerEnterPath))
{
var binding = new Binding(pointerEnterPath, this, "Enter");
binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
bindingList.Add(binding);
}
if (!string.IsNullOrEmpty(pointerExitPath))
{
var binding = new Binding(pointerExitPath, this, "Exit");
binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
bindingList.Add(binding);
}
BindingUtility.AddBinding(bindingList, transform, out dataContext);
}
void OnDestroy()
{
BindingUtility.RemoveBinding(bindingList, dataContext);
}
public void OnPointerEnter(PointerEventData pointerEventData)
{
if (enter != null)
{
enter.Invoke();
}
}
public void OnPointerExit(PointerEventData pointerEventData)
{
if (exit != null)
{
exit.Invoke();
}
}
}
ToolTipExample
public class ToolTipExample : BindableMonoBehaviour
{
public class Item : BindableObject
{
private string name;
private Action pointerEnter;
private Action pointerExit;
#region Bindable Properties
public string Name
{
get { return name; }
set { SetProperty(ref name, value, "Name"); }
}
public Action PointerEnter
{
get { return pointerEnter; }
set { SetProperty(ref pointerEnter, value, "PointerEnter"); }
}
public Action PointerExit
{
get { return pointerExit; }
set { SetProperty(ref pointerExit, value, "PointerExit"); }
}
#endregion
}
public int itemCount = 5;
private ObservableList<Item> itemList;
private bool showToolTip;
private Item currentItem;
#region Bindable Properties
public ObservableList<Item> ItemList
{
get { return itemList; }
set { SetProperty(ref itemList, value, "ItemList"); }
}
public bool ShowToolTip
{
get { return showToolTip; }
set { SetProperty(ref showToolTip, value, "ShowToolTip"); }
}
#endregion
void Start()
{
itemList = new ObservableList<Item>();
// create items
for (int index = 0; index < itemCount; index++)
{
var item = new Item()
{
Name = string.Format("Item {0}", (char)(index + 'A')),
};
// create actions
item.PointerEnter = () => OnPointerEnter(item);
item.PointerExit = () => OnPointerExit(item);
itemList.Add(item);
}
BindingManager.Instance.AddSource(this, typeof(ToolTipExample).Name);
}
void OnDestroy()
{
BindingManager.Instance.RemoveSource(this);
}
public void OnPointerEnter(Item target)
{
Debug.Log("OnPointerEnter: " + target.Name);
SetToolTip(target);
}
public void OnPointerExit(Item target)
{
Debug.Log("OnPointerExit: " + target.Name);
SetToolTip(null);
}
private void SetToolTip(Item target)
{
if (currentItem != null)
{
// remove old source
BindingManager.Instance.RemoveSource(currentItem);
}
// update current item
currentItem = target;
if (currentItem != null)
{
// add new source
BindingManager.Instance.AddSource(currentItem, "CurrentItem");
}
// show tool tip
ShowToolTip = (currentItem != null);
}
}
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.
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!
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
using System;
using UnityEngine;
namespace Peppermint.DataBinding.Example
{
// Bind animator state changes to action property.
[Binder]
[RequireComponent(typeof(Animator))]
public class AnimatorStateBinder : MonoBehaviour
{
public enum TriggerMode
{
Enter,
Exit,
}
public string path;
public string stateTag;
public int layer;
public TriggerMode triggerMode;
private int targetTagHash;
private int currentStateTagHash;
private int previousStateTagHash;
private Animator animator;
private Binding binding;
private IDataContext dataContext;
private Action stateChanged;
public Action StateChanged
{
set
{
stateChanged = value;
}
}
void Start()
{
// get animator
animator = GetComponent<Animator>();
if (animator == null)
{
Debug.LogError("Require Animator Component", gameObject);
return;
}
CreateBinding();
// initialize tag hash
targetTagHash = Animator.StringToHash(stateTag);
currentStateTagHash = animator.GetCurrentAnimatorStateInfo(layer).tagHash;
previousStateTagHash = currentStateTagHash;
}
void OnDestroy()
{
BindingUtility.RemoveBinding(binding, dataContext);
}
void Update()
{
// save current tag hash
previousStateTagHash = currentStateTagHash;
// update layer data
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layer);
currentStateTagHash = info.tagHash;
// check tag hash changes
if (triggerMode == TriggerMode.Exit)
{
if (currentStateTagHash != targetTagHash && previousStateTagHash == targetTagHash)
{
OnStateChanged();
}
}
else if (triggerMode == TriggerMode.Enter)
{
if (currentStateTagHash == targetTagHash && previousStateTagHash != targetTagHash)
{
OnStateChanged();
}
}
}
void CreateBinding()
{
binding = new Binding(path, this, "StateChanged");
binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
BindingUtility.AddBinding(binding, transform, out dataContext);
}
void OnStateChanged()
{
if (binding.IsBound && stateChanged != null)
{
stateChanged.Invoke();
}
}
}
}
AnimatorStateBinderExample
using System;
using UnityEngine;
namespace Peppermint.DataBinding.Example
{
public class AnimatorStateBinderExample : BindableMonoBehaviour
{
private Action enterPopup;
private Action exitPopup;
private ICommand selectCommand;
private AnimatorTrigger selectTrigger;
#region Bindable Properties
public Action EnterPopup
{
get { return enterPopup; }
set { SetProperty(ref enterPopup, value, "EnterPopup"); }
}
public Action ExitPopup
{
get { return exitPopup; }
set { SetProperty(ref exitPopup, value, "ExitPopup"); }
}
public ICommand SelectCommand
{
get { return selectCommand; }
set { SetProperty(ref selectCommand, value, "SelectCommand"); }
}
public AnimatorTrigger SelectTrigger
{
get { return selectTrigger; }
set { SetProperty(ref selectTrigger, value, "SelectTrigger"); }
}
#endregion
void Start()
{
exitPopup = () => Debug.Log("Exit Popup State");
enterPopup = () => Debug.Log("Enter Popup State");
selectTrigger = new AnimatorTrigger();
selectCommand = new DelegateCommand(() => selectTrigger.SetTrigger());
BindingManager.Instance.AddSource(this, typeof(AnimatorStateBinderExample).Name);
}
void OnDestroy()
{
BindingManager.Instance.RemoveSource(this);
}
}
}
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:
using Peppermint.DataBinding;
using UnityEngine;
// Bind animator state changes to action property.
[Binder]
[RequireComponent(typeof(Animator))]
public class AnimatorStateBinder : MonoBehaviour
{
public enum TriggerMode
{
Enter,
Exit,
}
public string path;
public string stateTag;
public int layer;
public TriggerMode triggerMode;
private int targetTagHash;
private int currentStateTagHash;
private int previousStateTagHash;
private Animator animator;
private Binding binding;
private IDataContext dataContext;
private ICommand command;
public ICommand Command
{
set
{
command = value;
}
}
void Start()
{
// get animator
animator = GetComponent<Animator>();
if (animator == null)
{
Debug.LogError("Require Animator Component", gameObject);
return;
}
CreateBinding();
// initialize tag hash
targetTagHash = Animator.StringToHash(stateTag);
currentStateTagHash = animator.GetCurrentAnimatorStateInfo(layer).tagHash;
previousStateTagHash = currentStateTagHash;
}
void OnDestroy()
{
BindingUtility.RemoveBinding(binding, dataContext);
}
void Update()
{
// save current tag hash
previousStateTagHash = currentStateTagHash;
// update layer data
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layer);
currentStateTagHash = info.tagHash;
// check tag hash changes
if (triggerMode == TriggerMode.Exit)
{
if (currentStateTagHash != targetTagHash && previousStateTagHash == targetTagHash)
{
OnStateChanged();
}
}
else if (triggerMode == TriggerMode.Enter)
{
if (currentStateTagHash == targetTagHash && previousStateTagHash != targetTagHash)
{
OnStateChanged();
}
}
}
void CreateBinding()
{
binding = new Binding(path, this, "Command", Binding.BindingMode.OneWay, Binding.ConversionMode.None, null);
binding.SetFlags(Binding.ControlFlags.ResetTargetValue);
BindingUtility.AddBinding(binding, transform, out dataContext);
}
void OnStateChanged()
{
if (binding.IsBound && command != null)
{
command.Execute();
}
}
}
Hi.
Error on web page demos, web page documentation.
Thanks.
Sorry for the inconvenience, the web server is now restored and all links are available.
Thanks!!!
No updates on the asset since November / 2018
Is it compatible with Unity 2019.2 versions, despite this?
Thank you
Peppermint data binding supports various versions of Unity, including Unity 2019.2 and Unity 2019.3 beta.
Thanks!!!
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.
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.
thanks, appreciate the response, I created a couple of collection binders myself, that was quite easy, in the framework
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
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:
private static void DumpTreeNode(TreeNode node)
{
var writer = new CodeWriter();
WriteTreeNode(node, writer, 0);
// copy to system pasteboard
GUIUtility.systemCopyBuffer = writer.GetText();
}
private static void WriteTreeNode(TreeNode node, CodeWriter writer, int depth)
{
writer.IndentLevel = depth;
foreach (var item in node.nodeList)
{
writer.WriteLine($"{item.name} ({item.NodeType})");
if (!string.IsNullOrEmpty(item.ExtraInfo))
{
// split extra info
var tokens = item.ExtraInfo.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
foreach (var token in tokens)
{
// write info text
writer.WriteLine($" {token}");
}
}
writer.WriteLine();
}
foreach (var child in node.children)
{
WriteTreeNode(child, writer, depth + 1);
}
}
Next modify the CreateGraph() method like this:
…
SetDataContextInfo(selectedTransform, root);
// ++++++
DumpTreeNode(root);
// ++++++
…
Now you can simply click the “Create Graph”, and the dumped text will be copied to clipboard.

thanks that’s amazingly quick of you to do that, much appreciated.
thanks