UXML could be easily replaced with C#

I’ve spent few hours learning about UIElements and trying to convert one editor tool. I came to the conclusion that UXML is not really needed. I don’t like how to access single VisualElement I have to Query (or Q) and write same object name twice in UXML and C#.

I’ll show what I was trying to do and how I converted it to C#. I’ll skip few parts that are same in both examples. (I’ve decided to inline specific styling if it’s not repetitive as writing object name to .USS file for that reason seems like an overkill.)

Old UXML:

<?xml version="1.0" encoding="utf-8"?>
<UXML
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="UnityEngine.UIElements">
    <Box name="header">
        <Image name="playfablogo" style="background-image: url(Images/playfablogo.png);"/>
        <Box style="flex-direction: row; align-items: center;">
            <Button name="gMText" class="gameManagerBtn" text="GAME MANAGER"/>
            <Button name="gMIcon" class="gameManagerBtn" />
        </Box>
    </Box>
    <IMGUIContainer name="progressBar"/>
    <IMGUIContainer name="mainIMGUI" style="flex-grow: 1;"/>
</UXML >

Old C#:

private Box header;
private VisualElement gMText;
private IMGUIContainer mainIMGUI;

void OnEnable()
{
    root = rootVisualElement;
    root.Clear();
    rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(Path.Combine(Strings.PATH_UI, "styles.uss")));
    var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(Path.Combine(Strings.PATH_UI, "mainView.uxml"));
    template.CloneTree(root);
    header = root.Q<Box>("header");
    mainIMGUI = root.Q<IMGUIContainer>("mainIMGUI");
    gMText = root.Q<VisualElement>("gMText");
}

void Update ()
{
    //actions on header, gMText, mainIMGUI
}

Here’s converted to full C# with help of few extension methods:

//this class in seperate file like .uxml
public class MainView
{
    public Box Header;
    public VisualElement GMText;
    public IMGUIContainer MainIMGUI;
    public VisualElement[] Elements;

    public MainView()
    {
        Elements = new VisualElement[] {
            Box.Set(name: "header").AssignTo(out Header).AddRange(
                Image.Set(name: "playfablogo", background_image: Strings.PATH_UI_IMG("playfablogo.png")),
                Box.Set(flexDirection: FlexDirection.Row, alignItems: Align.Center).AddRange(
                    Button.Set(name: "gMText", _class: "gameManagerBtn", text: "GAME MANAGER").AssignTo(out GMText),
                    Button.Set(name: "gMIcon", _class: "gameManagerBtn")
                )
            ),
            IMGUIContainer.Set(name: "progressBar"),
            IMGUIContainer.Set(name: "mainIMGUI", flexGrow: 1).AssignTo(out MainIMGUI)
        };
    }
}

//Editor class file

private MainView mainView;

void OnEnable()
{
    root = rootVisualElement;
    root.Clear();
    rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(Path.Combine(Strings.PATH_UI, "styles.uss")));
    mainView = new MainView();
    root.AddRange(mainView.Elements);
}

void Update ()
{
    //actions on mainView.Header, mainView.GMText, mainView.MainIMGUI
}

Yes uxml looks slightly better for ex. flex-direction: row; vs flexDirection: FlexDirection.Row but that could be improved with better methods/enums. Other than that C# is far more powerful. I could go further and for ex. type: new Box().Set(name: nameof(Header) instead of new Box().Set(name: "header") that way I wouldn’t have any problems with renaming this object in the future. As you can also see I’m using simple AssignTo to bind elements to fields.

public static VisualElement AssignTo(this VisualElement v, out VisualElement reference)
{
    reference = v;
    return v;
}

I actually do the same thing and just create the UI in C#. Seems more intuitive to me, but I can see XML being a bit better if you have a big layout and/or a lot of elements that don’t need to be queried like Labels.

Edit: The most important aspect of this is probably splitting the concerns (hope this is the correct expression), so you do not end up with a lot of UI creation code in C# and can mostly just focus on the logic.

