Search Unity

Fire Typeless ChangeEvent

Discussion in 'UI Toolkit' started by BinaryCats, Nov 29, 2019.

  1. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Hello,

    I have pieces of data that are tightly coupled, for example a string and an enum. With help from this post - I have created a custom VisualElement, which displays this data, and this works great!

    My
    CustomVisualElement 
    implements from
    INotifyValueChanged<string>
    and
    INotifyValueChanged<myEnum>


    This is Fine and works if I want to register call backs for
    ChangeEvent<string>
    or
    ChangeEvent<myEnum>
    . However I have cases where I want to just check if anything changed, and I don't care about the specific data.

    I could wrap my data in a struct

    Code (CSharp):
    1. struct MyStruct{
    2. string text;
    3. myEnum enum;
    4. }
    and implement
    INotifyValueChanged<MyStruct>
    , however anytime I need to fire the ChangeEvent

    Code (CSharp):
    1. newvalue = new MySruct(){text=TextValue, enum=EnumValue}
    2. using (var newEvent = ChangeEvent<MyStruct>.GetPooled(currentvalue, newValue))
    3.                 {
    4.                     newEvent.target = this;
    5.                     SendEvent(newEvent);
    6.                 }
    7. currentvalue = newValue
    new'ing the struct is a bit meh, especialy when I don't care about the current/new value.

    Is there a better way to Send a change event without the attached gubbins (previous value, new value)

    Thanks
     
    Xarbrough likes this.
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    As we don't have a non-generic base class for ChangeEvent<T>, what you have there with your MyStruct is what I'd do as well. But I am curious why you would never care about the new value. Ideally, your CustomVisualElement would not be the one performing the logic on value change - instead, the user/owner of CustomVisualElement would use your ChangeEvent<MyStruct> to perform the necessary actions.

    My only other suggestion is to choose a value type, like int or string, and have CustomVisualElement send out a ChangeEvent<T> with T as this simple type. It could be a hash or some other representation of your string/enum pair, only intended for outside users to know when something has changed.
     
  3. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Hi, Thank you for your reply.

    My use case is as followed:
    When the user edits a field (either the enum or text) in the
    CustomVisualElement
    this enables the "Save" button in the window.
    When the user clicks the "Save" button, the string/enum values inside the
    CustomVisualElement
    s get serialised into an object.
    (I do not wish to bind directly, more of a delayed bind requiring input from the user)

    Maybe a better pattern would be to store the string/enum values in the owner window; however there are multiple
    CustomVisualElement
    s in the tree. How would I know which
    CustomVisualElement
    text/enum was changed. As far as I am aware, there is no Owner parameter passed into the callback, so you don't know which VE it came from?

    Currently, when saving I iterate through all the (cached)
    CustomVisualElement
    s and serialise their field values into an array.

    Thanks
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    All events have these properties:
    target
    - element on which the event occured (so your
    CustomVisualElement
    if you're still sending the
    ChangeEvent<MyStruct>
    )
    currentTarget
    - element currently handling the event (the element you called
    RegisterCallback<ChangeEvent<MyStruct>>()
    on)

    Since you need to retroactively find the current values of your fields when the user presses Save, you can just register for ChangeEvent<string> and ChangeEvent<Enum> on your container/root element and enable the Save button whenever you get any event.

    I would just link all your fields to a central array via reference and index instead of relying on the values within the UI elements themselves. When you press Save, you can just serialize this array.
     
  5. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Hi,

    Sorry for the late reply I was just letting the new implementation settle.

    This is how it was, and what lead me to write this forum post. As it meant I would have repeat code, the code once in the callback for string, and then again in the Enum. I could foresee this leading to complications in the future and wanted to know if there was a more generic ChangeEvent which I could use for all :)

    I have done this now, and for my purpose it seems fine :)

    I had to change the struct to a class ass otherwise it meant I would have to new up a new object anytime something was changed

    Code (CSharp):
    1.         private void TextChanged(ChangeEvent<string> evt)
    2.         {
    3.             OldData.Text = evt.previousValue;
    4.             Data.Text = evt.newValue;
    5.             //as a struct:
    6.             //Data = new Data() { Text = evt.newValue, myEnum = Data.myEnum };
    7.             using (var newEvent = ChangeEvent<Data>.GetPooled(OldData, Data))
    8.             {
    9.                 newEvent.target = this;
    10.                 SendEvent(newEvent);
    11.             }
    12.         }
    But I guess that's horses for courses.
     
    uDamian likes this.