Control Customization In Code
Introduction
Gum Forms provide fully functional controls with minimal setup. These controls can be restyled in code, either per-instance, or globally per control type. Customization can be performed in-code or in the Gum tool.
States vs Direct Property Assignments
To customize a component, either the instances can be modified directly, such as directly changing a Text's color), or a variable can be modified through a state, such as changing a background color when the button is highlighted.
Whether you use a direct property assignment or whether you use a state depends on whether the variable should always be applied or whether it should only be applied depending on the actions the user has taken and other UI related properties like enabled/disabled.
This document covers both approaches.
Directly Setting an Instance's Values
Values can be directly assigned on items within an instance. If the variable being assigned does not change in response to user interaction or in response to the control being disabled, the variable can be directly assigned instead of using states.
For example we can adjust the font color on a Button directly.
The following code shows how to modify the text color on a Button.
We can see the type of objects in a control by inspecting the Visual.Children
collection in a debugger, or by printing out the children to text output. For example, we can see that a default Button has the following items:
ButtonBackground of type ColoredRectangleRuntime
TextInstance of type TextRuntime
FocusedIndicator of type RectangleRuntime
Components are ultimately made out of the following base runtimes:
For information on working with the individual components, click the links in the list above.
Identifying State vs Direct Assignment Variables
Some variables are assigned in states on default controls. We can identify which variables are assigned in states by inspecting the category for the control type. We can add the control's Visual.Categories
property to the debugger or output its information to text output to see this information. For example, a default Button has the following category:
ButtonCategory
Enabled
Focused
Highlighted
HighlightedFocused
Pushed
Disabled
DisabledFocused
These states modify some of the existing variables which are listed if we expand the states in the watch window. For example, if we expand Enabled, we see that the state modifies:
ButtonBackground.Color
FocusedIndicator.Visible
Since these variables are modified by our button's states, that means these variables are changed in response to button interactions.
Keep in mind that these variables are assigned only when a button is interacted with. Therefore, if we explicitly change the ButtonBackground's color when we initialize the button, this color will apply until we hover or click on the button, at which point our changes are overwritten by the application of the button's states.
We can see this by assigning the button background. The change shows initially but then the button reverts to the values assigned in its states when the mouse moves over the button:
To apply changes to variables which are modified by state, we have two options:
We can modify the state so that the color is assigned whenever the state is applied
We can clear the variables in the states so that the states do not overwrite our values
Customizing an Instance's States
Individual instances can be customized by modifying the built-in states which are added automatically by the default implementations.
First we'll begin with a default button as shown in the following code block:
Notice that this button has subtle color changes when the cursor hovers over or pushes on it.
We can customize the state by modifying the values. For example, we can change the color of the background by adding the following code:
Now the button highlights yellow instead of a lighter blue.
Any property on the button or its children can be modified through states. For example, we can also change the text color and size as shown in the following code. You may need to make the button bigger so it can contain the larger text.
The button text now becomes black and is twice as big when highlighted but notice that the text changes are not undone when the cursor moves off of the button (when the Highlighted state is unset).
The reason that the hover state is not unset is because all variables which are set through states persist until they are undone. Typically if you create states in the Gum tool, the Gum tool forces any state which is set in a category to also be propagated through all other states in the same category. However, when we're setting states in code, we must make sure to apply any states that we care to all other categories.
In this case we can fix the larger text by setting the TextInstance's color and FontScale back to its default:
Removing Variables from States
We can also remove variables from states so that the states do not overwrite our explicitly set color. For example, if we want buttons to always be orange, we can clear the states and set the color as shown in the following code:
Accessing Children Instances
Some Forms components contain other Forms components. For example, the ListBox component contains a scroll bar named VerticalScrollBar, which itself contains a button named UpButtonInstance. These children can be obtained by calling GetGraphicalUiElementByName. This method searches for items recursively, so it can be called at the top level object. For example, the following code can be used to obtain a reference to the UpButtonInstance's Visual.
Removing and Replacing Instances
As mentioned above, all Forms components are made of individual objects which are referenced through the Visual.Children
property. For example, a default Button includes the following children:
ButtonBackground of type ColoredRectangleRuntime
TextInstance of type TextRuntime
FocusedIndicator of type RectangleRuntime
Forms components have almost no requirements for their children unless the children are critical for the function of the component. For example, a Button can function without any of its children since the Button itself is a container which has size and which can be clicked. Therefore, we are free to remove elements from the button to achieve more flexibility in our styling.
For example, we can remove the background from a Button so it only displays its Text instance. Notice that it is still fully functional as shown by the click handler.
In this case the Button still has its default states which are attempting to set the ButtonBackground's color in response to hover and click. If a state references a missing instance then the component ignores the state value.
We are also free to add additional objects to any component by modifying its Visual. For example, we could use a NineSliceRuntime, SpriteRuntime, or even a regular RectangleRuntime for the button's background.
The following code shows how to replace the background with a RectangleRuntime so that the button has an outline instead of a filled background.
In this example we are creating a RectangleRuntime as a replacement for the existing background, but keep in mind Gum controls can contain any children. You are free to add and remove controls to style your component as needed.
Required Children
Some Forms components require having children of a certain type to function properly. For example, a TextBox must have a TextInstance child to be able to display its text. These children should not be removed from the the control's Visual.Children so that the control can remain functional. The following lists the children which should not be removed:
Button
TextInstance of type TextRuntime - optional, but needed if the Text property is used
CheckBox
TextInstance of type TextRuntime - optional, but needed if the Text property is used
ComboBox
ListBoxInstance of type InteractiveGue with a Forms control of ListBox
TextInstance of type TextRuntime
Label
TextInstance of type TextRuntime
ListBox
VerticalScrollBarInstance of type InteractiveGue
InnerPanelInstance of type InteractiveGue
ClipContainerInstance of type InteractiveGue
ListBoxItem
TextInstance of type TextRuntime - optional, is used to display text if it exists
MenuItem
TextInstance of type TextRuntime - optional, is used to display text if it exists
PasswordBox
TextInstance of type TextRuntime
CaretInstance of type InteractiveGue
SelectionInstance of type InteractiveGue
PlaceholderTextInstance of type TextRuntime - optional, is used to display placeholder text if it exists
RadioButton
TextInstance of type TextRuntime - optional, is used to display text if it exists
ScrollBar
UpButtonInstance of type InteractiveGue
DownButtonInstance of type InteractiveGue
ThumbInstance of type InteractiveGue
TrackInstance of type InteractiveGue
ScrollViewer
VerticalScrollBarInstance of type InteractiveGue
InnerPanelInstance of type InteractiveGue
ClipContainerInstance of type InteractiveGue
Slider
ThumbInstance of type InteractiveGue
TrackInstance of type InteractiveGue
TextBox
TextInstance of type TextRuntime
CaretInstance of type InteractiveGue
SelectionInstance of type InteractiveGue
PlaceholderTextInstance of type TextRuntime - optional, is used to display placeholder text if it exists
Replacing Styling Globally with Derived Classes
The example above shows how to replace styling on a single Button instance. Instead of changing each instance manually, we can create a derived class which defines its own custom styling.
The first step is to create a new class which inherits from the default Forms control. These can all be found in the DefaultVisuals page linked above.
For example, we can create a derived class which inherits from the base DefaultButtonRuntime as shown in the following code:
Notice that we must implement a constructor with the same parameters as the base class - this is important because Gum calls this constructor for us when we create a Button instance and the parameter list must match.
Now we can implement our own styling inside the if(fullInstantiation)
block. The code to implement styling here is the same as above except this object is the visual, so the category exists on this
. For example, we can make the background pink when highlighted and enabled as shown in the following code:
Next we need to tell Gum Forms to use our new button as the default Button type. We can do this by replacing the default type associated with Button
as shown in the following code. This code should go in Game1 right after Gum.Initialize(this);
, before instantiating any Buttons as shown in the following code:
You may want to also remove or comment out any code for customizing the button explicitly below otherwise your pink styling may get overwritten on the particular instance by the styling written earlier in this tutorial.
By instantiating a Button
, Forms automatically uses your new StyledButtonRuntime
resulting in the button appearing pink.
Defining a ButtonRuntime Without Inheritance
The section above shows how to customize a button using inheritance. This approach is beneificial if you would like to modify the styling colors on the existing children of the button. Since Gum visuals are regular Gum objects, you can achieve even more customization by creating a new Button class which inherits from InteractiveGue and adding your own controls. In other words, you can add your own instances to the button (such as additional Text instances) or replace existing instances with your own (such as replacing the background ColoredRectangleRuntime with a NineSlice or Sprite).
For example, we can create a NineSlice that uses the following image:
Download this file to your game's Content folder, and mark the file as Copy if Newer. Make sure it appears in Visual Studio. For more information on file loading, see the File Loading tutorial.
Now we can create a new Button which inherits from InteractiveGue rather than DefaultButtonRuntime to fully customize the appearance of our button. Note that the only requirement that Buttons have is that they contain a Text object named TextIntance, so we should copy this instance from the DefaultButtonRutime code into our own. Our new Button also has a NineSlice which references the button_square_gradient.png from above, and states for Enabled and Pressed.
In general when creating your own Forms control, it can be helpful to reference the existing Default implementation for the control you are creating.
Of course we also need to tell Forms to use our new class as the default button type:
Now we can run our game and see the button in action:
As mentioned above, you are free to add any controls to your custom button including icons, additional Text instances, and additional Sprites for other effects. You can customize your Forms objects to look however you want.
Available States
Most controls in Forms share the same common states. The exception is components which can be toggled on/off such as CheckBox. For all other controls the following states exist:
Enabled
Disabled
Highlighted
Pushed
Focused
HighlightedFocused
DisabledFocused
CheckBoxes append the words On, Off, and Indeterminate to the states. For example, a CheckBox can support states including:
EnabledOn
EnabledOff
EnabledIndeterminate
DisabledOn
DisabledOff
DisabledIndetermate
... and so on.
These states can be accessed through the FrameworkElement const strings, such as:
Defining a Custom Runtime from Gum
Buttons can be defined fully in the Gum tool. This approach allows you to preview your visuals in the editor, and allows editing of the styles without writing any code.
Conceptually the steps are as follows:
Define a component for your forms type in the Gum tool
Add the states needed for the forms type that you are working with in the proper category
Define a custom runtime for the Forms control in your Visual Studio project
Associate the custom runtime to the forms type using the DefaultFormsComponents dictionary
This section walks you through how to create a custom Button component, and how to use this in your project. Once you understand how to create a Button component, other Forms controls can be created similarly.
Defining a Button Component in Gum
The first step is to define a Button component in Gum. This component can be named anything you want. For example, you may name it Button or StandardButton. You can also create components for specific purposes such as CloseButton which would be a button that closes menus.
Components defined in Gum can contain almost anything you want; however, Buttons should usually contain a TextInstance so that the Text property can assign the string on an internal Text object.
The following image shows the a component named StandardButton which contains a ColoredRectangle, a Rectangle, and a Text instance. For other controls, see the DefaultVisuals page linked above.
Adding Button States to the Component
All Gum Forms components react to various properties by assigning states. For a full list of states needed, see the DefaultVisuals page linked above.
For Buttons, we can add a ButtonCategory state. You are free to implement as many or as few states as you want. For the full list of states see above in the Available States section.
Defining a Custom Runtime for the Forms Control
Once you have created a Component in your project, you need to tell your game to use this for Buttons. To do this, add the following code in your Game class:
Associate the Custom Runtime to the Forms Type
Finally, we can associate the Forms control with the runtime. For example, the following code can be used to create a StandardButtonRuntime whenever the code calls new Button
. Note that this is not a requirement for working with Forms, but it can make testing more convenient.
Note that in this case, we can only associate one type of component runtime with each type of Forms control. In other words, if you write code that instantiates a new Button using the Button constructor, a StandardButtonRuntime will be created internally.
Of course, you are not required to create buttons this way - you can also create buttons by adding instances of your component in your Gum screen in the tool, or by instantiating the desired runtime.
Modifying ListBoxItems
ListBoxItems are typically created automatically when items are added to a ListBox instance. We can modify ListBoxItems by creating a runtime object for our ListBoxItem then assigning the ListBox's VisualTemplate.
The easiest way to create a runtime object for ListBoxItem is to copy the existing DefaultListBoxItemRuntime class which can be found here: https://github.com/vchelaru/Gum/blob/master/MonoGameGum/Forms/DefaultVisuals/DefaultListBoxItemRuntime.cs
For an example of a fully customized ListBoxItem, see this example: https://github.com/vchelaru/Gum/blob/master/Samples/GumFormsSample/GumFormsSampleCommon/CustomRuntimes/CustomListBoxItemRuntime.cs
You may want to rename the class when creating your own version. For example, you may want to name yours CustomListBoxItemRuntime
.
Notice that the DefaultListBoxItemRuntime creates visual states beginning on this line: https://github.com/vchelaru/Gum/blob/cc88486578636cdb46f0c3333233e54f54a75eba/MonoGameGum/Forms/DefaultVisuals/DefaultListBoxItemRuntime.cs#L68
Once you have created your custom ListBoxItem runtime implementation, you can use it on a list box by assigning the VisualTemplate. Be sure to assign it before adding items to your ListBox, as shown in the following code:
Last updated
Was this helpful?