More efficient/clean way of programmatically generating UI Toolkit elements?

I am coding my UI in C#, and as I write more and more my code is getting quite annoying in terms of one particular issue.

I find myself repeating many similar lines of code but having to change the name of the VisualElement I am referring to manually each time which is annoying and wasteful. If I change a VisualElement name, I then have to do a find/replace to get all the points I described it and be sure I don’t pick up anything else by mistake.

For example, if I’m putting together a few VisualElements, it might look like:

      VisualElement visualElement1 = new VisualElement();
        visualElement1.name = "Element1";
        visualElement1.style.width = new Length(100, LengthUnit.Percent);
        visualElement1.style.height = 500;
        visualElement1.style.flexDirection = FlexDirection.Row;
        visualElement1.pickingMode = PickingMode.Ignore;
        visualElement1.style.position = Position.Absolute;
        visualElement1.style.bottom = 200;

        VisualElement visualElement2 = new VisualElement();
        visualElement2.name = "Element2";
        visualElement2.style.width = visualElement2.style.height = 500;
        visualElement2.style.marginLeft = 150;
        visualElement2.style.backgroundColor = Color.white;
        visualElement2.style.opacity = 0.6f;
        visualElement2.style.borderBottomLeftRadius = visualElement2.style.borderBottomRightRadius = visualElement2.style.borderTopLeftRadius = visualElement2.style.borderTopRightRadius = 200;

       visualElement2.Add(visualElement1);

So much of the code becomes repetitive in style that it would be much faster to copy and paste some settings lines from one element to the next rather than having to change the names or type them from scratch.

For example, what I would like to do is this:

       VisualElement *vePtr;

        VisualElement visualElement1 = new VisualElement();
       *vePtr = &visualElement1;
        *vePtr.name = "Element1";
        *vePtr.style.width = new Length(100, LengthUnit.Percent);
        *vePtr.style.height = 500;
        *vePtr.style.flexDirection = FlexDirection.Row;
        *vePtr.pickingMode = PickingMode.Ignore;
        *vePtr.style.position = Position.Absolute;
        *vePtr.style.bottom = 200;

        VisualElement visualElement2 = new VisualElement();
        *vePtr = &visualElement2;
        *vePtr.name = "Element2";
        *vePtr.style.width = *vePtr.style.height = 500;
        *vePtr.style.marginLeft = 150;
        *vePtr.style.backgroundColor = Color.white;
        *vePtr.style.opacity = 0.6f;
        *vePtr.style.borderBottomLeftRadius = *vePtr.style.borderBottomRightRadius = *vePtr.style.borderTopLeftRadius = *vePtr.style.borderTopRightRadius = 200;

       visualElement2.Add(visualElement1);

If I could use a pointer like this and just reassign the pointer after creating each new element, then I can copy and paste my settings lines from one element to another very quickly and I don’t have to re-write the whole line or change the name of the object it’s referring to over over. If I change an element’s name, I only have to do it once or twice as well.

For example, if I want the long border radius statement on Element2 to also apply to Element1, I can easily just copy and paste the line to where Element1 was created and change 200 to whatever I need, without having to rewrite or edit the whole line.

The problem is this doesn’t actually work. I am told “Pointers and fixed size buffers can only be used in an unsafe context” and “Cannot take the address of, get the size of, or declare a pointer to a managed type.”

I am not very good with C# to know what is possible or how. Is there some way to accomplish what I am trying to do above and make my life a bit easier or more flexible?

Thanks for any ideas.

Pointer thing won’t work, but you can do something very similar. Create a function (can be local) that returns a visual element. You can then create elements from the function, then modify them.

void MyFunction()
{

    VisualElement Element()
    {
        return New.Element.Size(100).BackgroundColor(Color.white);
    }

    var element1 = Element().Width(200);
    var element2 = Element().BorderRadius(20).BackgroundColor(Color.red);

}

C# element modification is a pain in UI Toolkit, so I redid it for Interface. Here is an image to get an idea of how it looks:


The alternative is to either do it the way you are currently doing it, which takes up a lot of space and is a real pain, or you can make your own snippet library, which (speaking from experience) can take some time.

Why genereate them by code in the fist place?

if (tree == null)
{
   tree = Resources.Load<VisualTreeAsset>("element");
}

var element = tree.Instantiate();

This way you can create your element with the ui builder and load them in runtime.

2 Likes

The UI Builder is quite unstable in my experience with it and its functions are too rudimentary unfortunately for what I need. I started with that and switched to this. My UI is quite complex and needs things like user profiles and custom types of elements derived from the standard ones for which programmatic is the only approach that works. It works well. It’s just annoying.

You could use object initializers to clean up the code a bit:

var visualElement1 = new VisualElement
{
    name = "Element1",
    style =
    {
        width = new Length(100, LengthUnit.Percent),
        height = 500,
        flexDirection = FlexDirection.Row,
        position = Position.Absolute,
        bottom = 200
    },
    pickingMode = PickingMode.Ignore
};

Then it should be easier to copy/paste settings between elements.

1 Like

Why not putting all the styles into uss and just give the elements a name and a uss class?

Initially, we used the UI Builder for styling all the elements, but eventually stopped doing that.

Our approach is:

  • Create a uxml file within a text editor
  • Style the uxml file with uss written in a text editor (Rider is really good in autocompletion with both, uxml and uss files)
  • Use UI Builder to check if all the styling is sound
  • Use the View-Model-ViewModel pattern to find elements by name and bind it to the view model.
  • If the uxml needs a template (e.g. a list containing complex items), we use UnityEngine.UIElements.VisualTreeAsset.Instantiate to create the items (as @manuelgoellnitz suggested already)
  • In addition, UniRx helps tremendously with all the binding (and cleaning up when the UI is destroyed) (some binding extensions can be found here: UI Toolkit Reactive Binding Extensions using UniRx · GitHub)
1 Like

@magnetic_scho Sounds super interesting how you solved that. Can you elaborate a bit more into your composition of MVVM? What Unity datatypes are you typically using for View, ViewModel and Model? How does a typical use case work?

@DominiqueSandoz :
View inherits from a base class (Minimalistic MVVM for Unity, UI Toolkit and UniRX · GitHub), ViewModel and Model are simple C# classes, nothing Unity related in them.

Regarding the Model:
This is a simple C# class exposing its data as a property of type T (if static) or as a property of type IObservable (if dynamic).

Regarding the View:
In the Initialize method, I query for all the elements that I want to bind values to and assign them to fields. E.g. if you want to display the time in the game, the labels for the hour and minute.
In the Bind method, I bind the properties from the ViewModel to the UI Toolkit elements using UniRx extensions. (UI Toolkit Reactive Binding Extensions using UniRx · GitHub).

Regarding the ViewModel:
This is just a wrapper around your Model. E.g. you have a Time object, exposing observables for the hour and the minutes. The ViewModel model takes the IObservable for hour and minute and converts them into IObservable by applying some localization (e.g. 1 => “Hour 1”, 15 => “Minute 15”).

1 Like

awesome, thank you! Very insightful