So I’ve spent some time battling this problem, because I have been trying to achieve the same thing.
I want a placeholder text, that is removed once a real option is selected.
I was messing around with this for a long time, and inspired by a post from Ali Kanat (thanks!) here: Disable an option(s) in a dropdown Unity - Stack Overflow
I was finally able to put a robust solution together that I was happy with.
The core idea of this solution is to:
- Utilize a GridLayout Group on the template content GameObject - this helps you to collapse and hide content.
- Add a hidden ‘placeholder’ item to your TMP options
- And finally, use a ‘ToggleCheck’ Start script on the Item in the TMP dropdown template, this helps you identify your placeholder and disable (HIDE) it.
- (A disabled object inside a GridLayoutGroup is ‘collapsed’ and hidden.)
The GridLayout script will ensure that the placeholder always remains hidden, and now you get the benefit of OnValueChanged firing properly (since the hidden default value is picked first!).
I’ll put my steps and scripts here, in case anyone wants to try my idea.
First, in the template of your dropdown, add a GridLayoutGroup Script to the Content GameObject.
Give it a fixed width that you like (or if you want more flexibilty, you can add your own grid resize controller script and have it resized to match its parent. for the purpose of this example, im just going to use a fixed width of 400).
Screenshot of where to add this:
Now, in your dropdown initialization, insert a ‘placeholder’ item. Give it a unique name that no one will use (I went with {{placeholder.com.data}}):
var ddl = new List<TMP_Dropdown.OptionData>();
//lets first insert a 'placeholder' option
ddl.Add(new TMP_Dropdown.OptionData("{{placeholder.com.data}}"));
//then fill out your usual entries in the dropdown, as you normally would
//...
//After that is done, insert these options to your dropdown
dropdownTMP.options.Clear();
dropdownTMP.options = ddl;
//and IMPORTANT - force set the choice to the first item (the placeholder)
dropdownTMP.value = 0;
Next, the onDropdownValueChange method can now do what it normally does, in addition just add a step to turn the placeholder text label you have off:
public void OnDropdownValueChange()
{
//turn the placeholder off now, or remove its text (or both)
TMPPlaceholderText.text = "";
TMPPlaceholderText.gameObject.SetActive(false);
//and turn the dropdown label on
dropdownLabelTMP.gameObject.SetActive(true);
}
And finally - how to turn the placeholder off on intialization. This you can now achieve by adding a “ToggleCheck” script to the ‘Item’ GameObject inside the dropdown template.
Screenshot of where to add the script:
And here is the content - you want to catch the name of the ‘placeholder’ toggle, if it matches the placeholder text you specified, hide it (and make it un-interactable too).
NOTE: The name will have the text “Item 0:” prefixed to it, as it was the first option you inserted to the TMP OptionData.
public class ToggleCheck : MonoBehaviour
{
//This script should be attached to Item
void Start()
{
Toggle toggle = gameObject.GetComponent<Toggle>();
if (toggle != null && toggle.name == "Item 0: {{placeholder.com.data}}")
{
toggle.interactable = false;
toggle.gameObject.SetActive(false);
//and resize the grid layout if you want, since this is the first item, and you only need to do this once.
//gameObject.GetComponentInParent<GridResizeController>().ApplyGridResize();
}
}
}
This ensures that the placeholder is selected first, allowing OnDropdownValueChange to fire when it is changed for the first time to a real value.
Obviously you should add additional placeholder validation, if someones tries to use item 0, etc. And you could be even smarter with the ToggleCheck script (read the placeholder from some internal lookup table, hide the name etc). It enables a lot more cool customization, potentially. You can decide how you want to implement those things.
Bonus: Here is a method you can use to resize the GridLayoutGroup width to match its parent:
private void ApplyGridResize(GridLayoutGroup gridLayout)
{
// grab the width of my parent
var width = gameObject.transform.parent.GetComponent<RectTransform>().rect.width;
Vector2 newSize = new Vector2(width, gridLayout.cellSize.y);
gridLayout.cellSize = newSize;
}
If there is an easier way to achieve this in Unity 2019 and onwards I’d love to hear it! But for now this works really well for me.