Search Unity

[RELEASED] Data Bind for Unity

Discussion in 'Assets and Asset Store' started by coeing, Feb 16, 2015.

  1. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    The new version 1.0.9 of Data Bind was just submitted and is now in review :)

    If there are any issues you are still missing, there is a public issue tracker where everybody can add their wishes, bug reports, ideas, feedback,...:

    https://bitbucket.org/coeing/data-bind/issues

    The main additions:
    - Two new examples (ActiveSetter and DropDown)
    - ActiveSetter has a toggle now to keep it enabled although the component gets inactive
    - Add base class for data converters and some basic converters (Vector3 -> Value, Quaternion -> Euler, Euler -> Quaternion)
    - Started with a generic converter for the items of a collection (similar to Linq Select)
    - Several small fixes and improvements for reported issues

    Here is the full changelog:
    * Issue #21 Add field to make ActiveSetter not disabled when the game object it is on is deactivated

    * Add unit tests for ActiveSetter

    * Use IDataProvider interface in DataBinding instead of concrete class (Makes unit testing much easier)

    * Add example for ActiveSetter

    * Issue #25 Not throwing warning for missing data property of a member of a non-context class

    * Issue #39 Only try loading settings from default path if no DataBindSettings asset was found

    * Issue #42 Updating count of collection before triggering event, so in the event handler the correct count is available

    * Issue #37 Add example how to use data bind with a Unity UI dropdown element

    * Don't get concrete collection, but base class in CollectionDataBinding (The CollectionDataBinding might be used non-generic, so it would fail if trying to get a Collection<object> from the binding)

    * Add concrete InvokeCommand methods to Command class to call the methods with parameters via UnityEvents

    * Change ContextDataProvider to be generic and don't need a concrete implementation to get a data value from a bound context

    * Issue #37 Add converter for the items of a collection

    * Add setter for the options of a Unity UI dropdown element

    * Add utility classes to observe a collection/collection binding

    * Use data property in collection example and add button to replace whole collection

    * Issue #33 Add ContextDataUpdater to make ComponentSingleGetter obsolete (Instead of the ComponentSingleGetter a ComponentDataProvider and a ContextDataUpdater can be used which makes usage more flexible (data value from component can be used before being set on the context))

    * Issue #33 Cache value of target binding instead of calling GetComponent each time

    * Use Assert.Throws instead of ExpectedException in unit tests (Unity 5.6 updated to NUnit 3.0 where this attribute doesn't exist anymore)

    * Add provider for getting the mesh of a mesh filter

    * Add base class for data providers which can't use an event to determine if their component data value changed (E.g. for transform values like position, local position, rotation and local rotation)

    * Add converters from Vector3 to Value, Quaternion to Euler Angles and Euler Angles to Quaternion

    * Use SetText method instead of text property to set text of TextMeshPro component (Font size was reset when initially set the text via the property.)

    The new version will be available in a few days in the Unity Asset Store:

    https://www.assetstore.unity3d.com/#!/content/28301
     
  2. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Tanks for the reply. So I have purchased this and just trying to get my head around it. First the basics, then eventually the Strange add-on because I am trying to learn and use Strange.

    Maybe I am being dense but I'm still not seeing it properly from the examples included.

    Let's say I have a DataModel, of something, like a Rocket engine. This Model will have properties on it like say Thrust, Mass, Name, and Description.

    I then have a UI that let's the player browse through engines and see the stats on the various ones in the database. I want to bind the properties of the Engine to the UI elements, so that each time the user selects a different engine, the UI would update with the properties of that engine.

    Do I make a Context for each property, or one Context for the entire View? If just one context, then give the naming conventions, how do you distinguish one from another that uses the same data type?
     
  3. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Also is there an example on how to use the StrangeIoC add-on? The autogenerated documentation really isn't very clear to me.
     
  4. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @jwvanderbeck,

    The rocket engine would have a context like this:
    Code (CSharp):
    1.    
    2.     public class RocketEngineContext : Context
    3.     {
    4.         private readonly Property<float> thrustProperty =
    5.             new Property<float>();
    6.  
    7.         public float Thrust
    8.         {
    9.             get { return this.thrustProperty.Value; }
    10.             set { this.thrustProperty.Value = value; }
    11.         }
    12.  
    13.         private readonly Property<float> massProperty =
    14.             new Property<float>();
    15.  
    16.         public float Mass
    17.         {
    18.             get { return this.massProperty.Value; }
    19.             set { this.massProperty.Value = value; }
    20.         }
    21.  
    22.         private readonly Property<string> nameProperty =
    23.             new Property<string>();
    24.  
    25.         public string Name
    26.         {
    27.             get { return this.nameProperty.Value; }
    28.             set { this.nameProperty.Value = value; }
    29.         }
    30.  
    31.         private readonly Property<string> descriptionProperty =
    32.             new Property<string>();
    33.  
    34.         public string Description
    35.         {
    36.             get { return this.descriptionProperty.Value; }
    37.             set { this.descriptionProperty.Value = value; }
    38.         }
    39.     }
    40.  
    Now let's say the user can open a shop to buy a new engine for his space ship. For the shop window you would have a specific context that will be filled when the window opens and that the UI of the window will use:
    Code (CSharp):
    1.     public class ShopWindowContext : Context
    2.     {
    3.         private readonly Property<Collection<RocketEngineContext>> rocketEnginesProperty
    4.             = new Property<Collection<RocketEngineContext>>(
    5.                 new Collection<RocketEngineContext>());
    6.  
    7.         public Collection<RocketEngineContext> RocketEngines
    8.         {
    9.             get { return this.rocketEnginesProperty.Value; }
    10.             set { this.rocketEnginesProperty.Value = value; }
    11.         }
    12.     }
    With this context the shop window can create an entry in the UI for each rocket engine (e.g. from a prefab using the GameObjectItemsSetter) and show the data of each rocket engine in a text field and/or image.

    Hope this quick example makes the usage a bit clearer.
     
  5. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    No, right now there is no example for StrangeIoC as the addon has just a few classes. Basically you derive from DataContextMediator and a DataContextView which are both based on a Context class instead deriving from a normal Mediator and View.

    Than you can steer nearly everything from the mediator (i.e. registering for events of the context and updating the data in the context). The view becomes nearly obsolete now as you bind your UI to the data from the context.
     
  6. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Ah yes that does make it more clear. This bit from the documentation is what confused me:

    > To make sure the bindings will find the data properties without changing the interface of the data context class, there is a naming convention for the data property. The bindings expect the data property for a data value to be named with the name of the data (e.g. Text) plus "Property".

    I thought that meant that I *had* to call the property something like floatProperty rather than thrustProperty. so that was the root of my confusion.

    Thank you!
     
    coeing likes this.
  7. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Ah, I see. Thanks for telling me, I will see how I can rephrase it :)
     
  8. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Yeah its obvious after your example above, just the ay that is worded was confusing. Perhaps just replace "text" with something that isn't a type of data, something like "age" or "name" or some sort of generic data name rather than a data type :)
     
  9. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Good idea, will do it like that :)
     
  10. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Aaaaand, it's in store! Version 1.0.9 successfully got reviewed and can be downloaded/bought from https://www.assetstore.unity3d.com/#!/content/28301

    If you encounter any issues, don't hesitate to report them at https://bitbucket.org/coeing/data-bind/issues Feedback is always welcome!
     
  11. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hello Christian,
    I tried to use the Additionalparameters, but i have failed, because you have to fill the DataProvider there and i do not know, how to fetch specific value of context as DataProvider.

    I have this class
    Public enum Action { close, open }
    Public class MyContext:Context
    {
    private readonly Property<Action> actionLeftProperty = new Property<Action>();
    private readonly Property<Action> actionRightProperty = new Property<Action>();

    public Action ActionLeft
    ...​

    public ButtonClicked(Action whichAction)
    {
    ...​
    }​
    }

    I have panel with this context attached. That panel has two buttons. Each button has one action assigned. I would like to have ButtonClicked as ButtonCommand, when i pick ActionLeft/ActionRight as parameter.

    I know, that i can have the ButtonContext, which would contains the Action enum inside, that i would be able to pass the ButtonContext somehow, but do i need to? I already have three levels of context and I would like to avoid the fourth one.

    I am not sure, that i can use this solution, because i have like 20 actions in my list and the buttons (and their actions) are generated dynamically. There are like 40 combinations of what kind of actions MyContext can have. How do i know, what action should be called on buttonCommand, when the LeftButton may contain more dirrerent ones (enum)?
     
  12. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    I see. Okay, let's try it with the additional parameters then :)

    In one of the tests I used a ConstantDataProvider which just returns a set value. I'll move that as the class ConstantObject into the next version (1.0.10). In your case you can write a data provider that returns an enum value you selected:
    Code (CSharp):
    1. /// <summary>
    2.     ///   Base class to provide a constant value.
    3.     /// </summary>
    4.     /// <typeparam name="T">Type of value the class provides.</typeparam>
    5.     public class ConstantObject<T> : DataProvider
    6.     {
    7.         /// <summary>
    8.         ///   Constant value to provide.
    9.         /// </summary>
    10.         public T ConstantValue;
    11.  
    12.         /// <inheritdoc />
    13.         public override object Value
    14.         {
    15.             get
    16.             {
    17.                 return this.ConstantValue;
    18.             }
    19.         }
    20.     }
    21.  
    22.     public class EnumActionObject : ConstantObject<GameAction>
    23.     {
    24.     }
    25.  
    26.     public enum GameAction
    27.     {
    28.         None,
    29.  
    30.         Open,
    31.         Close
    32.     }
    This way you can define the enum value that each button should provide:
    upload_2017-6-30_23-42-23.png

    Hope that way works for you :)

    By the way: Thanks for switching to the forum!
     
  13. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    I can use this as a workaround, because i do not have constant actions. In my case, i have two physical UI buttons, but lots of more actions, that i can attach to them - dynamically. Action, that is attached to them is determined inside Context, which is filled inside GameView, which is based on actual game state inside GameLogic class.

    I could use that to determine, which button have been pressed and because each button has a defined action (in the given time frame), i would be able to invoke the right action.

    This is similar solution as if i define DoLeftButtonAction/DoRightButtonAction inside context and call then as ButtonCommand for Left/Right button.

    The best solution would be, if there is DataProvider, which can load data directly from the context. Specific property. I would be use it to fetch and pass LeftButtonAction/RightButtonAction properties and invoke the right action directly.
     
  14. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Ah, sorry, I didn't read carefully enough. If you want to have it completely dynamic you can use your Action data properties in AdditionalParameters indeed. Have a look at the ContextDataProvider which fetches a data value from a specific context:
    upload_2017-7-2_15-37-17.png

    In your case the Path would be ActionLeft or ActionRight then. You can set the ContextType to have a nice drop down for the path, but you can as well just enter the path manually.
    If you don't fill in the Path/Custom Path of the Context, the ContextDataProvider will use the context of the ContextHolder it is placed under.

    Hope this finally solves your case :) If not, I will happily give it another try ;)
     
  15. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    It works, thank you!
     
  16. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Yay, finally! :)
     
    Jochanan likes this.
  17. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hello,
    In the latest version Example StringToUpperStringUnity, you are using InputFieldGetter, which is obsoleted
    ifts.PNG
    When i change it to the InputFieldTextProvider, i cannot set the path, that is updated by the component, because it is not there iftp.PNG
    How do i use the new component for the same functionality?
     
  18. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @Jochanan,
    Good catch! :) You need the ContextDataUpdater as well. In that script you can specify the path of the context to store the text from the input field. I added a bug issue for that and will fix it for the next version. So thanks!
     
    Jochanan likes this.
  19. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi @coeing
    It works. I am just curious about what is the reason behind splitting into two separate components?

    I have one more serious topic to cover.
    I have bought two assets in the past, which is used as optimized list views. It may be used for example for chat messages. You do not want to instantiate 1000 chat message prefabs, but only the ones, which are visible.

    I would like to ask you about general approach about how to integrate these types of things into DataBinding approach?
     
  20. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Sorry for the late answer, I had a great Game Jam weekend :)
    The reason is that the functionality of the ContextDataUpdater can be used in a much more general sense without having to duplicate code in every former Getter class. Plus the obsolete Getter scripts lacked the possibility to modify the provided value e.g. by a Formatter before writing them into the context. Now you can put several data binding scripts in between before writing the fetched data into the data context.

    Have a look at the base class ItemsSetter and the (real basic) implementation GameObjectItemsSetter. Those classes take a collection as their data binding. The GameObjectItemsSetter instantiates a game object from a specified Prefab for each item in the collection, uses the item as the context for the created game object and puts the game object below the target game object.

    I don't know excactly which assets you purchased, but I worked with the EnhancedScroller in one project. In general those assets require a data source of some kind, so you can just give them a bound data collection and may use the ItemsSetter as a base class for this. Or maybe a CollectionDataBinding might be enough, too.

    Hope this gives you a few pointers how to connect those assets with data bind. It should definitively be possible :)
     
  21. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Both components i would like to use have a prefab as a param an they are Instantiating the prefabs on their own. There is nothing i can do about that.

    Both of them have to have access to the data - i might give them data, which are derived from Context class. Additionally both of them have something like update function, which is used to set/fill prefab object

    Looks like
    protected override void UpdateViewsHolder(SpecificHolder derivedFromSpecificClass)
    inside SpecificHolder class, which has to be derived from specific asset class which is used for setting up the content of the prefab from data.
    What if i add ContextHolder component to the prefab, set the UI using context and set the right context (based on item index of the list) inside "update" function. Something like that.
     
  22. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Yes, that's sounds like the way to go. Let me know if it works. If not, just post a small code snippet of the Setup class and methods.
     
  23. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    It is working. I will be experimenting with that a bit, so i will let you know if i stuck with something
     
  24. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Great, always good to hear :) Just let me know if you encounter further problems.
     
  25. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    My only problem is, that i need to listen to changes in collection. I need to set the Count of the collection every time it changes. I added a new event to your collection class, which is fired when the Count value is changed. I did not want to listen all the other events when i am interested just in Count. Is there any other way than adding the event for count?

    Currently, it looks like that
    Code (CSharp):
    1.         /// <summary>
    2.         ///     Number of items in the collection.
    3.         /// </summary>
    4.         public int Count
    5.         {
    6.             get
    7.             {
    8.                 return this.countProperty.Value;
    9.             }
    10.             protected set
    11.             {
    12.                 this.countProperty.Value = value;
    13.                 if (this.CountChanged != null)
    14.                 {
    15.                     this.CountChanged(this.Count);
    16.                 }
    17.             }
    18.         }
     
  26. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Mmh, you should probably use those existing events, that's what they are there for ;) Where exactly do you need to listen to changes of the collection? If it's in a Data Binding script, you can wrap the DataBinding into a CollectionDataBinding which has some easier setup.
    If you have to observe a specific collection, check out the CollectionObserver class :)
     
  27. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    I have to join all of these:
    - Context (for example ChatContext), that countains collection of Contexts for example MessageContext)
    - prefab, that is linked to the MessageContext
    - third party asset, which takes a prefab and implements a function, that sets up the Context of the prefab based on the index of the index

    Third party asset contains Collection of MessageContext so it can set up the specific MessageContext to specific prefab based on index. It instantializes/fills only prefabs, that are visible on the screen. Does not matter if i have 10 messages or 1000 of them i still have same amount of prefabs instantiated and filled with specific context based on the position in the collection.

    The third party asset is derived from ScrollRect and it needs to know how many items Messages are there (so it can scroll to the beggining and the end of the panel). Thats why i need to monitor the "Count" changes. Content of the instantialized prefabs are filled inside special function and it just sets up the right MessageContext. I could listen to the all events, but at the end, i would just do this.itemsCount = Collection.Count. I wanted to know, whether there is any other way.
     
  28. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Which third party asset is it exactly? I may have a look at the documentation.

    But in general it sounds like the way it was done in an asset I used in a past project.

    I like to avoid that you have to make changes to the DataBind asset itself as this would make it harder to update to a new version later (you would have to track all the changes you made and re-apply them to the updated version). That's why I suggested using the existing events, the CollectionDataBinding or the CollectionObserver.

    A 4th possibility would be to bind the Count property of the collection to a separate DataBinding with a path like "MyMessages.Count". The Count property has a backing field as well, so it would tell you when its value changed.
     
  29. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    I am using https://www.assetstore.unity3d.com/en/#!/content/68436 and https://www.assetstore.unity3d.com/en/#!/content/31412
    How would i do that? Property field count inside Collection is private readonly.
     
  30. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    You would do that in a custom DataBindingOperator. I can't tell exactly because I don't now how your code is structured that connects Data Bind and the other asset. If you like, just post the class where you access the collection and register for the CountChanged event right now, than I may be able to suggest an alternative way.
     
  31. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,347
    Hi,

    I have a question related to the deprecated "Getters".
    As I understood they are now split into a Provider and a ContextDataUpdater (I am refering to
    Jochanans question: https://forum.unity3d.com/threads/released-data-bind-for-unity.298471/page-4#post-3139089).

    The Situation:

    No here is a situation which I am not sure how to solve properly.

    1) I have an input field in which a user can type in arbitrary text.
    2) The value (string) needs to get parsed into a float and then handed over to a context.

    The Problem:

    The problem is that the parsing of the string may not be successful and thus I want to return the current value of the data in the context as fallback - but due to the split, the provider no longer has any information on the context (the bound data).

    The Solution (Workaround):

    I think there are multiple possible solutions:
    1) Get the transform of the gameobject and look in die upper hierarchy for the ContextHolder and fetch the data from there. But that means reimplementing some functionality which is already part of DataBind (context lookup).
    2) Do something similar by trying to find a ContextDataUpdater and fetch the data from there (not quite sure how, but I guess it should be possible).
    3) Store the lastValid value in the Provider and fall back to that value if parsing fails (that's what I have chosen to do).

    The Question:

    Now my question is: how should we access the data? My guess would be that looking for a ContextDataUpdater is the cleanest solution, but then this (kind of) reverts the "split" and simulates what the old getters have done.

    Source Code for clarity:

    Here is the Code I am using right now:

    The situation can be avoided by forcing users to only input valid data, but as you can see I am using a "mathematical expression evaluator" to handle the string and thus an arbitrary string needs to be allowed as input.

    Code (CSharp):
    1. namespace Slash.Unity.DataBind.UI.Unity.Getters
    2. {
    3.    using Slash.Unity.DataBind.Foundation.Providers.Getters;
    4.    using System;
    5.    using UnityEngine;
    6.    using UnityEngine.UI;
    7.    using AK;
    8.    using System.Globalization;
    9.  
    10.    /// <summary>
    11.    ///     Provides the text of an input field as float number.
    12.    /// </summary>
    13.    [AddComponentMenu("Data Bind/UnityUI/Getters/[DB] Input Field float Provider (Unity)")]
    14.     public class InputFieldFloatProvider : InputFieldGenericProvider<float>
    15.     {
    16.         ExpressionSolver alu = new ExpressionSolver();
    17.  
    18.         /// <inheritdoc />
    19.         protected override float GetValue(InputField target)
    20.         {
    21.             var text = target.text;
    22.             try
    23.             {
    24.                float valueAsFloat;
    25.                if( float.TryParse(target.text.Replace(',','.'), NumberStyles.Any, CultureInfo.InvariantCulture, out valueAsFloat) )
    26.                {
    27.                   lastValidValue = valueAsFloat;
    28.                   return valueAsFloat;
    29.                }
    30.                else
    31.                {
    32.                   alu.SetGlobalVariable("x", lastValidValue );
    33.                   var evaluationResult = alu.EvaluateExpression(text);
    34.                   var result = Convert.ToSingle(evaluationResult);
    35.                   lastValidValue = result;
    36.                   return result;
    37.                }
    38.             }
    39.             catch
    40.             {
    41.                // THIS IS THE PROBLEM - how to access the context data here?
    42.                return lastValidValue;
    43.             }
    44.         }
    45.     }
    46. }
    The base Class is derived from ComponentDataProvider and looks like this:
    Code (CSharp):
    1. namespace Slash.Unity.DataBind.UI.Unity.Getters
    2. {
    3.     using Slash.Unity.DataBind.Foundation.Providers.Getters;
    4.     using UnityEngine;
    5.     using UnityEngine.UI;
    6.  
    7.     /// <summary>
    8.     ///     Provides the text of an input field as generic of Type TData.
    9.     /// </summary>
    10.     public abstract class InputFieldGenericProvider<TData> : ComponentDataProvider<InputField, TData>
    11.     {
    12.         protected TData lastValidValue;
    13.      
    14.         /// <summary>
    15.         ///   If set, the ValueChanged event is only dispatched when editing of the input field ended (i.e. input field left).
    16.         /// </summary>
    17.         [Tooltip("If set, the ValueChanged event is only dispatched when editing of the input field ended (i.e. input field left).")]
    18.         public bool OnlyUpdateValueOnEndEdit;
    19.  
    20.         /// <inheritdoc />
    21.         protected override void AddListener(InputField target)
    22.         {
    23.             target.onValueChanged.AddListener(this.OnInputFieldValueChanged);
    24.             target.onEndEdit.AddListener(this.OnInputFieldEndEdit);
    25.         }
    26.  
    27.         /// <inheritdoc />
    28.         protected override abstract TData GetValue(InputField target);
    29.  
    30.         /// <inheritdoc />
    31.         protected override void RemoveListener(InputField target)
    32.         {
    33.             target.onValueChanged.RemoveListener(this.OnInputFieldValueChanged);
    34.             target.onEndEdit.RemoveListener(this.OnInputFieldEndEdit);
    35.         }
    36.  
    37.         private void OnInputFieldEndEdit(string newValue)
    38.         {
    39.             if (this.OnlyUpdateValueOnEndEdit)
    40.             {
    41.                 this.OnTargetValueChanged();
    42.             }
    43.         }
    44.  
    45.         private void OnInputFieldValueChanged(string newValue)
    46.         {
    47.             if (!this.OnlyUpdateValueOnEndEdit)
    48.             {
    49.                 this.OnTargetValueChanged();
    50.             }
    51.         }
    52.     }
    53. }
    Thank you.
     
    Last edited: Jul 27, 2017
  32. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @_geo__,

    Thanks for using Data Bind! It's an interesting case you have there :)

    I think you won't require the InputFieldGenericProvider at all. Instead you should create something like a StringToFloatConverter:

    Code (CSharp):
    1. using System.Globalization;
    2. using Slash.Unity.DataBind.Foundation.Providers.Converters;
    3.  
    4. public class StringToFloatConverter : DataConverter<string, float?>
    5. {
    6.     private readonly ExpressionSolver alu = new ExpressionSolver();
    7.  
    8.     /// <inheritdoc />
    9.     protected override float? Convert(string text)
    10.     {
    11.         try
    12.         {
    13.             float valueAsFloat;
    14.             if (float.TryParse(text.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture,
    15.                 out valueAsFloat))
    16.             {
    17.                 return valueAsFloat;
    18.             }
    19.  
    20.             // Not sure why you need to set the last valid value in the expression solver.
    21.             // this.alu.SetGlobalVariable("x", lastValidValue);
    22.             var evaluationResult = this.alu.EvaluateExpression(text);
    23.             var result = System.Convert.ToSingle(evaluationResult);
    24.             return result;
    25.         }
    26.         catch
    27.         {
    28.             // Couldn't convert value, return null.
    29.             return null;
    30.         }
    31.     }
    32. }
    Now to set up your data flow:

    InputFieldTextProvider

    StringToFloatConverter (Data: InputFieldTextProvider)

    FallbackValueFormatter (Data: StringToFloatConverter, Fallback: Float Value from Context)

    Hope I got the problem correct and that works as a solution or at least helps to find one.
     
  33. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,347
    Hi,

    thank you for your quick reply.
    I think it is solved. I'll present my solution below.
    Please let me know if that's the right way to do it.

    My initial question "how should we access the data?" is answered by the FallbackValueFormatter, thank you for that. I have completely overlooked that handy class (sorry). But, I think I can not use it in my special case.

    The remaining problem is that the line
    this.alu.SetGlobalVariable("x", lastValidValue);
    is very important.

    I'll explain why:
    1) Let's say the value in an input (and in the data) is 1 (data: 1.0f, input: "1.0").
    2) The user then types in "X*2" into the input field.
    3) Now the StringToFloatConverter would need to evaluate the expression (multiply X with 2) and return the result (should be 2.0f). But in order to be able to do that it has to know what the value of X was in the beginning, thus the "lastValidValue" part.

    Solution:
    In my case the StringToFloatConverter needs not one but TWO input values:
    1. the string
    2. and the former float value (to set the X value in the evaluator)

    I will create a subclass of the DataConverter (StringExpressionToFloatConverter) with an additional binding for the X value (FallbackAndEvalInput). In there I will plug the context + path I need. The string value will arrive just as always with the DataConverter.Data binding. It will return the value of FallbackAndEvalInput if the evaluation of the expression fails.

    The new flow would be:

    InputFieldTextProvider

    StringExpressionToFloatConverter (Data: InputFieldTextProvider, FallbackAndEvalInput: Float Value from Context )
    (derived from DataConverter)

    ContextDataUpdater ( Data: value from StringExpressionToFloatConverter )

    Hope that makes any sense.
    Thank you for your help.
     
    Last edited: Jul 27, 2017
  34. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Glad that you found a solution!

    It looks good from my side. Converting the string expression which the user entered in the text field to a float is exactly what the StringExpressionToFloatConverter does :)

    Maybe you can still use the FallbackValueFormatter to provide the fallback value if the expression solving fails (later you may want to know if the expression could be resolved e.g. to show a warning or so). But you can also change that later if needed.
     
  35. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,347
    Showing a warning is a good idea, thank you for your help :)
     
  36. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hi,

    So for the lastest version, There is a bug (or maybe it's intentional but that would be a UX issue then), the Reference of a LayoutGroupItemsSetter can only be set to a RectTransform for it to work, if you bind it to a GameObject that doesn't work.

    Since when you drop something on the Reference field, the popup ask you which component you want to bind, there is no way to know that you need to bind only the RectTransform...

    Is it a bug in the LayoutGroupItemsSetter ?

    Thx
     
  37. JoergWinterstein

    JoergWinterstein

    Joined:
    Oct 11, 2012
    Posts:
    3
    Hi.

    I am having trouble using Data Binding on a TMP_InputField.

    I have written my own Setter and Provider classes using ComponentSingleSetter<TMP_InputField, string> and ComponentDataProvider<TMP_InputField, string> accordingly. Basically, they are just copies of InputFieldTextSetter and InputFieldTextProvider using TMP_InputField instead of InputField.

    Setting the text to the desired property works, but getting a value once the Provider changes does not.
    I have tried using the standard InputField setter and providers as well. I cant get it to work, too.

    I must be missing something in the basic "setup" of the components in the editor to be able to have a property displayed by an input field or TMP_InputField respectively.

    Could you please provide a basic solution? I did not find anything useful, and the InputFieldGetter example uses the deprecated InputFieldGetter provider.
     
  38. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hello, how does the ContextDataProvider works?

    My original thoughts was, that it takes some context and path to property inside that context and outputs a value of some property in the given context and path. When the property inside that context changed, the output value ContextDataProvider changes.

    But when the content of property changed, the ContextDataProvider is still the same. It only updates, when the Context itself changes.

    I did experiment and changed ContextDataProvider.UpdateValue to
    Code (CSharp):
    1. /// <inheritdoc />
    2. protected override void UpdateValue()
    3. {
    4.     var context = this.Context.GetValue<Context>();
    5.     object newData = null;
    6.     if (context != null)
    7.     {
    8.         context.RegisterListener(this.Path, (object newValue) => { this.data = newValue; this.OnValueChanged(); });
    9.         newData = this.GetDataValue(context);
    10.     }
    11.     if (!Equals(newData, this.data))
    12.     {
    13.         this.data = newData;
    14.         this.OnValueChanged();
    15.     }
    16. }
    which does what i need - create a DataProvider for specific property of specific context that changes its value when the binded property changes. Did i misunderstand the function of ContextDataProvider or it is bugged? Is there different provider for my use case? Thanks.

    PS: I know, that i should be unregistering listeners as well, this was just a quick test
     
  39. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    First of all: Sorry for the late reply. Unity haven't informed me about new replies in this thread :/

    About your issue: It is intentional but a UX issue indeed. I already added an issue some time ago (https://bitbucket.org/coeing/data-bind/issues/44/add-hint-when-assigning-wrong-target-value), but it is a bigger change to make it work correctly. I hope that I can tackle it for the next version 1.0.11 (planned for October).

    The idea is that the Reference field will automatically take the mono behaviour of the dragged game object that it needs, shows a selection popup like it does now if it finds multiple and shows up a warning if it doesn't find any.
     
  40. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @JoergWinterstein,

    Sorry for the late reply, I wasn't informed about the new replies in this thread :/

    Your approach sounds correct. In the new version 1.0.10 which I submitted yesterday, the InputFieldGetter example now uses the correct way with a ContextDataUpdater that writes the value of the InputFieldTextProvider into the data context. Did you use a ContextDataUpdater in your setup?
     
  41. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @Jochanan,

    Sorry for the late reply to you, too! Haven't been informed about the new replies in this thread :/

    Your idea about the ContextDataProvider is correct and your observation that it's a bug as well. I'm glad to tell you that I stumbled upon this bug as well and it is fixed in the new version 1.0.10 which was submitted yesterday to the Asset Store. It registers a listener and removes it correctly as well. So hopefully you can wait a few more days for the update to hit the store, so your problem is fixed.

    Thanks anyway for reporting the issue! I won't be able to find all issues by myself, so every hint is valuable :)
     
  42. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    The new version 1.0.10 of Data Bind was submitted yesterday and is now in review :)

    If there are any issues you are still missing, there is a public issue tracker where everybody can add their wishes, bug reports, ideas, feedback,...:

    https://bitbucket.org/coeing/data-bind/issues

    The main additions:
    - Usage of any INotifyPropertyChanged class as a data context (e.g. when working with Protobuf)
    - Two new examples (Two-Way-Bindings and NotifyPropertyChanged usage)
    - Several fixes for bugs from the issue tracker

    A big thank you to SLM Solutions Software (https://slm-solutions.de/) from Austria who sponsored one work day for the implementation of the INotifyPropertyChanged extension! So if you have a specific thing that is missing for you in Data Bind, send me a message and I'll see how long it will take. If it's not on the agenda yet, I'm willing to implement it as a small freelancer job.

    Here is the full changelog:
    * Use OnValueChanged without parameter in ContextDataProvider instead of obsolete version

    * Clean up RoundToNearestOperation

    * DataBindingOperator only IsInitialized when the Init method is called. Otherwise it may not have added its bindings and return the default value.

    * Add example of two way bindings (input field and slider)

    * Add utility property to NotifyPropertyChangedDataContext to easily access wrapped data object

    * Issue #53 Wrap INotifyPropertyChanged class inside NotifyPropertyChangedDataContext automatically in ContextHolder

    * Issue #52 Using NETFX_CORE define instead of UNITY_WSA/UNITY_METRO

    * Issue #46 Use InputFieldTextProvider/InputValueProvider and ContextDataUpdater instead of obsolete InputFieldTextGetter/InputValueGetter

    * Add unit tests to check if a property in a sub context that implements INotifyPropertyChanged can be accessed and triggers value changes

    * Issue #51 Use !field.IsPublic instead of field.IsPrivate. field.IsPrivate flag is always false

    * Add TypeInfoUtils to have WSA specifics in one place

    * Show any list instead of only Collections for ContextHolder in inspector

    * Update value of ContextDataProvider when value inside context changed

    * Add MeshFilterMeshSetter

    * Change folder name from NotifyPropertyChangedExample to NotifyPropertyChanged to be consistent with former examples

    * Rename DataContextNode to DataNode and use Branch and Leaf data nodes to build data tree

    * Use INodeValueObserver to unify observation of a node value, either with a DataProvider or INotifyPropertyChanged mechanic

    * Rename ContextNode to DataContextNodeConnector

    * Wrap INotifyPropertyChanged class when selected as context type and context should be created automatically

    * Move out some stuff from ContextHolderEditor

    * Draw data class in inspector when NotifyPropertyChangedDataContext is used as a wrapper

    * Add option to only trigger ValueChanged of an InputFieldTextProvider when editing ended

    * Add non-generic version of CollectionDataBinding

    * Move common code into BranchDataContextNode and static methods into DataBindingSettingsProvider and out of DataContextNode. Make as much as possible private for DataContextNode

    * Issue #48 Special case for getting paths for MasterPath path dropdown

    * Add example for usage of MasterPath

    * Issue #26 Show paths for custom serializable classes as well

    * Add header to DataContextNode

    * Issue #47 Use Equals to check for value equality instead of == operator

    * Add number increase/decrease buttons to NotifyPropertyChangedExample

    * Specify data context type of context holder in NotifyPropertyChangedExample

    * Use concrete class instead of NSubstitute in NotifyPropertyChangedDataContextTest

    * Use INotifyPropertyChanged classes as context types and create paths for those types

    * Cache data context paths in PathPropertyDrawer

    * Move data node handling into DataTree to avoid code duplication

    * Rename NotifyPropertyChangedContext to NotifyPropertyChangedDataContext

    * Add unit tests for NotifyPropertyChangedContext

    * Add example to test NotifyPropertyChanged data contexts

    * Add wrapper for data context that use INotifyPropertyChanged instead of data properties

    * Add null reference check to EnumGetter to avoid an exception

    * Add base class for a data provider that provides a constant value

    The new version will be available in a few days in the Unity Asset Store:

    https://www.assetstore.unity3d.com/#!/content/28301
     
  43. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    And it's already published! Hope you enjoy the new features and enhancements. Let me know here or via the issue tracker (https://bitbucket.org/coeing/data-bind/issues) when you find any bugs or got an idea for a cool new feature or small but useful enhancement.
     
  44. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi @coeing,
    I downloaded newer version of Asset and the ContextDataProvider seems to be working, but something else is broken right now.

    I have following hiearchy in the scene
    ContextHolder
    - ColorProvider1 (fetches color from context)
    - Button2 with Image2 (Enabled) with FallbackValueProvider2, ColorProvider2 and GraphicColorSetter2
    - Button3 with Image3 (Disabled) with FallbackValueProvider3, ColorProvider3 and GraphicColorSetter3

    FallbackValueProvider2 is set, that Data is Provider (ColorProvider1) and Fallback is Provider (ColorProvider2)
    FallbackValueProvider3 is set, that Data is Provider (ColorProvider1) and Fallback is Provider (ColorProvider3)

    GraphicColorSetter2 sets the Color of Image2 based on FallbackValueProvider2
    GraphicColorSetter3 sets the Color of Image3 based on FallbackValueProvider3

    When I start the Scene, then FallbackValueProvider2 contains value of ColorProvider1 and FallbackValueProvider3 contains value ColorProvider3. Color of Button2 is set correctly.

    BUT When i Enable Button3 gameobject, then FallbackValueProvider3 is still set to ColorProvider3 (i expected ColorProvider1) AND the actual Image3 color is 0x00000000 (that is not ColorProvider3)!

    Everything starts to work when i change the content of ContextHolder (the one, which causes ColorProvider1 to change AND i have to have Button3 enabled in that time).

    In the previous version, when i enabled the Button3, than FallbackValueProvider3 was set to ColorProvider1, so the image3 had a color that have been provided by ColorProvider1

    Is this behaviour a bug or a feature?

    Thanks,
    Jan
     
  45. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi Jan,

    Do you have an example project that you can send me? I'm currently on vacation and back in one week. I'll check it then.

    Cheers
    Christian
     
  46. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi @coeing, i have send you the email. For the rest of the forum users, i recorded a video that demonstrates the problem.
    DisabledGOProblem.gif
     
    coeing likes this.
  47. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hello, thanks for the update.

    Would it be possible to have more Exemples ?
    This asset has a nice demo if you want inspiration: https://assetstore.unity.com/packages/tools/gui/peppermint-data-binding-90687

    Feature Request:
    Can we have a GameObjectItemsSetter that handle pooling ?

    Right now it destroy go and recreate them, would be cool to just reassign context and have an option to toggle the go if want to

    Thanks
     
  48. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    On an other topic, do you have advice for screen management (hud states, main menu, etc..) that could handle animation, everything databind.
    I guess you've done that in one of your project.

    Thanks a lot
     
  49. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @Ziboo,

    There are a few more examples with every update for the new features. If you are missing one for a specific use case, just let me know and will create an example for it :)

    Thanks for the suggestion, I added an issue for it (https://bitbucket.org/coeing/data-bind/issues/57/add-game-object-pooling-for). I'll see when I can implement it.

    What I used in recent projects was a combination of Data Bind with StrangeIoC. You'll have a DataContextView on the UI window than with a DataContextMediator that connects the UI window to your application logic.

    In general, animation is completely done on the view/presentation side and shouldn't influence the data context. There is the concept of a DataTrigger for a data context that might trigger an animation (e.g. LevelUp as a data trigger).

    For more advanced animations it may be necessary to have an own script that uses a DataBinding (derive it from DataBindingOperator) and triggers/modifies an animation when the value of the DataBinding changes. If you have any concrete case where you struggle, let me know, maybe that would be a good thing to write an example for :)
     
  50. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    I think every providers and setters should have an exemple scene, some of them seems cool but maybe not user friendly at first glance

    Any chance to have that pretty quickly, I now I'm asking a lot, but I need it badly for a project, maybe you can send me a PM of what you charge for a day of work (like SLM Solutions Software) I might be able to negociate this feature with my company.

    Cheers
     
    Last edited: Aug 29, 2017
    Asse1 likes this.