UXML is probably a better target for code-generation, if you want to create a framework for outputting UI. But other than that, yeah, I also prefer writing the editors with C#/uss. I imaging that people who have a background in html/css and the web in general would prefer it the other way round.

Here are the extension methods that I’m using. A lot of null checks but that’s probably faster than parsing uxml anyway.

public static class VisualElementExtensions
    {
        public static T Set<T>(this T v,
            string name = null,
            string _class = null,
            FlexDirection? flexDirection = null,
            Justify? justifyContent = null,
            Align? alignItems = null,
            string background_image = null,
            float? flexGrow = null,
            float? maxHeight = null,
            float? maxWidth = null,
            float? height = null,
            float? width = null,
            Color? color = null,
            ScaleMode? unityBackgroundScaleMode = null,
            DisplayStyle? display = null) where T : VisualElement
        {
            if (name != null)
                v.name = name;
            if (_class != null)
                v.AddToClassList(_class);
            if (flexDirection.HasValue)
                v.style.flexDirection = new StyleEnum<FlexDirection>(flexDirection.Value);
            if (alignItems.HasValue)
                v.style.alignItems = new StyleEnum<Align>(alignItems.Value);
            if (flexGrow.HasValue)
                v.style.flexGrow = new StyleFloat(flexGrow.Value);
            if (background_image != null)
                v.style.backgroundImage = new Background(AssetDatabase.LoadAssetAtPath<Texture2D>(background_image));
            if (maxHeight.HasValue)
                v.style.maxHeight = maxHeight.Value;
            if (maxWidth.HasValue)
                v.style.maxWidth = maxWidth.Value;
            if (height.HasValue)
                v.style.height = height.Value;
            if (width.HasValue)
                v.style.width = width.Value;
            if (justifyContent.HasValue)
                v.style.justifyContent = new StyleEnum<Justify>(justifyContent.Value);
            if (color.HasValue)
                v.style.color = new StyleColor(color.Value);
            if (unityBackgroundScaleMode.HasValue)
                v.style.unityBackgroundScaleMode = new StyleEnum<ScaleMode>(unityBackgroundScaleMode.Value);
            if (display.HasValue)
                v.style.display = display.Value;
            return v;
        }

        public static T Set<T>(this T v, string text = null) where T : TextElement
        {
            if (text != null)
                v.text = text;
            return v;
        }

        public static T AssignTo<T>(this T v, out T reference) where T : VisualElement
        {
            reference = v;
            return v;
        }

        public static VisualElement AddRange(this VisualElement v, params VisualElement[] elements)
        {
            foreach (var el in elements)
                v.Add(el);
            return v;
        }
    }
1 Like

Maybe but probably not for majority of unity programmers that are used to C# and especially for beginners that barely know C# and suddenly also have to learn xml.

Yes but the same can be achieved with C#. Not only that but in the end you end up with simpler solution.

Let’s say I’m making fairly complex page. I’d rather split it to small C# files like header.cs, body.cs, footer.cs and just add them all up in page.cs. It’s up to me if for ex. body is very complex and I will split it to bodyModel.cs, bodyController.cs etc. With uxml you can do the same but it’s… worse?

You can make page.cs and page.uxml, you’ve “splitted the concerns” but you didn’t split the page making it far more difficult to maintain and develop. Let’s say you are working in team it’s easier to say u work on header and u on body than uxml/cs split.

Ok so let’s say you will split everything making header.uxml, body.uxml, footer.uxml and handle logic in page.cs or separate scripts (body.cs). You end up using Query a lot making it more complex than plain C#.

