How would I create a custom control, e.g. a dropdown field of images?

I’ve read through the documentation but I’m having a hard time understanding how I would approach creating a custom UI control. For example, I would very much like a drop-down menu whose items are all images–or even custom VisualElements (similar to what WPF does).

How should I approach something like this within the UIElements framework?

I apologize if this is too open-ended. I can clarify if needed.

Yes, the question is quite open-ended, so it’s hard to tell what is “not enough” information and what is “too much”. But I’ll give it a shot, and let me know if I’m off-topic.

The basic idea to build custom control with UIElements is to make a subclass of VisualElement that will drive the behavior of that control. The subclass will create the child elements that it requires, and will attach event manipulators to process these events.

In the case of a custom dropdown menu, I would imagine a VisualElement that displays the current selection. You could register a Clickable manipulator on it that, when triggered, would add a VisualElement that contains the full menu of available items.

In the case of a dropdown menu, you would probably want to close the menu if a click is detected outside of the menu list. There are a few ways to do this. One way is to first add a fullscreen empty element that is only there to catch any click outside the menu. The menu itself would be a child of that fullscreen element, so it would intercept clicks that are on the actual items shown in the menu.

I’ve made a quick prototype of that basic idea (in attachment). It only shows the basic dropdown behaviour (i.e., it doesn’t actually populate the menu with items, etc.). Hopefully, that will help to get you started.

4984688–486212–CustomControl.zip (21.1 KB)

1 Like

One thing that’s not mentioned and that I had to dig through source code to find, is that you need to also have a UxmlFactory and a UxmlTraits. These are self registering classes that will let you use your custom control in UXML.

UxmlFactory is easy. Just define it within your class like the example below. This registers your class with the UXML parser.

Next is UxmlTraits. This is how the UXML factory reads attributes on your UXML tag and decides how to modify your c# class to represent that.

Simple Example:

namespace MyCompany.UIElements
{
  public class CustomElement : VisualElement
  {
    public int someNumber { get; set; }

    public CustomElement() : this(0) {  }

    public CustomElement(int someNumber)
    {
      this.someNumber = someNumber;
    }

    public new class UxmlFactory : UxmlFactory<CustomElement, UxmlTraits> { }
    public new class UxmlTraits : VisualElement.UxmlTraits
    {
      UxmlIntAttributeDescription someNumberAttr = new UxmlIntAttributeDescription { name = "some-number" }

      public override void Init(VisualElement vr, IUxmlAttributes bag, CreationContext cc)
      {
        base.Init(ve, bag, cc);
        CustomElement ce = (CustomElement)ve;
        ve.someNumber = someNumberAttr.GetValueFromBag(bag, cc);
      }
    }
  }
}

The Attribute descriptions will just help you parse the attributes on your uxml tag. For example the someNumberAttr will look for the some-number=“5” in . Init is how you then turn that parsed value and modify the class. Some things to note:

First class is created using default constructor, THEN Init is called on the UxmlTraits. This means you’ll need setters and getters to modify your custom element after it’s constructor has been called. Usually this is wise anyways. Have your properties have setters that update the element live. That way whether used through code or via UXML the behavior is consistent.

Last thing is in your UXML don’t forget to include xmlns:mc=“MyCompany.UIElements” and then use <mc:CustomElement />

7 Likes

This is very helpful, thank you both very much. I had actually been moving away from using UXML despite its seeming advantages, because I just couldn’t figure out how to customize everything I needed. I initially just had a bunch of placeholder elements that needed to be replaced in code, but it looks like I may not need to do that as much. (For example, I have situations where, depending on the current selected value of an EnumField, I need to display different sections of controls. It looks like that can stay as code-behind but I can UXML-ize a lot more.)

@mcoted3d Your sample code is extremely helpful, as it shows how I might actually draw the dropdown menu in the correct location, which is what scared me away from this task the most. I will give this a go, thanks again.

1 Like

For this kind of situation, I often define all possible UIs (for all Enum values in your case) in the UXML file, under separate “container” elements. That way, via code, I just flip the myElement.style.display style property to only have one part (“container”) of the UI visible at any one time. You can then bind all controls once, at the initialization stage, and just worry about visibility while the Enum is being changed.

@mcoted3d I’ve searched the documentation but can’t find a list of built-in “manipulators” like the Clickable object you instantiated. In fact, that paradigm is completely new to me. The docs specify a method called RegisterCallback which allows handling events from VisualElements (Unity - Manual: Responding to Events), but where do manipulators fit in to this and where can I find that documentation?

It is true that the manipulator documentation is pretty much non-existing, but don’t worry too much about those. They are basically helpers to facilitate events registration and state tracking. You can absolutely achieve the same result with good old RegisterCallback() calls on your VisualElements.

EDIT: The docs for AddManipulator() can be found here, it’s an extension method:

But, again, this documentation is not really useful.

Here’s a “video form” description of Manipulators.

In short, they are just mini-state machines that track multiple types of events. For Clickable, it registers for the MouseDown and MouseUp events, then when it detects a MouseDown on its target, it waits for the corresponding MouseUp on the same target before declaring that a “click” has occurred.

Thank you so much, I was pulling my hair on defining a custom UI Elelement. Turn out I was missing the namesapce definition in the uxml !

@mcoted3d If I wanted to make the menu extend outside/on top of its parent window, much like the popup fields in the Unity editor do, would I have to create a completely separate window for the popup menu content? With the solution you provided, the menu contents get clipped by the window. Here is the desired behavior:

5158934--511235--upload_2019-11-9_14-23-57.png

Edit: I’m making a little bit of progress creating a new EditorWindow subclass and using a method like ShowAsDropdown (though I’m not sure it behaves any differently from the other Show methods), but it still displays the title bar (with title, maximize/close buttons, etc.). That’s not what I want from a drop-down. :confused:

I think the title bar results from the GetWindow() method. If you instantiate your EditorWindow with CreateInstance() instead, you might be able to achieve the right result.

var p = ScriptableObject.CreateInstance<PopupWindow>();
p.ShowAsDropDown(rect, size);
1 Like

With IMGUI you would use PopupWindowContent, but there doesn’t seem to be one with UIElements. @mcoted3d are there any plans on adding different styles of Windows? Up until now I’ve only been able to make dockable windows, but not Utility, PopupWindowContent ones, etc…

I’m not aware of future plans for window styles. I’ll add that to our feature requests list.

1 Like

Thanks! I highly appreciate it. We’re working on a tool that displays a dropdown like the “Add Component” window at each Game Object. We’re trying to do everything with pure UIElements, and so far it’s working marvelously. Congrats!

5195825--516542--dropdown.png

2 Likes

Hi, you can check my tools I create non-binary tree popup, and you can simply implement it with attribute.

I meant using UIToolkit, not IMGUI :slight_smile: Note that the question was asked three years ago. Despite that, thanks anyway!

Ah, understood, sorry miss that point)

1 Like