Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

A working, stylable combo box (drop down list)

Discussion in 'UGUI & TextMesh Pro' started by jbooth, Aug 24, 2014.

  1. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I spent most of the last two days getting a working Combo Box (aka Drop Down List) working in uGUI. However, I had some specific requirements for it that made this a lot harder than simply building all the objects and hooking them up. I'll go over these requirements and why I think they are important below, then post the code at the bottom of this message along with an example project. The code is still pretty rough, as I'd like to improve some of the abstractions, but will likely be useful if you want to wrap your controls this way. This post is going to be long, but hopefully worth your time.

    My requirements:

    1. The user should be able to create a combo box as a single item in their UI, not as 20+ objects
    2. The coder should have a single point of access for everything about the combo box and not have to worry about the objects it's made from.
    3. The entire system should be stylable by the UI artist, without coder intervention.
    4. The styling should update automatically when the style is changed where ever it is used.
    5. You should be able to prefab the UI which contains the combo box.
    6. Any type of item should be able to be used in the combo box - want a list if images with text, a list of textures, or something else? No problem.

    Many of these requirements would be met by having a better encapsulation system for Unity, which I'll discuss later.

    Here is what a combo box looks like in Unity.



    Notice that it's made from a very large number of objects. This is problematic for a number of reasons. First, it's very time consuming to make all of this by hand and get them all working well together. Second, any programmer who needs to work with data in these components will have to have some way of finding them all first, or a giant list of references for the artist to fill out. Both of these options are extremely brittle, and IMO, not scalable to a large production.

    Lets put that aside for a second and talk about reuse. I should prefab this so I don't have to create it again - and then users can just drop them into the scene and just use them. The problem with this is that Unity does not support nested prefabs - so if I have a panel that needs to appear on every screen and prefab it, then if I change the combo box prefab it will not update in that scene.

    So this is what I set out to solve.

    How it works for the UI Artist:

    When they want to create a combo box in the game they go to GameObject->UI->Combo box and it will create a new combo box object under the currently selected uGui element. This component has three fields for the prefabs described below. As soon as you fill them out, it will create the combo box for you, hiding it's guts so it looks like a single item in the hierarchy. The prefabs act as the styling for the combo box - note that they aren't part of the scene in the classical sense (they are created on start, etc)

    How it works for the coder:

    To populate the combo box with items, you can do one of the following:

    Code (CSharp):
    1. // populate the combo box with text
    2. comboBox.AddItems("Test1", "Test2", "Test3", "Unity", "Needs", "A", "Better", "Encapsulation", "System", "Than", "Prefabs");
    3.  
    4. // or, populate it with textures
    5. comboBox.AddItems(myTex1, myText2);
    6.  
    7. // or populate it with both
    8.  
    9. comboBox.AddItems(StyledComboBox(new StyledItemButtonImageText.Data("MyString", myTex1));
    10.  
    11. // finally, listen for changes:
    12. comboBox.OnSelectionChanged += delegate(StyledItemitem)
    13. {
    14.    Debug.Log (item.GetText() + "" + comboBox.SelectedIndex);
    15. }
    16.  


    The coder essentially only works with the StyledComboBox component, setting it's items, getting the result, etc. They don't have to worry about the items being created/destroyed/placed/managed at all.

    How the UI artist styles the control:

    The UI artist sets up a prefab for the combo box and add's the ComboBoxPrefab template to it (or just modifies mine). This template has properties to link several things the system wants to know about; what rect to place the items under, which panel to toggle alpha on when the user opens or closes the combo box, and where to put the version that shows up in the menu.

    The UI artist then creates a second prefab for how an item looks in this menu, and adds a StyleItem component to it - StyleItem is a base class, and theirs currently one usable subclass (StyledItemButtonImageText), but it's easy to add new ones - this acts as the abstraction layer so the combo box doesn't really have to know about it's contents. For instance, they could make a version which has a button, an image, and a text in it, and arrange them however they should look using the current control, or a code could create a new StyledItem subclass with any number of controls inside of it.

    They can optionally create a third prefab if they want the version which appears as the main control (currently selected item) to look different than the regular items in the list, but in most cases they don't need this.

    ----------------------------------------------------------------------------------------------------------
    How Unity could provide better encapsulation and scale their engine better..
    ----------------------------------------------------------------------------------------------------------

    One of my major issues with Unity is the lack of proper encapsulation. Even if nested prefabs were to work correctly, I don't think the system provides enough encapsulation. The code posted below is a work around to provide the level of encapsulation I feel is necessary for a project to scale. It takes a complex system of interconnected objects, encapsulates them into a single object, and provides a single, unified API for the system.

    What I would like to see unity provide is a way to have a "closed prefab". This is essentially a prefab that hides all of it's children and data from the world, while allowing the person who sets up that prefab to provide a single API to work with it.

    The workflow would go something like this, using our combo box as an example, but could be equally applied to any reasonably complex game object network.

    - The creator of the asset would create the combo box prefab in the same manner they do now.
    - The creator would mark the prefab as closed, which would hide all the subobjects and show them an "edit" button. Pressing edit would show all of the hidden objects for them to make changes.
    - While in the edit mode, the user would be able to right click on any field in any sub-object and select expose, typing in a new name for the field's alias. This would then show up as a field on the top level prefab object for the user to edit, or the code to set.

    With this, you can create a prefab, hide it's guts, and expose only the data they want the user to be able to change, providing a single place to address the data, with a narrow aperture. More importantly, there's no giant list of pointers to fill out, or Find("someobject") in the code which breaks every time someone renames something.

    -----------------
    The Code
    -----------------

    The code is attached to this thread. It can likely be improved considerably, as I'm currently doing manual placement because I couldn't get the layout components to do what I expected to be able to do with them. I welcome suggestions to make the system better, or if people would like to work with me on wrapping some other common controls like this that would be wonderful. I hope this is useful for someone else, enjoy!
     

    Attached Files:

  2. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,221
    This is a cool combo box.

    I'll pass on your prefab feedback to the core team as they are responsible for prefab implementation. I do agree with many of the things you say and as we developed the UI we came across a number of things that would have been easier if there was better encapsulation support, unfortunately it is a non trivial thing to add.

    Some components (like this) work really well with programatic API that 'does it all' and a combo box is a great example of this (in fact our prototype combobox does basically what your one does). In some other components through it's not quite as ideal. I do think we could improve API for automatic creation of buttons and other simple elements that we have.
     
  3. senritsu

    senritsu

    Joined:
    Dec 12, 2012
    Posts:
    37
    Wouldn't it work (at least as a temporary measure as long as no better way to encapsulate is available) to use a custom inspector and just include a toggle button to apply HideInHierarchy to all children of the root combo box object? That would sort of do what your mentioned Edit button would do. The whole part about a simple way of exposing fields from childen would be a bit more tricky though.
     
  4. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I used to design the custom engines for a few different companies. The way we handled this type of encapsulation in the past was via a component that acted as a delegate between the root object the user see's and the internals, binding the properties of one to the other. HideInHierarchy and a custom editor could be used to accomplish the open/closed nature right now - but the property exposing would require storing all the values/target mappings in a dictionary. Doable, but likely much slower on the C# side..
     
  5. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    @tim: I actually plan to do this for things as simple as a button with a text component. I think all the individual pieces are great, but I never want to have a Find in my code, or list of pointers on a component so the code can know where the Text object is, etc. These things are all brittle by nature, and as production size scales up this is where Unity classically has issues. Additionally, having the styling for everything controlled in one central prefab is incredibly useful.

    Being able to have users create these types of abstractions without writing tons of code is really where I'd love to see Unity go. Mecanim is one place where Unity shines in this regard, in that a content creator can create an entire state machine and expose a simple blackboard of values/events for the coder to work with. However, ideally this type of flow would work in either direction - the coder should be able to create the blackboard and have the artist bind it's data to the state machine as well. UI is really no different, as it's usually about this same type of binding (score -> this text field or animation frame, etc)
     
  6. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,221
    Yeah. I understand what you are saying. I think it is the direction we are going with the UI, I would expect to see more tightly controlled interfaces appearing over time.
     
  7. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Hey Tim, one questions while you're here.

    So my combo box uses a large panel for the drop down area. However, if someone places this into a horrizontal layout group, how do I get it to only use the smaller area of the menu button for layout? I noticed the ILayoutIgnorer and ILayoutElement interfaces, but not a "GetLayoutRect" type function which can be overridden to provide this information..
     
  8. Melang

    Melang

    Joined:
    Mar 30, 2014
    Posts:
    166
    I'm not Tim, but I'll try to answer. :) Usually to make elements in a horizontal group use fixed width/height, I add a Layout Element to the element I need to adjust, and set flexible width to 0 , and preferred width to some number. (If all the elements need to be of the same width, it's better to use grid layout instead i think)

    But when I parent combo box to any element, it disappears in game (not in scene) and some weird red stuff appears. This is not the first time I've seen this behaviour, it happened not only with combo box but with other elements as well...
     

    Attached Files:

  9. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    The red X is a control that has negative width or height values. This seems to happen a lot by accident with me too, and I'm never quite sure why it ends up that way. I personally find the RectTransform confusing to work with; between having several different editing modes and the API not having functions which match the UI, it's easy to get confused as to what it's going to do. It seems like a really slick tool with a simple, elegant interface, but either there's bugs and bad behavior in it, or I'm just a dolt who can't quite make sense of it.

    What I was asking Tim for was a little different. Basically, I want to programmatically control how big a layout group thinks my element is - something like what the Layout Element actually does, in a way. Essentially, I want my control to not take the size of the panel which will open into account when it is being positioned in a layout. I don't want this to be set with a Layout Element component, because I want the user to be able to size the rect visually for the panel, and the item instantiated into the template to determine how big each element is.

    Though I've noticed that controls like the layout grid prefer to tell the elements inside of them how big to be instead of asking them how much space they need. This is certainly simpler and avoids lots of issues with dynamic layout and multiple sized elements, but it makes it much more difficult to do good styling since the parent control needs to be styled explicitly for the objects which go inside of it..
     
  10. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,221
    Hi, I'm going to ping the layout expert to take a look at this thread, I know a bit about it but want to make sure you get the right answer.
     
  11. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    I can't really comment on this without more details. I recommend recording a screencast (using Jing or similar) of the entire Editor (including Scene View, Hierarchy, Inspector) that shows the issues you're having. This is usually much more useful to us than written explanations.

    You can do that by creating a MonoBehaviour that implements the ILayoutElement interface.

    Well this is just how the GridLayoutGroup is designed. It's for elements that all have the same size, and for that, asking the individual elements for their size is not supported. It would be possible to make a TableLayoutGroup or similar that would arrange children in a table and ask them for their individual sizes and use that information somehow, but it's out of the scope of 4.6.
     
  12. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,688
    @Tim C Any chance the prototype ComboBox can get published to your Gists list? I remember seeing it a while back and would be good to compare notes.

    Thanks for the info @runevision will have to check out the LayoutElement interface a bit more closely
     
  13. Ziron999

    Ziron999

    Joined:
    Jan 22, 2014
    Posts:
    282
    I'm not sure why you made it harder on yourself... I got rid of all the hiding and added a bool check for if the menu is opened/closed to make it so you can't interact with the items until after it's open.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine.UI;
    5.  
    6.  
    7.  
    8. [RequireComponent(typeof(RectTransform))]
    9. public class StyledComboBox : StyledItem
    10. {
    11.     public delegate void SelectionChangedHandler(StyledItem item);
    12.     public SelectionChangedHandler OnSelectionChanged;
    13.  
    14.     public StyledComboBoxPrefab     containerPrefab;        // prefab for whole control
    15.     public StyledItem                 itemPrefab;                // prefab for item in drop down
    16.     public StyledItem                 itemMenuPrefab;        // prefab for item in menu
    17.  
    18.     [SerializeField]
    19.     //[HideInInspector]
    20.     private StyledComboBoxPrefab     root;
    21.  
    22.     [SerializeField]
    23.     //[HideInInspector]
    24.     private List<StyledItem> items = new List<StyledItem>();
    25.  
    26.     [SerializeField]
    27.     private int selectedIndex = 0;
    28.     public int SelectedIndex
    29.     {
    30.         get
    31.         {
    32.             return selectedIndex;
    33.         }
    34.         set
    35.         {
    36.             if (value >= 0 && value <= items.Count)
    37.             {
    38.                 selectedIndex = value;
    39.                 CreateMenuButton(items[selectedIndex]);
    40.             }
    41.  
    42.         }
    43.     }
    44.  
    45.  
    46.     public StyledItem SelectedItem
    47.     {
    48.         get
    49.         {
    50.             if (selectedIndex >= 0 && selectedIndex <= items.Count)
    51.                 return items[selectedIndex];
    52.             return null;
    53.         }
    54.     }
    55.  
    56.  
    57.     void Awake()
    58.     {
    59.         InitControl();
    60.     }
    61.  
    62.     bool DropBoxOpen = false;
    63.  
    64.     private void AddItem(object data)
    65.     {
    66.         if (itemPrefab != null)
    67.         {
    68.             Vector3[] corners = new Vector3[4];
    69.             itemPrefab.GetComponent<RectTransform>().GetLocalCorners(corners);
    70.             Vector3 pos = corners[0];
    71.             float sizeY = pos.y - corners[2].y;
    72.             pos.y = items.Count * sizeY;
    73.             StyledItem styledItem = Instantiate(itemPrefab, pos, root.itemRoot.rotation) as StyledItem;
    74.             RectTransform trans = styledItem.GetComponent<RectTransform>();
    75.             styledItem.Populate(data);
    76.             trans.parent = root.itemRoot.transform;
    77.  
    78.             trans.pivot = new Vector2(0,1);
    79.             trans.anchorMin = new Vector2(0,1);
    80.             trans.anchorMax = Vector2.one;
    81.             trans.anchoredPosition = new Vector2(0.0f, pos.y);
    82.             items.Add(styledItem);
    83.  
    84.             trans.offsetMin = new Vector2(0, pos.y + sizeY);
    85.             trans.offsetMax = new Vector2(0, pos.y);
    86.  
    87.             root.itemRoot.offsetMin = new Vector2(root.itemRoot.offsetMin.x, (items.Count + 2) * sizeY);
    88.  
    89.             Button b = styledItem.GetButton();
    90.             int curIndex = items.Count - 1;
    91.             if (b != null)
    92.             {
    93.                 b.onClick.AddListener(delegate() { OnItemClicked(styledItem, curIndex); });
    94.             }
    95.         }
    96.     }
    97.  
    98.     public void OnItemClicked(StyledItem item, int index)
    99.     {
    100.         if (DropBoxOpen)
    101.         {
    102.             SelectedIndex = index;
    103.  
    104.             TogglePanelState ();    // close
    105.             if (OnSelectionChanged != null)
    106.             {
    107.                 OnSelectionChanged (item);
    108.                 DropBoxOpen = false;
    109.             }
    110.         }
    111.     }
    112.  
    113.     public void ClearItems()
    114.     {
    115.         for (int i = items.Count - 1; i >= 0; --i)
    116.             DestroyObject (items [i].gameObject);
    117.     }
    118.  
    119.     public void AddItems(params object[] list)
    120.     {
    121.         ClearItems();
    122.  
    123.         for (int i = 0; i < list.Length; ++i)
    124.         {
    125.             AddItem(list[i]);
    126.         }
    127.         SelectedIndex = 0;
    128.     }
    129.  
    130.  
    131.  
    132.     public void InitControl()
    133.     {
    134.         if (root != null)
    135.             DestroyImmediate(root.gameObject);
    136.  
    137.         if (containerPrefab != null)
    138.         {
    139.             // create
    140.             RectTransform own = GetComponent<RectTransform>();
    141.             root = Instantiate(containerPrefab, own.position, own.rotation) as StyledComboBoxPrefab;
    142.             root.transform.parent = this.transform;
    143.  
    144.             RectTransform rt = root.GetComponent<RectTransform>();
    145.             rt.pivot = new Vector2(0.5f, 0.5f);
    146.             //root.anchoredPosition = Vector2.zero;
    147.             rt.anchorMin = Vector2.zero;
    148.             rt.anchorMax = Vector2.one;
    149.             rt.offsetMax = Vector2.zero;
    150.             rt.offsetMin = Vector2.zero;
    151.             //root.gameObject.hideFlags = HideFlags.HideInHierarchy; // should really be HideAndDontSave, but unity crashes
    152.             root.itemPanel.alpha = 0.0f;
    153.  
    154.             // create menu item
    155.             StyledItem toCreate = itemMenuPrefab;
    156.             if (toCreate == null)
    157.                 toCreate = itemPrefab;
    158.             CreateMenuButton(toCreate);
    159.         }
    160.     }
    161.  
    162.     private void CreateMenuButton(StyledItem toCreate)
    163.     {
    164.         if (root.menuItem.transform.childCount > 0)
    165.         {
    166.             for (int i = root.menuItem.transform.childCount - 1; i >= 0; --i)
    167.                 DestroyObject(root.menuItem.transform.GetChild(i).gameObject);
    168.         }
    169.         if (toCreate != null && root.menuItem != null)
    170.         {
    171.             StyledItem menuItem = Instantiate(toCreate) as StyledItem;
    172.             menuItem.transform.parent = root.menuItem.transform;
    173.             RectTransform mt = menuItem.GetComponent<RectTransform>();
    174.             mt.pivot = new Vector2(0.5f, 0.5f);
    175.             mt.anchorMin = Vector2.zero;
    176.             mt.anchorMax = Vector2.one;
    177.             mt.offsetMin = Vector2.zero;
    178.             mt.offsetMax = Vector2.zero;
    179.             //root.gameObject.hideFlags = HideFlags.HideInHierarchy; // should really be HideAndDontSave, but unity crashes
    180.             Button b = menuItem.GetButton();
    181.             if (b != null)
    182.             {
    183.                 b.onClick.AddListener(TogglePanelState);
    184.  
    185.             }
    186.         }
    187.     }
    188.  
    189.     public void TogglePanelState()
    190.     {
    191.         if (!DropBoxOpen)
    192.             DropBoxOpen = true;
    193.         else if (DropBoxOpen)
    194.             DropBoxOpen = false;
    195.         root.itemPanel.alpha = Mathf.Abs(root.itemPanel.alpha - 1.0f);
    196.     }
    197. }
    EDIT:
    Got rid of the debug log's lol...
     
    Last edited: Sep 30, 2014
  14. yakm

    yakm

    Joined:
    Sep 12, 2014
    Posts:
    24
    How would i use Screen Space - Camera and combo box together?
    On the canvas when using Screen Space - Camera, instead of Screen Space - Overlay on start. The image becomes too big to display.
     

    Attached Files:

  15. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    The script uses Transform.parent, which is almost always a bad idea with the UI system because it keeps the world space position and scale of the object. Use Transform.SetParent(parent, false) instead.
     
    Nanity and yakm like this.
  16. yakm

    yakm

    Joined:
    Sep 12, 2014
    Posts:
    24
    Thanks for the help, now it works perfectly with screen space - camera.

    I have provided the little modification that i have made for anyone running into the same issue in the future, to change it back to screen space - overlay change the bool worldSpaceOn.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine.UI;
    5.  
    6. [RequireComponent(typeof(RectTransform))]
    7. public class StyledComboBox : StyledItem
    8. {
    9.     public delegate void SelectionChangedHandler(StyledItem item);
    10.     public SelectionChangedHandler OnSelectionChanged;
    11.  
    12.     public StyledComboBoxPrefab     containerPrefab;        // prefab for whole control
    13.     public StyledItem                 itemPrefab;                // prefab for item in drop down
    14.     public StyledItem                 itemMenuPrefab;        // prefab for item in menu
    15.  
    16.     bool worldSpaceOn = false;
    17.  
    18.     [SerializeField]
    19.     [HideInInspector]
    20.     private StyledComboBoxPrefab     root;
    21.    
    22.     [SerializeField]
    23.     [HideInInspector]
    24.     private List<StyledItem> items = new List<StyledItem>();
    25.  
    26.     [SerializeField]
    27.     private int selectedIndex = 0;
    28.     public int SelectedIndex
    29.     {
    30.         get
    31.         {
    32.             return selectedIndex;
    33.         }
    34.         set
    35.         {
    36.             if (value >= 0 && value <= items.Count)
    37.             {
    38.                 selectedIndex = value;
    39.                 CreateMenuButton(items[selectedIndex]);
    40.             }
    41.  
    42.         }
    43.     }
    44.  
    45.  
    46.     public StyledItem SelectedItem
    47.     {
    48.         get
    49.         {
    50.             if (selectedIndex >= 0 && selectedIndex <= items.Count)
    51.                 return items[selectedIndex];
    52.             return null;
    53.         }
    54.     }
    55.  
    56.  
    57.     void Awake()
    58.     {
    59.         InitControl();
    60.     }
    61.    
    62.  
    63.     private void AddItem(object data)
    64.     {
    65.         if (itemPrefab != null)
    66.         {
    67.             Vector3[] corners = new Vector3[4];
    68.             itemPrefab.GetComponent<RectTransform>().GetLocalCorners(corners);
    69.             Vector3 pos = corners[0];
    70.             float sizeY = pos.y - corners[2].y;
    71.             pos.y = items.Count * sizeY;
    72.             StyledItem styledItem = Instantiate(itemPrefab, pos, root.itemRoot.rotation) as StyledItem;
    73.             RectTransform trans = styledItem.GetComponent<RectTransform>();
    74.             styledItem.Populate(data);
    75.             trans.parent = root.itemRoot.transform;
    76.  
    77.             trans.pivot = new Vector2(0,1);
    78.             trans.anchorMin = new Vector2(0,1);
    79.             trans.anchorMax = Vector2.one;
    80.             trans.anchoredPosition = new Vector2(0.0f, pos.y);
    81.             items.Add(styledItem);
    82.  
    83.             trans.offsetMin = new Vector2(0, pos.y + sizeY);
    84.             trans.offsetMax = new Vector2(0, pos.y);
    85.  
    86.             root.itemRoot.offsetMin = new Vector2(root.itemRoot.offsetMin.x, (items.Count + 2) * sizeY);
    87.  
    88.             Button b = styledItem.GetButton();
    89.             int curIndex = items.Count - 1;
    90.             if (b != null)
    91.             {
    92.                 b.onClick.AddListener(delegate() { OnItemClicked(styledItem, curIndex); });
    93.             }
    94.         }
    95.     }
    96.  
    97.     public void OnItemClicked(StyledItem item, int index)
    98.     {
    99.         SelectedIndex = index;
    100.  
    101.         TogglePanelState();    // close
    102.         if (OnSelectionChanged != null)
    103.         {
    104.             OnSelectionChanged(item);
    105.         }
    106.     }
    107.  
    108.     public void ClearItems()
    109.     {
    110.         for (int i = items.Count - 1; i >= 0; --i)
    111.             DestroyObject(items[i].gameObject);
    112.     }
    113.  
    114.     public void AddItems(params object[] list)
    115.     {
    116.         ClearItems();
    117.  
    118.         for (int i = 0; i < list.Length; ++i)
    119.         {
    120.             AddItem(list[i]);
    121.         }
    122.         SelectedIndex = 0;
    123.     }
    124.  
    125.     public void InitControl()
    126.     {
    127.         if (root != null)
    128.             DestroyImmediate(root.gameObject);
    129.  
    130.         if (containerPrefab != null)
    131.         {
    132.             // create
    133.             RectTransform own = GetComponent<RectTransform>();
    134.             root = Instantiate(containerPrefab, own.position, own.rotation) as StyledComboBoxPrefab;
    135.             //root.transform.parent = this.transform;
    136.             root.transform.SetParent(this.transform, worldSpaceOn);
    137.  
    138.             RectTransform rt = root.GetComponent<RectTransform>();
    139.             rt.pivot = new Vector2(0.5f, 0.5f);
    140.             //root.anchoredPosition = Vector2.zero;
    141.             rt.anchorMin = Vector2.zero;
    142.             rt.anchorMax = Vector2.one;
    143.             rt.offsetMax = Vector2.zero;
    144.             rt.offsetMin = Vector2.zero;
    145.             root.gameObject.hideFlags = HideFlags.HideInHierarchy; // should really be HideAndDontSave, but unity crashes
    146.             root.itemPanel.alpha = 0.0f;
    147.  
    148.             // create menu item
    149.             StyledItem toCreate = itemMenuPrefab;
    150.             if (toCreate == null)
    151.                 toCreate = itemPrefab;
    152.             CreateMenuButton(toCreate);
    153.         }
    154.     }
    155.  
    156.     private void CreateMenuButton(StyledItem toCreate)
    157.     {
    158.         if (root.menuItem.transform.childCount > 0)
    159.         {
    160.             for (int i = root.menuItem.transform.childCount - 1; i >= 0; --i)
    161.                 DestroyObject(root.menuItem.transform.GetChild(i).gameObject);
    162.         }
    163.         if (toCreate != null && root.menuItem != null)
    164.         {
    165.             StyledItem menuItem = Instantiate(toCreate) as StyledItem;
    166.             //menuItem.transform.parent = root.menuItem.transform;
    167.             menuItem.transform.SetParent(root.menuItem.transform, worldSpaceOn);
    168.  
    169.             RectTransform mt = menuItem.GetComponent<RectTransform>();
    170.             mt.pivot = new Vector2(0.5f, 0.5f);
    171.             mt.anchorMin = Vector2.zero;
    172.             mt.anchorMax = Vector2.one;
    173.             mt.offsetMin = Vector2.zero;
    174.             mt.offsetMax = Vector2.zero;
    175.             root.gameObject.hideFlags = HideFlags.HideInHierarchy; // should really be HideAndDontSave, but unity crashes
    176.             Button b = menuItem.GetButton();
    177.             if (b != null)
    178.             {
    179.                 b.onClick.AddListener(TogglePanelState);
    180.             }
    181.         }
    182.     }
    183.    
    184.     public void TogglePanelState()
    185.     {
    186.         root.itemPanel.alpha = Mathf.Abs(root.itemPanel.alpha - 1.0f);
    187.     }
    188. }
     
  17. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Keep getting two of these errors:
    Type `Button' does not contain a definition for `onClick' and no extension method `onClick' of type `Button' could be found (are you missing a using directive or an assembly reference?)
     
  18. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    sounds like your missing :

    #using UnityEngine.UI
     
  19. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    I wish it was that simple, im just using your zip file as it is, no modifications at all.
    The strange thing is that at work i get no error and home i get it, both using 4.6b21 : /
     
  20. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
    is there a way to make the height bigger? doesn't seem to let me go any higher than a certain height :/
     
  21. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
  22. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Just look at the code example in the first post, very easy.
     
  23. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
    Ok, I got it working, but also, is there a way to remove the scrollbar? It doesnt want to be removed for me..
     
  24. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    You want to remove it only when there are a few items in the list and then show it when its needed?
     
  25. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
    I just want to fully remove it from all my dropdowns
     
  26. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
    also, i closed out of my unity, reopened it and the combo boxes are giving me this really dumb error...

    http://gyazo.com/94b67846ad6b5f6851f000b877e559e9
     
  27. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
  28. Elit3d

    Elit3d

    Joined:
    Jan 25, 2014
    Posts:
    21
    4.6 BETA 20
     
  29. Dahlvash

    Dahlvash

    Joined:
    Feb 9, 2014
    Posts:
    54
    Have a problem with the download.
    The project itself is completely empty, not even an asses folder.
    In windows explorer however, there are some files. But after trying to reimport them, there seems to be things missing, like any prefabs (a couple of prefab meta files exist though).
    Any idea what's going on?
     
  30. Cherubim79

    Cherubim79

    Joined:
    May 3, 2014
    Posts:
    56
    OP: I'm still on 4.5.5 and haven't made the jump yet, but having looked at the vids for 4.6 and also creating my own worldspace ComboBox in 4.5.5 based on SpriteRenderers so I could have a 3D ComboBox, one of the things I also see missing and really needed in 4.6 UI is an actual runtime editable TextBox, because ideally what I would like to have, similar to WPF in .NET, is an editable ComboBox (much like a TextBox) with Autocompletion based on the underlying data source for the ComboBox.

    Just thought that I would weigh in and say I completely agree with you on needing a more scalable solution with one point of entry for the developer rather than templating lots of objects and instantiating (which is painfully slow in the editor but thankfully not on deploy). That seems so reminiscent to me of trying to create a game in the game "Little Big Planet" to cobble together instantiated objects at runtime and have it all be a kind of cobbled and fragile mess, that at any point could be poked the wrong way and fall like a giant Jenga tower of Babel, but I digress. :p

    Just looking over your code (and I need to do a better job of this too), controls in the .NET framework in Winforms, XAML, ASP.NET, etc, seem to generally be supplied data by an underlying data context and that the control should ideally be data agnostic. So you might give it a System.Object property named DataSource and supply a DisplayMember and ValueMember for the fields that are displayed and the underlying data value rather than simply doing an "Add Items" with an array of strings if you're dealing with a data needs in a higher production environment. One of the other pains of Unity of course is that at least the last time I tried I couldn't set a setter on a property and have it display in the Inspector, which cripples me on lots of things I want to do.

    (I understand that Unity was made with gaming in mind, but entertain me, I like to think in terms of gamification of apps that have utillity. :p )

    In the midst of working on what I'm calling "Virtual UI" (a separate worldspace based 3D UI for buttons, combobox, vertical sliders, and a data repeater for a rudimentary paginated data grid), I've found a need for lots of things that don't already exist in 4.6 uGUI, see pic below of a Windows Store app I'm creating in 4.5.5 just to test with doing an expense tracker with some reporting behind it from a local MSSQL -> ASP.NET/MVC stack via JSON. If that's successful, maybe I might write some other things like calorie trackers, workout programs, home automation interfaces, etc.

    I'll try and get this baby updated and out there soon, probably on codeplex before Asset Store, I haven't even done the prefab work yet, and feel like I really need to tackle a 3D editable textbox and date/time picker control before releasing, but would love to sync up and collaborate. 4.6 uGui seems promising and has a lot of things that look attractive to me, but still lacks much of what I need so I will probably give it some time to mature out of beta before making the jump, or incorporate much of what I'm doing into uGui by changing out my underlying VirtualUIButton and VirtualUIVerticalSlider.

     
    Jotave likes this.
  31. phileday

    phileday

    Joined:
    Feb 8, 2014
    Posts:
    120
    Hi
    Just trying out this combo box and think I'll find it really useful, except. I program in Java and my main script that I use is all in java. Would there be any chance you could give a java version of the test script.

    -------------------------------
    using UnityEngine;
    using System.Collections;

    public class TestStyledComboBox : MonoBehaviour
    {
    public StyledComboBox comboBox;

    void Start ()
    {
    // just text
    comboBox.AddItems("Test1", "Test2", "Test3", "Unity", "Needs", "A", "Better", "Encapsulation", "System", "Than", "Prefabs");

    }
    }
    ---------------------------

    That you give as an example?

    Thanks

    Phil
     
  32. Hybrid1969

    Hybrid1969

    Joined:
    Apr 19, 2013
    Posts:
    31
    i just tried this with 4.6.b20 and got error -

    Assets/ComboBox/Scripts/StyledComboBox.cs(92,35): error CS1061: Type `Button' does not contain a definition for `onClick' and no extension method `onClick' of type `Button' could be found (are you missing a using directive or an assembly reference?)

    Any clues?
     
  33. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Yes, i had that too and you most likely have some asset with a Button class in it thats causing a conflict.
    Try it with an empty project and you will see that it works.
     
  34. Hybrid1969

    Hybrid1969

    Joined:
    Apr 19, 2013
    Posts:
    31
    yes that works fine on its own but not alot of use if i cant add it with anything else :D

    So any ideas how I track down the conflict?

    TBH i may delay converting to 4.6 UI because Im finding extremely unstable ATM
     
  35. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Just do a search for "Button" should be the simplest way ; )
     
  36. Hybrid1969

    Hybrid1969

    Joined:
    Apr 19, 2013
    Posts:
    31
    Um....well i have ALOT of buttons so not really helpful, but appreciate it anyway
     
  37. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    Unity's Sample Assets package is in the global namespace, and it defines a Button class that conflicts with UnityEngine.UI's Button class. In the ComboBox scripts, change all instances of "Button" to "UnityEngine.UI.Button". Here are the files and line numbers:
    • StyledItem.cs: line 15
    • StyledItemButtonImageText.cs: lines 11 and 20
    • StyledComboBox.cs: lines 88 and 175
     
  38. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    I have implemented version, that does not require prefabs. Easy to use. Has a lot of cool features (see and try demo).
     

    Attached Files:

  39. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    This is a awesome addition to the gui, Unity should add it right away as a standard component : D
    Works really well and its very tweakable and flexible, top notch!
     
    Orimay likes this.
  40. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    This ComboBox has an invisible overlay, that closes dropdown on outer click. Also, any property update, both for ComboBox and ComboBoxItem, immediately apply changes. This ComboBox has dropdown shift correction, so that selected item is always displayed (even if it was changed programmatically) and it always has correct offset.

    ComboBox properties and methods:
    Action<int> OnSelectionChanged
    Fires when new item was selected​
    bool Interactable
    Enables or disables ComboBox​
    int ItemsToDisplay
    This setting shows how many items long visible dropdown will be​
    bool HideFirstItem
    Enables or disables first item in dropdown list; used for placeholder​
    int SelectedIndex
    Switches selected item​
    ComboBoxItem[] Items
    Updates ComboBox with items​
    void OnItemClicked(int index)
    Emits item selection​
    void AddItems(params object[] list)
    Adds new items of types ComboBoxItem, string and Sprite​
    void ClearItems()
    Removes all items​
    void UpdateGraphics()
    Updates ComboBox with it's new rect; used to recalculate ComboBox size on it's new rect size​

    ComboBoxItem properties and constructors:
    string Caption
    Text, that will be displayed for this item​
    Sprite Image
    Image, that will be displayed for this item​
    bool IsDisabled
    Makes item enabled or disabled​
    Action OnSelect
    Fires when item is selected​
    ComboBoxItem(string caption)
    ComboBoxItem(Sprite image)
    ComboBoxItem(string caption, bool disabled)
    ComboBoxItem(Sprite image, bool disabled)
    ComboBoxItem(string caption, Sprite image, bool disabled)
    ComboBoxItem(string caption, Sprite image, bool disabled, Action onSelect)
    ComboBoxItem(string caption, Sprite image, Action onSelect)
    ComboBoxItem(string caption, Action onSelect)
    ComboBoxItem(Sprite image, Action onSelect)
     
  41. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Hmm, seems to be a bug in the comboBox when using 3 or less items in it (ItemsToDisplay = 4).
    They wont show as they should, with 3 items the first item is blank and with 2 items both of them show up blank.
     
    Orimay likes this.
  42. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    Virror, thank You for Your feedback!
    Here is a fix.
     

    Attached Files:

  43. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Great, thanx!

    Edit: Works perfectly now, very, very useful asset!
     
    Orimay likes this.
  44. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    One major issue, it can be used in a build : p
    You need to get rid of the: AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
    since you cant use UnityEditor stuff in builds.

    Sorry for being such a bother : p
     
    Orimay likes this.
  45. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    This function wouldn't ever run on build if assets were loaded before. Anyway, I have an idea. Will try it soon.
     
  46. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Even if it wont run, all scripts will still be compiled without access to the Editor stuff. I just commented it out for now : D
     
  47. soportelsp

    soportelsp

    Joined:
    Sep 10, 2014
    Posts:
    2
    Hi kender, thanks for your script. I have a problem when you touch on dropdown, scale in differents resolutions on mobiles. For example samsung galaxy note 4, dropdown is very small.

    Thanksssss
     
    Orimay likes this.
  48. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    Well, the new fix is ready. Images are imported in Editor script now. Fixed scroll on runtime ComboBox fill. Fixed scaling. Please let me know if it solved Your problem.
     

    Attached Files:

  49. Orimay

    Orimay

    Joined:
    Nov 16, 2012
    Posts:
    304
    By the way, You can resize ComboBox RectTransform. If You resize it through code, You should call UpdateGraphics();
    All sizes will be recalculated immediately.
     
  50. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Great job, will get the new one right away!