Another concern is that uxml is resolved at runtime. At start I thought that’s good as my code won’t recompile and Ill be able to see changes immediately. Yes but I’ve also realized that there’s no code completion, no pre-compile null checks (if I remove something from uxml and I was using it in C# query everything breaks), no names check. It’s like writing usual C# script but having to declare all the fields in separate .txt file.

Hi Kamkyker,

One thing that you must be aware of is that when you do “v.style =” you’re writing the style “inline”.
Inline style have a “hidden” cost and are not the most optimal way to use UIElements.
I won’t go into the gritty details but if you have many elements that share the same styles, you’ll have some performance gain by using USS since they will share the same data…

1 Like

Good to know but as I said:

Just wanted to show that C# can do same thing as uxml. Whole styling part can be ignored in this thread I’m focusing more about logic/data and c#/uxml

Yes you can do everything from C#, if that is what you prefer you’re totally free to do it.
But I just wanted to let you know that if you have a complex UI with many elements you might have a performance hit if all styles are inline.
This is something we are aware of and want to improve over the next releases.

2 Likes

I ended up just creating everything apart from my main layout in C# too. I would prefer to use UXML, but CloneTree doesn’t do what I want. I just use an MVC pattern and build the UI in code instead of loading the UXML…

I still want to use USS, so the only thing I added was a function that lets me add multiple classes to an element, instead of the regular AddToClassList which only allows one.

_nodeVE = new VisualElement();
_nodeVE.AddToClassList("node-container");

_titleVE = new VisualElement();
_titleVE.AddToClassList("title-container");
_nodeVE.Add(_titleVE);

_nameVE = new Label { text = Model.Name };
_nameVE.AddToClassList("name");
_titleVE.Add(_nameVE);

_resizerVE = new VisualElement();
_resizerVE.AddToClassList("resizer");
_nodeVE.Add(_resizerVE);
<VisualElement class="node-container">

    <VisualElement class="title-container">
        <Label text="Name" class="name" />
    </VisualElement>

    <VisualElement class="resizer" />
      
</VisualElement>

With extensions you can write this:

VisualElement _nodeVE;
VisualElement _titleVE;
VisualElement _resizerVE;

new VisualElement().Set(_class: "node-container").AssignTo(out _nodeVE).AddRange(
    new VisualElement().Set(_class: "title-container").AssignTo(out _titleVE).AddRange(
        new Label().Set(_class: "name").Set(text: "Name")
    ),
    new VisualElement().Set(_class: "resizer").AssignTo(out _resizerVE)
);

But there’s no way of writing it easily out of box. Hmm maybe it would be enough if VisualElement constructor could parse text that’s usually inside uxml <>.
For ex:

<Label text="Name" class="name" />

would become:

new Label("text='Name' class='name'")

I guess this may not be possible as normally it happens during uxml import (right?).

Trying to figure out some kind of solution that wouldn’t require writing extension methods (that may stop working in the future) and wouldn’t be tough to implement from Unity’s perspective.

Yes, UXML is parsed at import.

Any chance we could get to invoke the uxml importer ourselves? Something like:

VisualTreeAsset tree = UIElementUtil.Parse(someString);

That would make building things on top of frameworks be a lot easier, and I don’t imagine it would be too hard to implement?

Another advantage of using UXML over C# for creating UI is the opportunity to use the upcoming UI Builder, which authors UXML/USS assets only.

2 Likes

Ok, I’m starting to understand how uxml makes sense. It’s similar to what .yaml files (prefabs/scenes) do for usual Unity UI.

I still didn’t like the Q() so I’ve made small tool to generate C# classes based on uxml (or rather VisualAssetTree). Ill post it soon.

Ex from the first post:
TestPage.uxml:

<?xml version="1.0" encoding="utf-8"?>
<UXML
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="UnityEngine.UIElements">
    <Box name="header" style="--csName: gMIcon">
        <Image name="playfablogo" style="background-image: url(Images/playfablogo.png);"/>
        <Box style="flex-direction: row; align-items: center;">
            <Button name="gMText" class="gameManagerBtn" text="GAME MANAGER" style="--csName: gMIcon" />
            <Button name="gMIcon" class="gameManagerBtn" />
        </Box>
    </Box>
    <IMGUIContainer name="progressBar"/>
    <IMGUIContainer name="mainIMGUI" style="flex-grow: 1; --csName: mainIMGUI" />
</UXML >

After creating uxml I right click it and select Create C# class. TestPageConverted.cs is generated.

C# (again from 1st post):

private TestPageConverted page = new TestPageConverted(); //this does CloneTree and assigns fields
void OnEnable()
{
    root = rootVisualElement;
    root.Add(page);
}
void Update ()
{
    //actions on page.header, page.gMText, page.mainIMGUI
}

This looks pretty good, I don’t have to Q(uery) or write code load the uxml/VisualAssetTree. “–csName” custom style is used to generate C# fields. Couldn’t use “name” as it’s used for ex. in unity’s toggle.

1 Like

Done, [Plugin] Free Uxml to C# wrappers generator + logical templates . It works better than I expected. My issue here is resolved.

1 Like

I feel UIElements should be improved for same use in code by naming uxml elements, like .NET’s WPF or UWP(xaml).
When write a layout in WPF and UWP, the compiler writes new C# code(extension of *.g.cs, *.g.i.cs) for the layout. (Like you made)
So then even IntelliSense detect it name as property with element type.

xaml

<Page
    x:Class="MyApp.MyPage"
    ...
    >
    <StackPanel>
 
        <Button x:Name="SomeButton"
                 Content="Click me"
                 Click="OnSomeButtonClick"/>
 
    </StackPanel>
</Page>

c#

namespace MyApp {
 
    public sealed partial class MyPage : Page {
     
        public MyPage() {
         
            this.InitializeComponent();
         
        }
     
        int count = 0;
        void OnSomeButtonClick(object sender, RoutedEventArgs e) {
         
            SomeButton.Content = $"You clicked {++count} time(s)";
        }
     
    }
 
}

In other respects, however, this follows a similar web development approach, so UIElements appears to be this way.
I think because the official documentation says that UQuery is similar to jQuery (commonly used JS library) as an example.

1 Like

I like that you can have both options. It allows for greater extensibility. As you already have done it - you have written a tool that let’s you do what you want and how you want. And people who need to squeeze out performance - they can fully use all practices to make best .uxml and .uss . I have a background in Flutter(mobile framework) and it has lots of stuff like you have shown in your example that is just null, things that take up memory space, obviously they probably do some optimizations for it, like stripping unnecessary code like some kind of Tree Shaking. But it might be impossible at the moment to do this for C#.

@Kamyker Official implementation would be nice, though, if Unity can focus on other features then it’s really unnecessary because we have the ability to write this kind of tools like you did (also we can contribute and have full access to it which we probably wouldn’t have (I constantly have UnityCSReference open just in case I need to look something up)), and you are very nice to share it with us.

I prefer C# option for building UI, it’s faster(speed of development) and easier. I will most likely create my own thing for this, I have already seen a couple of implementations because some good people decided to share.

My hope is that UI Builder will be so good that in majority of cases we won’t have to write css/xml manually - similar to Unity 4.6 UI.

When we created XAML we never ever intended you to write it by hand, it was always supposed to be a tool generated solution. When devs started to ignore Blend and do this by hand, it was concerning.

I don’t know why Unity3d are re-creating the wheel here given Adobe/Microsoft have already tried and failed at this experiment with bad results.

XML to describe UI, there are enough of a foundation of logic here to draw from to avoid further complicating the market with yet-another-xml-attempt-at-ui.

In fact, some of the folks who worked on XAML are now over at Google doing Flutter, in attempt to resurrect this problem via code-centric UI principles - much like React/Vui are attempting but again differently.

The realistic round-trip problem to be solved in this is via tooling, meaning invest in tooling bridges between apps like Sketch/Figma/Adobe XD and Illustrator vs asking unity3d to be the tool. The “dev-igner” role never will adopt this and it will always be a handball to the dev(s) to translate back into code/unity3d form.

This is from someone who has failed to make the platforms like this work by doing this exact strategy. We spent millions of USD on this problem and we …i cannot stress this more clearly - failed including Adobe.

5 Likes