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
    I'll try to add a few examples with every new version, also for existing scripts.

    What I do right now is using the sales of the previous months to decide how much to develop on Data Bind in the current month, so there is at least a small rate per hour. The budget for August is already done and the sales in August were pretty low, so right now there is no time for a new feature.

    A sponsored development is always possible and much appreciated :) I only take half of my daily rate (500€) for such jobs as they are good for the asset as well. For the pooling in the GameObjectItemsSetter I would estimate half a day of work, which would mean 125€ plus VAT. If that's not feasible, I would suggest having a look at the Enhanced Scroller asset (https://assetstore.unity.com/packages/tools/gui/enhancedscroller-36378). I used it in two projects and it does a pretty good job with pooling of items and can also be used together with Data Bind.
     
  2. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Awesome, I own this asset. Didn't tested it, I though I was going to be a pain to integrate with Data Bind.
    Is there any gotcha ? or is it straight forward to integrate with Data Bind ?
    Thanks
     
  3. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    I ended up with this:

    Code (CSharp):
    1. public abstract class DataBindedScroller<T> : SingleSetter<Collection<T>>, IEnhancedScrollerDelegate where T : Context
    2.     {
    3.         public EnhancedScroller Scroller;
    4.         public EnhancedScrollerCellView CellViewPrefab;
    5.         public float CellSize = 40;
    6.  
    7.         private Collection<T> collection;
    8.         private bool needRefresh;
    9.  
    10.         //----------------------------------------------------------------------------
    11.         //----- Scroller Override
    12.         //----------------------------------------------------------------------------
    13.         public float GetCellViewSize(EnhancedScroller scroller, int dataIndex)
    14.         {
    15.             return CellSize;
    16.         }
    17.  
    18.         public EnhancedScrollerCellView GetCellView(EnhancedScroller scroller, int dataIndex, int cellIndex)
    19.         {
    20.             var cellView = scroller.GetCellView(CellViewPrefab) as DataBindedCellView;
    21.  
    22.             cellView.gameObject.SetActive(true);
    23.             cellView.ContextHolder.Context = collection[dataIndex];//.SetContext(collection[dataIndex], Data.Path + Context.PathSeparator + dataIndex);
    24.             return cellView;
    25.         }
    26.  
    27.  
    28.         public int GetNumberOfCells(EnhancedScroller scroller)
    29.         {
    30.             if (collection == null)
    31.             {
    32.                 return 0;
    33.             }
    34.             return collection.Count;
    35.         }
    36.  
    37.         //----------------------------------------------------------------------------
    38.         //----- DataBind Overrides
    39.         //----------------------------------------------------------------------------
    40.         public override void Init()
    41.         {
    42.             base.Init();
    43.             CellViewPrefab = Instantiate(CellViewPrefab.gameObject, Scroller.transform).GetComponent<EnhancedScrollerCellView>();
    44.             CellViewPrefab.gameObject.SetActive(false);
    45.             Scroller.Delegate = this;
    46.             Scroller.ReloadData();
    47.         }
    48.  
    49.         protected override void OnValueChanged(Collection<T> newValue)
    50.         {
    51.             if (collection != null)
    52.             {
    53.                 collection.ItemAdded -= CollectionOnItemAdded;
    54.                 collection.ItemInserted -= CollectionOnItemInserted;
    55.                 collection.ItemRemoved -= CollectionOnItemRemoved;
    56.                 collection.ClearedItems -= CollectionOnClearedItems;
    57.             }
    58.  
    59.             collection = newValue;
    60.  
    61.             collection.ItemAdded += CollectionOnItemAdded;
    62.             collection.ItemInserted += CollectionOnItemInserted;
    63.             collection.ItemRemoved += CollectionOnItemRemoved;
    64.             collection.ClearedItems += CollectionOnClearedItems;
    65.  
    66.             Refresh();
    67.         }
    68.  
    69.  
    70.         //----------------------------------------------------------------------------
    71.         //----- Private
    72.         //----------------------------------------------------------------------------
    73.         private void Refresh()
    74.         {
    75.             needRefresh = true;
    76.         }
    77.  
    78.         //----------------------------------------------------------------------------
    79.         //----- Unity
    80.         //----------------------------------------------------------------------------
    81.         private void LateUpdate()
    82.         {
    83.             if (needRefresh)
    84.             {
    85.                 Scroller.ReloadData();
    86.                 needRefresh = false;
    87.             }
    88.         }
    89.  
    90.  
    91.         //----------------------------------------------------------------------------
    92.         //----- Collection Callbacks
    93.         //----------------------------------------------------------------------------
    94.         private void CollectionOnItemAdded(object item)
    95.         {
    96.             Refresh();
    97.         }
    98.  
    99.         private void CollectionOnClearedItems(IEnumerable<object> items)
    100.         {
    101.             Refresh();
    102.         }
    103.  
    104.         private void CollectionOnItemRemoved(object item)
    105.         {
    106.             Refresh();
    107.         }
    108.  
    109.         private void CollectionOnItemInserted(object item, int index)
    110.         {
    111.             Refresh();
    112.         }
    113.     }

    Code (CSharp):
    1. public class CellContextScroller : DataBindedScroller<CellContext>
    2.     {
    3.        
    4.     }
    Code (CSharp):
    1. public class DataBindedCellView : EnhancedScrollerCellView
    2.     {
    3.         public ContextHolder ContextHolder;
    4.     }
    Do you see any bad practices ?

    The only think I would like to avoid is to have to create derivated class from DataBindedScroller.
    But that's the only way I had it to work, the collection needed to be Typed like the Context
    If you have a better way ?

    Thanks
     
  4. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Looks very good! :)

    You might just use a non-generic Collection instead of the generic variant, than you won't need to derive from DataBindedScroller but could add it as a script to a game object directly.
     
  5. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    I tried, I can't...
    First because I cannot acces an element of a non-generic Collection like: collection[index]
    And it seems that I cannot store it in a non-generic collection when OnValueChanged send me a generic collection

    EDIT:
    Got it working but not sure I like that, the only way is to cast the non-generic collection like:
    Code (CSharp):
    1. collection.Cast<Context>().ElementAt(index)
     
    Last edited: Aug 30, 2017
  6. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    That's true, the base class Collection doesn't have an Indexer. I'll add a task to check if it's possible to add one.

    OnValueChanged will give you a Collection if you derive from SingleSetter<Collection>.
     
  7. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Is there any DataProvider, which can get a number of elements inside collection (Count from collection)?
     
  8. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @Jochanan,
    No, because you don't need one. You can just reference it via its path (e.g. MyCollection.Count). If that doesn't work for your specific use case, check out the ContextDataProvider which provides a data value from a context via its path.

    Hope that helps! If not, just post your specific use case, so I can check it out.
     
    Jochanan likes this.
  9. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Hello again,

    Is it possible for an item setter to choose a prefab based on the type of the context ? (I guess through a provider)

    Let say I have:

    Code (CSharp):
    1. public class UIControl : Context
    2. {
    3. }
    4.  
    5. public class CheckBox : UIControl
    6. {
    7. }
    8.  
    9. public class Button : UIControl
    10. {
    11. }
    12.  
    13. public Collection<UIControl> Controls;
    I'd like to have just one collection filled with CheckBoxes and Buttons, and the item setters will choose the right prefab based on the type.

    Thanks
     
  10. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    The GameObjectItemsSetter doesn't have a DataBinding for the Prefab, so the used prefab is always the same.

    But you could derive your own class from the ItemsSetter base class and replace the GameObject Prefab field with a DataBinding Prefab field. The data binding could then use a data provider that returns a prefab depending on a context (e.g. ContextPrefabMapping or ContextPrefabProvider).

    Another possibility would be to create a GameObjectItemsSetterPrefabSetter which sets the Prefab field of a GameObjectItemsSetter. But the GameObjectItemsSetter isn't really prepared to handle changes of the Prefab field, so this might give you some side effects.
     
  11. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi, i am having issues with loading images from files. I am using StringFormatter, SpriteLoader and ImageSprite setter to set the image based on context. Images are stored inside "Resources" folder.
    When the image is loaded directly from within resource folder, everything is fine:
    DB_No_warning.PNG
    But when the image is loaded from subfolder in resource folder, i am getting warning "No sprite resource found at path 'SubFolder/'", even thought the image id is set in contexts constructor, so there is no chance, that the context contains null value.
    DB_warning.PNG
    I need to categorize the images in my project, because there are different kinds of images. But i am having like tens of warnings as soon as i run the scene. Do you have any idea how to ged rid of the warnings? Everything else works fine
     
  12. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @Jochanan,
    It looks like the warning is thrown when the SpriteLoader tries to load the sprite from the path, but the context is not set in the context holder yet. Would it be possible to send me the test project you have there and add a bug report in the issue tracker at https://bitbucket.org/coeing/data-bind/issues ? I will check it out as soon as I have time and it should be fixed in the next version (probably released in October)
     
  13. Asse1

    Asse1

    Joined:
    Jan 9, 2013
    Posts:
    89
    Greetings,

    is it possible to directly access a Context item of a Collection at a specific index? E.g. if I want to only display the first entry. Currently I'm using a SlotItemsSetter which instantiates a prefab but that's to inconvenient in my opinion.
     
  14. coeing

    coeing

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

    Yes, it's possible, you can just enter a custom path, e.g. ExampleCollection.0 to access the first item.
     
  15. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hello @coeing, do you have any idea about when you finish 1.0.11?
     
  16. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    I planned to release it already, but a project takes all my time currently. I will try to release it at the end of the next week :)
     
    Jochanan likes this.
  17. Asse1

    Asse1

    Joined:
    Jan 9, 2013
    Posts:
    89
    We've updated to 1.0.10 and since then the ContextHolder inspector doesn't show the items of a Collection anymore.

    I captured a screenshot of the ContextHolder in the CollectionExampleUnity.scene.
     

    Attached Files:

  18. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
  19. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Finally! Phew, it was a tough month, I was very busy working on some projects, but now I managed to submit the new version 1.0.11 of Data Bind which 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 to version 1.0.11:
    • Fixed a lot of issues that were reported by fellow developers who use my asset. Thanks a lot, it really helps to improve the asset!
    • Adjusted initialization order of Data Bindings, so they are not updated that often during first setup. Hopefully this doesn't create problems for running projects, but if you encounter something, let me know! The initialization process is still not perfect and I plan to get it completely right within the next updates.

    Here is the full changelog:
    * Call first Enable of a data binding operator in the Start method instead of OnEnable to make sure that all other scripts had time to initialize in their Awake method (Unity may call OnEnable of an object before the Awake method of other objects was called, see https://forum.unity.com/threads/onenable-before-awake.361429/)

    * Add small SpriteLoading example that also shows the issue #59 (invalid warning from SpriteLoader)

    * Issue #58 Add indexer to Collection class

    * Issue #63 Add warning if more than one settings objects are found and none is called like the default one

    * Issue #63 Use any DataBindSettings object found in resources when not found at default path

    * Issue #63 Made DataBindSettings editable again

    * Issue #62 Show items of any IEnumerable in ContextHolderEditor and add buttons to remove/add for ICollection members (which includes Collection class)

    * Issue #34 Add error when target binding doesn't receive an object of the expected type

    * Use GameObjectItemsSetter instead of LayoutGroupItemsSetter and flag LayoutGroupItemsSetter as obsolete

    * Use virtual method NotifyContextOperatorsAboutContextChange instead of IContextHolder interface

    * Only update context data in ContextDataUpdater if active and enabled

    * Only use Coroutine for delayed value initialization in DataContextNodeConnector if mono behaviour is enabled

    * Issue #60 Only register listener when a valueChangedCallback is set, otherwise an initial value 'null' is returned which changes the context value incorrectly

    * Issue #60 Add example that shows a bug when switching the context of a context holder

    * Issue #49 Delay data update when context changed till end of frame as there may be multiple data context node connectors that have to update their context first

    * Use MonoBehaviour a data binding and data context node connector belong to instead of game object

    * Add test case for issue #49

    * Issue #45 Make GetValue and SetValue of a context work with struct members

    * Issue #56 Make Unity callbacks protected in DataBindingOperator, so there is at least a warning when overriding them instead of the virtual methods (A derived class should use the virtual methods (Init, Deinit, Enable, Disable) instead of the Unity callbacks, so the initialization order remains correct.)

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

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

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
  21. Asse1

    Asse1

    Joined:
    Jan 9, 2013
    Posts:
    89
    Hey @coeing the SmoothItemSetter components currently also wait until the first item gets created, which look like an error under certain circumstances. For example when the list changes on-the-fly and not only when a ui-window opens.

    Would you implement a boolean or something else so that the user has control of this behavior?
     
  22. coeing

    coeing

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

    I added the issue to the issue tracker (https://bitbucket.org/coeing/data-bind/issues/64/smoothitemsetter-only-updates-after-first). If possible could you send a small sample project where the SmoothItemSetter doesn't work as you would expect? That would help a lot to provide the behaviour you want.

    Cheers
    Christian
     
  23. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    Hello @coeing
    I have a question about DataFormatter, Does they notify all objects which use them about changes?
    I have following config. And it works on Start, but if i change the value text mesh pro component does not pickup changes.

    ContextHolder on the root node show actual value
    upload_2017-12-6_11-45-21.png
    but string formatter still use old refference
    upload_2017-12-6_11-45-58.png

    update1: just tried own setter without formatter. and it's strange because I see that data is updated but setter still get old value. probably problem somwhere else
     

    Attached Files:

    Last edited: Dec 6, 2017
  24. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    @coeing
    I've created a repo for you with isolated problem, you can find it here https://github.com/nicloay/databind_bug
    I've used Unity 5.6.3 and DataBind 1.0.11

    So basically you can see that I have Main context (Singleton) wich has collection and property of the same type InternalData. Property links to first internal element of collection.
    And then I have 2 buttons which change scene from one to another and on second scene I change value in subcontext trhough additional property.
    Button on main scene open *MainScene*
    Code (csharp):
    1.  
    2. [RequireComponent(typeof(Button))]
    3. public class OpenSecondScene : MonoBehaviour {
    4.    void Start () {
    5.       GetComponent<Button>().onClick.AddListener(() =>
    6.       {
    7.          MainData.Instance.CurrentProp = MainData.Instance.Collection.ElementAt(0);
    8.          SceneManager.LoadScene("SecondScene");
    9.       });
    10.    }
    11. }
    12.  
    And this is Button on second scene
    Code (csharp):
    1.  
    2. [RequireComponent(typeof(Button))]
    3. public class OpenMainScene : MonoBehaviour {
    4.    void Start ()
    5.    {
    6.       MainData.Instance.CurrentProp.IntProp = 2;
    7.       GetComponent<Button>().onClick.AddListener(() => SceneManager.LoadScene("MainScene"));
    8.    }
    9. }
    10.  
    And when you start you can see that by default Collection.ElementAt(0) has value 1 (from constructor)
    and when you change scene to second and back ( you see that I assign IntProp to 2)
    the value in context 2 But text setter set old value to old (1) somehow
    upload_2017-12-6_16-15-4.png
    Hope this helps.
    Let me know if you have any further questions.

    And anyway thanks for your great plugin, it already saved tons of time for me.
     
  25. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    Sorry @coeing.
    Problem was that I didn't setup databind config properly. I used underscore for private variables, but didn't checked this option at DataBind config.
     
  26. coeing

    coeing

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

    I just wanted to start to look into your problem, but as I saw you just solved it on your own :) Thanks anyway that you set up a project, this would have made the search very easy for me.

    I'm sorry to hear that it took some time before you found out that the naming settings for the data property fields were the reason for the bug. It's something that a few other users also stumbled upon if they don't use the initial naming convention. If you have an idea how a user could be informed about this, let me know. Some kind of warning would be nice, but then the question is when this warning should be triggered.
     
  27. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    @coeing
    Thanks for reply.
    Actually my issue also was that my private property didn't match public (I refactored public, and forgot to made a change for private).

    I think it could be good solution if you will define new directive like DATABIND_DEBUG and then in your code, where you use reflection to find which property must be changed just add something like
    Code (csharp):
    1.  
    2. #if DATABIND_DEBUG
    3. if (propertyNotFound){
    4.     Debug.LogWarning("Cant find private property {0} for public {1}
    5. }
    6. #endif
    7.  
    So if some issue appeared it could be possible to enable this option in build mode like
    upload_2017-12-6_20-5-19.png

    And when issue solved just remove this directive so final build won't have impact on performance.
     
  28. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    That sounds like a good idea. What I want to avoid is that the check has an influence on the performance.

    But there is also another thing to consider: One might bind a field/property from an immutable object that doesn't have a data property backing field. In that case you would get a false positive warning which is also not that great. I might need a better solution, but to start with the preprocessor directive could be something like DATABIND_FIND_MISSING_BACKING_FIELDS which will report all used bindings where no backing field was found. The developer can then decide if it is a bug (in most cases) or if it is by design.
     
    nicloay likes this.
  29. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    @coeing Looks like ConstantObject as data providers is not working anymore. I have sent you an email with test scene and i gonna report issue as well
     
  30. coeing

    coeing

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

    Thanks a lot for the report and the sample project. This issue has high prio for me, because it's a feature that worked already. Nonetheless I will probably only have time to look into it on Thursday. Hopefully it doesn't block you too much.

    Cheers
    Christian
     
    Jochanan likes this.
  31. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    @coeing Could you please take a look to another bug.
    I also submited it to my github https://github.com/nicloay/databind_bug

    The problem is following
    I have Enum at my context and it work properly only on first creation.
    Let me show you the code
    Here is how i define and use enum (you can see that I also tried to use int instead of enum, i remember you had something like this in previous version maybe even in this one).
    Code (csharp):
    1.  
    2. using Slash.Unity.DataBind.Core.Data;
    3. using UnityEngine;
    4. public enum TestEnum : int
    5. {
    6.    Option1,
    7.    Option2,
    8.    Option3
    9. }
    10.  
    11. public class TestData : Context
    12. {
    13.    private static TestData _instance;
    14.    public static TestData Instance
    15.    {
    16.       get
    17.       {
    18.          if (_instance == null)
    19.          {
    20.             _instance = new TestData();
    21.          }
    22.          return _instance;
    23.       }
    24.    }
    25.    private TestData()
    26.    {  
    27.       Debug.Log("Constructor call");
    28.       TestEnum = TestEnum.Option3;
    29.    }
    30.  
    31.    readonly Property<int> _testEnumProperty = new Property<int>();
    32.    public TestEnum TestEnum {
    33.       get { return (TestEnum)this._testEnumProperty.Value; }
    34.       set { this._testEnumProperty.Value = (int)value; }
    35.    }
    36. }
    37.  
    And then I have Text appender (similar as setter but don't clear the text) this is only for debug purpose only
    Code (csharp):
    1.  
    2. using Slash.Unity.DataBind.Foundation.Setters;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class TextAppender : ComponentSingleSetter<Text, TestEnum>
    7. {
    8.    protected override void UpdateTargetValue(Text target, TestEnum value)
    9.    {
    10.       target.text += ">"+value;
    11.    }
    12. }
    13.  
    And here is what's happening (I have singleton context and initialize it only once, and then assign the same context to the context holder).
    And on Initialization everything works as expected (see TestData constructor where I assign TestEnum.Option3)
    And text show that everything is ok

    upload_2017-12-15_16-28-38.png

    But if I open second scene and come back I have this

    upload_2017-12-15_16-29-6.png

    DataBind assing default enum value somewhere to the context, and setter pickup this change.

    upd1:
    Just tried the same thing for integer, and it has opposite behaviour
    On start it set value through 0 and on scene change it just pickup actual value without any problem

    upload_2017-12-15_16-37-18.png upload_2017-12-15_16-37-29.png


    Thanks in advance.
     

    Attached Files:

    Last edited: Dec 15, 2017
  32. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
  33. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Hi @nicloay
    Thanks a lot for your test setup, it made finding the bug much easier. The problem was the initialization order of the data bindings and setters. As you might know the initialization order of Unity is a bit random (as you can see with the difference between the int and Enum property). Therefore I have to check in several locations if a data value is already initialized to not work with an uninitialized (and therefore default = 0 = null) value.

    This was missing in two places and caused updating the TextAppender with the default value.

    Attached is a patch with the required checks in DataBinding.cs and ComponentSingleSetter.cs

    Hope that fixes the problem, let me know if there are further problems :)
     

    Attached Files:

    nicloay likes this.
  34. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    Do you have any examples how to use CollectionWhereBehaviour.
    I don't understand what is ComparisonValue and ItemPath.

    What i need to do is to filter collection, I found one your post here which lead me to this task https://bitbucket.org/coeing/data-bind/issues/23/implement-an-easy-way-to-add-filters-for but it doesn't contains details as well.

    And according to code, maybe i'm wrong, you return just single value from collection which fit some condition, but what I need to return is another colelction with all elements which fit some condition

    Thanks
     
  35. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    This feature doesn't exist yet and is not planned for a release yet. It was added because I thought such filters would be helpful, especially during prototyping. But it would need a good concept to be useful in a generic way, which would a bit too time-consuming for the moment.
     
  36. nicloay

    nicloay

    Joined:
    Jul 11, 2012
    Posts:
    540
    Thanks for quick reply. Yes, i understand complexity here, probably because it require 2 different context lookups.

    Could you please check the following code, I tried to make DataProvider for similar case (to filter data). and followed SingleSetter.cs design, maybe it's possible to make it more simple. As all of this code bellow is just for 34 line where I check is page hidden or not. Maybe it's possible to make it more simple.

    Code (csharp):
    1. using Slash.Unity.DataBind.Core.Data;
    2. using Slash.Unity.DataBind.Core.Presentation;
    3. using UnityEngine;
    4.  
    5. public class ActivePagesListProvider : DataProvider {
    6.    public DataBinding SourceCollection;
    7.  
    8.    readonly Collection<Page> _collection = new Collection<Page>();
    9.  
    10.  
    11.    public override object Value
    12.    {
    13.       get { return _collection; }
    14.    }
    15.  
    16.  
    17.    public override void Init()
    18.    {
    19.       AddBinding(SourceCollection);
    20.       UpdateValue();
    21.    }
    22.  
    23.    protected override void UpdateValue()
    24.    {
    25.       base.UpdateValue();
    26.       _collection.Clear();
    27.       var src = SourceCollection.GetValue<Collection<Page>>();
    28.       if (src == null)
    29.       {  
    30.          return;    
    31.       }
    32.       foreach (Page page in src)
    33.       {
    34.          if (!page.IsHidden)
    35.          {
    36.             _collection.Add(page);
    37.          }
    38.       }
    39.       OnValueChanged();
    40.    }
    41.  
    42.    public override void Deinit()
    43.    {
    44.       RemoveBinding(SourceCollection);
    45.    }
    46.  
    47.    public override void Disable()
    48.    {
    49.       SourceCollection.ValueChanged -= SourceCollectionOnValueChanged;
    50.    }
    51.  
    52.    public override void Enable()
    53.    {
    54.       SourceCollection.ValueChanged += SourceCollectionOnValueChanged;
    55.       if (SourceCollection.IsInitialized)
    56.       {
    57.          SourceCollectionOnValueChanged(SourceCollection.Value);
    58.       }
    59.    }
    60.  
    61.    private void SourceCollectionOnValueChanged(object newValue)
    62.    {
    63.       UpdateValue();
    64.    }
    65. }
    66.  

    If it's ok, maybe create generic class for this kind of filters, where you specify collection type and have one method to override which will indicate include or not collection element in to result collection.

    Thanks.


    upd1:
    I see one problem here, if internal element change property my dataprovider doesn't see it as it monitor only count change I suppose.
     
    Last edited: Dec 29, 2017
  37. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    @nicloay Thanks for the example, I had a look at it. What you can do here is to use a utility class called CollectionDataBinding which will observe the bound collection for changes. Whenever something changes you can call your UpdateValue method and filter the collection:

    Code (CSharp):
    1. using Slash.Unity.DataBind.Core.Data;
    2. using Slash.Unity.DataBind.Core.Presentation;
    3.  
    4. public class ActivePagesListProvider : DataProvider
    5. {
    6.     private readonly Collection<Page> collection = new Collection<Page>();
    7.  
    8.     public DataBinding SourceCollection;
    9.  
    10.     private CollectionDataBinding<Page> sourceCollection;
    11.  
    12.     public override object Value
    13.     {
    14.         get
    15.         {
    16.             return this.collection;
    17.         }
    18.     }
    19.  
    20.     public override void Deinit()
    21.     {
    22.         if (this.sourceCollection != null)
    23.         {
    24.             this.sourceCollection.CollectionChanged = null;
    25.             this.sourceCollection.ItemAdded = null;
    26.             this.sourceCollection.ItemRemoved = null;
    27.             this.sourceCollection.ItemInserted = null;
    28.             this.sourceCollection = null;
    29.         }
    30.  
    31.         this.RemoveBinding(this.SourceCollection);
    32.     }
    33.  
    34.     public override void Disable()
    35.     {
    36.         base.Disable();
    37.         this.sourceCollection.Disable();
    38.     }
    39.  
    40.     public override void Enable()
    41.     {
    42.         base.Enable();
    43.         this.sourceCollection.Enable();
    44.     }
    45.  
    46.     public override void Init()
    47.     {
    48.         this.AddBinding(this.SourceCollection);
    49.         this.sourceCollection =
    50.             new CollectionDataBinding<Page>(this.SourceCollection)
    51.             {
    52.                 CollectionChanged = this.OnCollectionChanged,
    53.                 ItemAdded = this.OnItemAdded,
    54.                 ItemRemoved = this.OnItemRemoved,
    55.                 ItemInserted = this.OnItemInserted
    56.             };
    57.     }
    58.  
    59.     protected override void UpdateValue()
    60.     {
    61.         base.UpdateValue();
    62.         this.collection.Clear();
    63.         var src = this.SourceCollection.GetValue<Collection<Page>>();
    64.         if (src == null)
    65.         {
    66.             return;
    67.         }
    68.  
    69.         foreach (var page in src)
    70.         {
    71.             if (!page.IsHidden)
    72.             {
    73.                 this.collection.Add(page);
    74.             }
    75.         }
    76.  
    77.         this.OnValueChanged();
    78.     }
    79.  
    80.     private void OnCollectionChanged(Collection obj)
    81.     {
    82.         this.UpdateValue();
    83.     }
    84.  
    85.     private void OnItemAdded(Page obj)
    86.     {
    87.         this.UpdateValue();
    88.     }
    89.  
    90.     private void OnItemInserted(int arg1, Page arg2)
    91.     {
    92.         this.UpdateValue();
    93.     }
    94.  
    95.     private void OnItemRemoved(Page obj)
    96.     {
    97.         this.UpdateValue();
    98.     }
    99. }
     
    nicloay likes this.
  38. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    The new version 1.0.12 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

    Main additions:
    - Synchronizers which do a two-way-binding between a UI control and a data property in a context
    - Find missing data property fields easier: Warnings for missing backing fields if preprocessor define DATABIND_REPORT_MISSING_BACKING_FIELDS is set
    - Several fixes and small improvements from the issue tracker

    Full changelog:
    * Issue #67 Change #ifdef code to if switch, so Unity can do their auto script updates correctly (which doesn't consider #ifdefs)

    * Issue #67 Add switch for VR namespaces as the namespace changed in Unity 2017.2 and higher

    * Issue #64 Add fix of SmoothCollectionsChangesFormatter to obsolete scripts as well

    * Issue #64 Reset delay timer in SmoothCollectionChangesFormatter when bound collection is cleared

    * Flag SmoothSlotItemsSetter and SmoothLayoutGroupItemsSetter as obsolete, SmoothCollectionChangesFormatter should be used instead

    * Add default behaviour to DataBindingOperator.Deinit with removing all bindings

    * Show enumerable values of data providers as an imploded string

    * Issue #65 Report warnings for missing backing fields if preprocessor define DATABIND_REPORT_MISSING_BACKING_FIELDS is set

    * Report initial value change for EnumGetter

    * Issue #66 Make sure ConstantObject calls ValueChanged once when enabled

    * Don't update target value if data of a ComponentSingleSetter is not initialized yet

    * Don't initialize DataBinding if context of DataContextNodeConnector is not initialized yet

    * Handling null value for path in DataContextNodeConnector

    * Issue #31 Move add ons into separate folder to exclude them easily

    * Set initial value of data node to default value of its type when parent object is null

    * Add Synchronizers example to show usage

    * Add observers and synchronizers for input field and slider

    * Add error when trying to assign a target with a wrong type from a data binding to a component single getter

    * Make indexer and GetEnumerator hide implementation of non-generic Collection to return the value with the item type instead of object

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

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

    One last thing:

    API Documentation and NGUI suppport

    I have the feeling that the API documentation and the NGUI support are not used that much. Both of them take a bit of time to keep them updated with each update, so I am thinking about dropping them. For NGUI this just means that I won't do explicit testing with each version, but the already existing bindings will remain. The API documentation at http://slashgames.org/tools/data-bind/api will be removed completely or stay there with the current version.

    If you are using one of them, please let me know! If I'm wrong and many people use those features, I will continue them, of course.
     
    nicloay likes this.
  39. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
  40. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    HI,

    I cannot seems to fix one of my issue.
    Let's say I have a slider, and when the user change the value I want to fire a Callback (Action<float>).
    This is fine.
    But I don't want to fire this callback if I set the value from code.

    What's the correct setup to do that ?
    Only fire when user interaction.

    Thanks
     
  41. coeing

    coeing

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

    You probably already checked out the Synchronizers and TwoWayBinding examples?

    The Unity UI Slider already has a OnValueChanged event that you can register for, maybe that would work already? You could easily use this to invoke a DataBind Command (Put the Command script on a game object, drag the game object onto the OnValueChanged event of the Slider and select the "InvokeCommand" method as the callback for the event).

    Does that help or do you need something else? :)

    Cheers
    Christian
     
  42. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    The thing is with the OnValueChanged event on the slider is that if I change my databind value by code, this event will also be fire, which I don't want.
    Only want if the user did it.

    In more details, it's for an option screen.
    So at the begging I load the options save state, set the values, register to an event value changed to fire the logic and save the state.
    But I also have a "Revert To Default" button, where I reset all values, and that's where I don't want the event to fire anymore.
     
  43. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Mmh, I see. The Unity Slider doesn't distinguish if it was changed by code or by user. What you could do is to set a flag somewhere before you reset the value (something like "ResetInProgress"), set the default value and clear the flag again. This way you are able to check the flag at the location where you want to execute something if the value change wasn't triggered by the reset button.

    There might be different ways, but that depends how your systems work. E.g. instead of setting the slider value when "Revert to Default" was clicked you might be able to set the context value directly which would update the slider value afterwards.
     
  44. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    Yep that's what I ended with, but it's really dirty. Don't really like it
     
  45. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    Me neither :/ If you come across a better solution, let me know!
     
  46. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi @coeing
    Is it possible to implement a setter which combines ActiveSetters and InvertBoolOperation in the next version?
    There would be three inputs - boolean, SetActiveWhenTrue and SetActiveWhenFalse. It would behave like the attached image:
    setter.PNG
    The best option would be, if multiple GameObjects might be activated or deactivated when True/False, but event the single True/False gameobject would help me a lot. Thanks
     
  47. coeing

    coeing

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

    Yeah, I also stumbled upon this issue a few times. Adding so many scripts for such a simple thing feels wrong. On the other side I don't want to have behaviours that include a functionality that can be created by plugging together multiple smaller behaviours. This would duplicate some code.

    For now I added an issue to the issue tracker: https://bitbucket.org/coeing/data-bind/issues/72/provide-a-setter-to-activate-deactivate I will probably think about this issue a bit and see if I can come up with a clean solution. Maybe something like an ActiveSwitch wouldn't be too much of duplicate code for a use case that is quite common.

    Thanks for your feedback! :)
     
  48. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hi @coeing,
    i would like to implement something, which would get triggered, when user press specific button (for example escape).
    Then this trigger would set property to true.

    What is the best strategy to implement something like that?

    My master plan is to open exit dialog, when the user press escape. I know, that i could use some monobehaviour script with a reference to the context, which would check key pressed in update and update the context, but there must be other - nicer - way how to do that...
     
    Last edited: Mar 23, 2018
  49. coeing

    coeing

    Joined:
    Mar 17, 2011
    Posts:
    271
    What I use in my projects is a plain Command script on a GameObject. This way you can drag the Command script onto a Unity event (e.g. the Clicked event of a Button script) and call its InvokeCommand method. The Command points to a method on the Context (e.g. public void Close()) and calls that method.

    The game object with the Command script has to be below your context holder, than you will be able to select the method you want to be called from a drop down.

    Here's an example from a current project (with a custom input system, but it works with others that provide a Unity event, too):
    upload_2018-3-23_18-42-20.png

    upload_2018-3-23_18-43-42.png
     
  50. Jochanan

    Jochanan

    Joined:
    Nov 9, 2016
    Posts:
    85
    Hello again @coeing
    I might misslead you with that "button" word. I was talking about keystroke on keyboard.
    I would use ButtonClickedCommand, when i need to trigger the event on button click.
    I would use EventTriggerCommand, but there is no Event trigger for KeyPress (KeyDown, KeyUp).
    I know, that i can create a script derived from gameObject with simple
    Code (CSharp):
    1. public void Update() {
    2.     if (Input.GetKeyDown(KeyCode.Escape)) {
    3.         XXX
    4.     }
    5. }
    but is there a way, how to create a dataProvider or something different, that i could directly link to a context?

    EDIT1:
    I did it this way:
    Code (CSharp):
    1. public class KeyCodeEnum : ConstantObject<UnityEngine.KeyCode>
    2. {
    3. }
    Code (CSharp):
    1. using Slash.Unity.DataBind.Core.Presentation;
    2. using UnityEngine;
    3.  
    4. public class KeyPressedProvider : DataProvider {
    5.  
    6.     public DataBinding Code;
    7.  
    8.     private KeyCode KeyCodeValue = KeyCode.None;
    9.  
    10.     private bool keyPressed;
    11.  
    12.     /// <inheritdoc />
    13.     public override object Value
    14.     {
    15.         get
    16.         {
    17.             return this.keyPressed;
    18.         }
    19.     }
    20.  
    21.     /// <inheritdoc />
    22.     protected override void UpdateValue()
    23.     {
    24.         this.OnValueChanged();
    25.     }
    26.  
    27.     /// <inheritdoc />
    28.     public override void Init()
    29.     {
    30.         this.AddBinding(this.Code);
    31.     }
    32.  
    33.     /// <inheritdoc />
    34.     public override void Deinit()
    35.     {
    36.         this.RemoveBinding(this.Code);
    37.     }
    38.  
    39.     /// <inheritdoc />
    40.     public override void Enable()
    41.     {
    42.         base.Enable();  
    43.     }
    44.  
    45.     private void OnKeyPressed()
    46.     {
    47.         this.keyPressed = true;
    48.         this.OnValueChanged();
    49.     }
    50.  
    51.     private void Update()
    52.     {
    53.         if (this.KeyCodeValue == KeyCode.None)
    54.         {
    55.             this.KeyCodeValue = this.Code.GetValue<KeyCode>();
    56.         }
    57.  
    58.         bool keyPressed = Input.GetKeyDown(this.KeyCodeValue);
    59.         Debug.Log(keyPressed);
    60.         if (keyPressed)
    61.         {
    62.             OnKeyPressed();
    63.         }
    64.     }
    65.  
    66. }
    67.  
    + I am using ContextDataUpdater, which sets bool value, that acitavetes dialog.
    But i would rather used that command, but i do not know, how to trigger it right now.
    Problem with my current solution is, that it can be activated only once.

    EDIT2:
    I think i have it. I will do some polishing and testing and update the post with a new code/layout
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. public class KeyPressedEvent : MonoBehaviour {
    5.  
    6.     public KeyCode KeyCodeValue;
    7.  
    8.     /// <summary>
    9.     ///   Called when notification faded out.
    10.     /// </summary>
    11.     [Tooltip("Called when key with KeyCodeValue was pressed")]
    12.     public UnityEvent KeyPressed;
    13.  
    14.     public void OnKeyPressed()
    15.     {
    16.         this.KeyPressed.Invoke();
    17.     }
    18.  
    19.     private void Update()
    20.     {
    21.         bool keyPressed = Input.GetKeyDown(this.KeyCodeValue);
    22.         if (Input.GetKeyDown(this.KeyCodeValue))
    23.         {
    24.             OnKeyPressed();
    25.         }
    26.     }
    27. }
    KeyPressed.PNG
     
    Last edited: Mar 28, 2018