Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
FlatRedBall Forms has full support for control with a mouse and keyboard as well as an Xbox360GamePad. This document discusses how to enable this behavior and details about its behavior.
FlatRedBall.Forms controls have built-in support for gamepad navigation. Once gamepad input is enabled for Forms objects, the user can move between controls and perform common UI operations. For example, consider the following layout using default Forms elements:
The layout above contains the following interactive Forms elements:
Button
CheckBox
ComboBox
ListBox
RadioButton
Slider
TextBox
To enable gamepad interaction with these elements:
Add the which gamepads can control focus and selection using the GuiManager.GamePadsForUiControl list as shown in the following snipet:
This code can be added anywhere, and gamepads can be added and removed to allow only certain gamepads to control the UI.
Select which control should begin with focus. For example, the code above sets the button as having initial focus: Forms.ButtonInstance.IsFocused = true;
Once one of the controls on a screen has focus, the gamepad can interact with it. For example, the following images and animations show interaction which can be performed with a gamepad including clicking a button, selecting items in a list box, and moving the slider. Once the ButtonInstance IsFocused is set to true, the button appears focused with a white rectangle.
Navigation between elements can be done by pressing up, down, left, or right on the analog stick or d-pad. The A button selects the control:
The tab order of the items matches the order of the items in Gum. The layout has the following tab order, as shown in Gum:
ListBoxes can have two focused states:
Top-level focus (the entire list box has focus)
Item focus - the items within the list box have focus
When the list box has top-level focus, tabbing between items selects the next sibling item. For example, the following image shows the ListBox on the top left of the screen having focus. Pressing "down" to tab to the next control results in the ComboBox (with the word "Impossible") gaining focus.
If a ListBox has item focus ( its DoListItemsHaveFocus
property is set to true), then tabbing up and down selects the next or previous items in the list, as shown in the following animation:
The DoListItemsHaveFocus property can be set to true in the following ways:
By explicitly setting the value in code, either directly or through data binding.
By giving the ListBox top-level focus, then pressing the confirm button on the gamepad. This is similar to pressing the confirm button on a ComboBox.
This two-layer selection mode is useful if the list box is part of a more complex screen and you would like the player to be able to navigate between controls. By contrast, if your screen contains a list box which must be selected before proceeding (such as a level select screen), then you may want to give the ListBox focus and also set its DoListItemsHaveFocus
to true on screen CustomInitialize.
Note that if an item is selected with the gamepad's confirm button, then the item appears selected and DoListItemsHaveFocus
is set to false, giving the list box top-level focus.
FlatRedBall.Forms includes a control called OnScreenKeyboard which can be used to enter text with a gamepad into a TextBox. For information on how to use this control, see the OnScreenKeyboard page.
Slider controls can be focused with the gamepad. When a Slider has focus, pressing left or right on the analog stick or d-pad moves the slider value by its SmallChange property. For more information, see the Slider page.
By default any control which implements IInputReceiver can be focused through tabbing. To skip a control, set its GamepadTabbingFocusBehavior to TabbingFocusBehavior.SkipOnTab. For example, the following code would mark a TextBox as not being selected through gamepad tabbing:
FlatRedBall Forms is a set of classes which are used to give UI controls automatic behavior. When using FlatRedBall Forms, your code has access to the FlatRedBall Forms object (such as Button) as well as the Visual for the Forms object (such as the component DefaultForms/Button). Since your code has access to both the Forms and Gum object, some confusion may arise about which object to interact with in code.
This guide discusses the relationship between the Forms and Gum objects, and provides some guidelines to determine whether to interact with one of the other.
The most important concept here is - some components in your project are Forms types, and some are not. Only Forms-implementing component instances appear in the Forms property, but all instances appear in the GumScreen property.
To understand how Forms and Gum objects interact, we will consider a simple example - a Screen with a single Button. The following image shows a default Button instance in a Gum screen:
In this example, the MenuScreenGum is loaded by the FlatRedBall screen MenuScreen.
We can access both the Gum and Forms objects in Visual Studio as shown in the following code. Keep in mind that every Screen with Gum and Forms objects will have GumScreen and Forms properties. These make it easy to access objects the same way no matter which Screen you are working on:
Notice that setting width and height modifies the same object at runtime - it appears as a square. Also, filling in the two click handlers would result in both handlers being called when the button is clicked.
You may be wondering - which object should I access in code? The answer is - usually it's best to use the Forms object. If you end up needing to make changes to the Gum object (such as to change its position or size), it's worth considering whether this change should actually be done in code. Most of the time these changes should be performed in the Gum tool.
FlatRedBall.Forms objects are wrappers around Gum objects. For example, in the example above we have a Forms.ButtonInstance. This object has a reference to the Gum object. The Forms object automates behavior so that the Gum object behaves like a UI element. For example, the Forms Button object automatically modifies the state of the button in response to cursor hovers and clicks. No custom code is necessary to achieve this behavior.
This behavior is convenient, but it is not particularly complex - at least not conceptually. The Forms object is responsible for detecting if the cursor is hovering over the button, or if the left mouse button is pressed. If so, the Forms object modifies the state of the button. These states are part of the default Button control, and can be inspected (and modified) in Gum.
The Forms Button logic is responsible for setting the button's state, and it will do so in response to any UI behavior. Therefore, manually setting the Gum object's state is not recommended - it will be over written by the Forms object as shown in the following code and animation:
Notice that the state is not immediately changed, but rather only when the mouse moves over the button. Once the mouse hovers over the button, its state changes immediatelz. After the mouse leaves the button, the state is reverted to Enabled. Setting the state through the Gum object can produce confusion. For example, a button may appear pushed or disabled if the state is manually assigned; however, the actual behavior of the button will not match a disabled state. Therefore, the button's state (specifically the CurrentButtonCategoryState) is controlled by the Forms Button object. The Forms Button object will modify the state according to standard button behavior, so custom code should not modify the Gum button's state.
The Forms property in FlatRedBall Screens contains properties for all Forms objects in the Gum screen, and only the Forms objects. Therefore, any object that is part of the Gum screen which is not a Forms type (such as Button or TextBox) does not appear in the Forms property.
For example, consider the following screen in Gum:
The left column consists of Forms controls: a Button, ListBox, and TextBox. The right column consists of instances of Gum objects which do not implement any Forms controls: a Text, ColoredRectangle, and Container.
In code, the Forms property only contains references to the Gum-implementing instances. Therefore, the following code is valid:
However, the following is not valid code because these instances are not Forms-implementing types:
To access the non-forms objects, you must use the GameScreen object. For example, the following code is valid:
Keep in mind that an instance in a Gum screen will always be a Gum object but only sometimes be a Forms object. This means that any instance that is a Forms object can also be accessed through GumScreen as shown in the following code:
As mentioned above, accessing Forms objects through GumScreen is generally not good practice.
FlatRedBall Forms contains many types of controls each with their own type of automatic behavior. The following list is an example (not a complete list) of properties which are controlled by Forms behaviors, and which should not be modified on the Gum object:
Button ButtonCategoryState controlled by cursor and Button.IsEnabled
RadioButton RadioButtonCategoryState controlled by RadioButton.IsChecked
ComboBox ListBoxInstance Visible controlled by ComboBox.IsDropDownOpen
ListBox and ScrollViewer VerticalScrollBarInstance Visible controlled by ScrollViewer.VerticalScrollBarVisibility and the number of items in the list
Slider ThumbInstance X controlled by Slider.Value, Slider.Minimum, and Slider.Maximum
TextBox.CaretInstance X and Y controlled by TextBox.CareIndex and TextBox.TextWrapping
As mentioned above, this is not a complete list. In general, if you plan on modifying a Forms object in code, you should first look to the Forms object to see if it provides a way to modify the object as desired. If your custom code modifies the Gum properties mentioned above, these changes will likely be overwritten by automatic Forms behavior.
FlatRedBall Forms also provides a way to standardize UI programming. When working against Gum objects, the types of the Gum objects depend on the project. For example, one project may use the default Button object for its Button, whereas another project may create a customized button called StyledButton which contains a different set of values. In both cases (assuming the behaviors have been set up correctly in Gum), both objects will produce a Forms Button, and programming against both will be identical. This makes it easier to learn the Forms syntax once and code the same everywhere.
The Forms objects standardize syntax, but they do not provide the full control that can be provided by access to the Gum objects. For example, consider a game which lets the player choose which save slot to load. Each save slot may be represented by a Button, and the Text on the button may display the name of the character. However, the game may also include additional information on each save slot, such as the level of the character, or the percentage completed in the game. These properties are not part of the Button class because they are custom to a particular game. In this case, the code may need to access the Gum object to modify the CharacterLevelText or PercentageCompletedText objects. Forms objects also provide a subset of the standard Gum layout variables. The following lists the current set of layout variables available to every Forms control:
X
Y
Width
Height
Games which need to modify the layout in code may benefit from access to the full set of Gum layout variables:
X
XUnits
XOrigin
Y
YUnits
YOrigin
Width
WidthUnits
Height
HeightUnits
To access the full set (units and origin values), the Gum object must be accessed.
The BindingContext property is used for all data binding. This tutorial introduces the BindingContext property and creates a simple example using BindingContext to control the UI.
For this tutorial we will create a Gum screen which contains the following:
Points Display
Health Bar
Button for awarding points
Button for taking damage
Button for healing
Normally the actions performed by the buttons listed above would occur through regular game logic (such as collision) but we will use buttons for the sake of simplicity. The specifics of the visuals do not matter, so your screen may look like the following:
A few details in the screen above are important:
The three buttons are FlatRedBall.Forms Button objects. In other words, the three buttons are created in Gum using components which implement the Button behavior. The most common of these is ButtonStandard.
The HealthBar (ColoredRectangle) is contained within a HealthContainer (Container) using the Percent Width Units
Each object in the screen is descriptively named. This will make data binding easier.
The screen is called GameScreenGum. This is the default Gum Screen name for a FlatRedBall Screen called GameScreen. If you have created a game with a GameScreen using the wizard, you will also have a GameScreenGum
The first step in binding to your UI is to create a ViewModel object. ViewModel objects contain all of the properties which you would want to control on your UI programmatically. For the screen above, we will control the following properties:
The string displayed by the PointsTextInstance
The string displayed by the HealthTextInstance
The width of the HealthBar
The ViewModel class should implement the INotifyPropertyChanged interface. Fortunately, FlatRedBall provides a convenient class for this. View models are plain C# classes that are created in Visual Studio. We recommend creating a ViewModels folder in your project, but you can organize them however you like. The following code is an example of what the ViewModel class might look like:
The base ViewModel class provides common functionality for MVVM implementation - specifically the Get and Set functions and the DependsOn attribute. Using the ViewModel base class is not required, and any INotifyPropertyChanged implementation will work.
The Get and Set functions provide a quick way to implement notification to the UI. When the Set function is called (such as when Score is assigned), internally the Score value will be stored, and any object watching for changes to the Score variable will be notified. Furthermore, any object watching properties which depend on Score will also be notified.
The DependsOn attribute creates a dependency relationship between the property which has the attribute (such as ScoreDisplay) and the property which it depends on (such as Score). Once this dependency is established, changing the Score property will also notify the UI that ScoreDisplay has changed. Notice that a single property can depend on multiple properties, as is the case of HealthDisplay depending on both CurrentHealth and MaxHealth.
The ViewModel establishes which properties can be assigned, and the dependency between properties. Once a ViewModel is created, it can be applied to a Gum object. Note that ViewModels can be applied to Gum objects (GraphicalUiElements) or Forms objects (such as Button). BindingContext assignments cascade - you only need to assign the BindingContext at the top level and all children will recursively receive the same BindingContext.
Typically the BindingContext is done at the root level. We recommend assigning the Forms object which is available if you are using Forms in your game. Once the BindingContext is assigned, each individual UI property needs to be bound to the corresponding ViewModel property. The following code shows how this type of binding would be done:
Note that the above code assigns the binding on Gum objects through GumScreen. Although we're not doing it in this tutorial, we could also call SetBinding on Forms objects. For example, if the ViewModel had a property which was to be used as the Text on one of the buttons, the following code could be used:
This topic is covered in more detail in the next tutorial.
The ViewModel object is defined at GameScreen class scope. This is required so that the game can make modifications to the ViewModel after the screen is initialized. FlatRedBall recommends that the ViewModel property is named ViewModel to standardize code across different pages and views.
Once the ViewModel is created, we assign the BindingContext. This assignment tells the GumScreen and everything inside of the GumScreen to use this as its BindingContext. This assignment will also assign the BindingContext on any Forms objects in your screen, so you only need to do this assignment on the GumScreen. Once the BindingContext is assigned, the code establishes individual bindings between UI properties and ViewModel properties. This binding creates an automatic connection between the ViewModel property to the UI property, so that any changes to the ViewModel will automatically update the UI (assuming the Get and Set functions and DependsOn are written correctly in the ViewModel). Notice that the code uses the nameof keyword in C#. The code above could also be written as shown in the following snippet:
While this is less verbose, it produces code which is easier to break. Using nameof provides compile-time checks against referenced properties.
Once the binding is set up, assigning properties on the ViewModel will automatically update the UI. For example, the values above initialize the score and health as shown in the following screenshot:
The ViewModel properties can be updated at any time. Doing so automatically updates the bound UI. For example, the following code can be used to modify the properties in response to button clicks:
Notice that the click events on the buttons do not directly access any UI elements - only the properties on the ViewModel. This makes it much easier to maintain the UI and to continue to add dependencies.
Data binding is the process of controlling the appearance and behavior of UI through a separate object usually called a view model. The data binding pattern used by FlatRedBall.Forms is referred to as Model View ViewModel, abbreviated as MVVM. Developers familiar with data binding in WPF, Xamarin.Forms, or Knockout JS will find data binding in FlatRedBall.Forms familiar.
Binding can be performed on FlatRedBall Forms objects similar to binding on other UI frameworks such as WPF and Xamarin.Forms. This tutorial provides a binding example using Button, TextBox, and ListBox instances.
This tutorial uses a Glue screen which contains the following:
TextBoxInstance (TextBox)
AddButton (Button)
ListBoxInstance (ListBox)
RemoveButton (Button)
These instances are all default FlatRedBall.Forms objects which are available in all FlatRedBall projects created with the wizard.
Like the previous tutorial, this tutorial uses a ViewModel for binding. The ViewModel will need properties to bind to the following UI properties:
The string displayed in the text box.
Whether the Add button is enabled. It is enabled only if there is a string in the text box.
The list of objects in the list box.
The selected item in the list box.
Whether the Remove button is enabled. It is only enabled if there is a selection in the ListBox.
Not only will we be binding to FlatRedBall.Forms objects, but the binding will be two way binding. This means that the UI objects can make changes to the view model. For example, when the user clicks on an item in the list box, the SelectedItem is updated automatically.
The ViewModel includes a number of properties which will be controlled by the UI elements:
TextBoxText
SelectedItem
Notice that although these are controlled by the UI rather than code, they are written identical to other ViewModel properties. Also, they can be used as dependency properties for properties like IsAddButtonEnabled and IsRemoveButtonEnabled. In other words, the binding properties and dependency attributes will result in the add and remove buttons automatically enabling and disabling themselves according to properties that they depend on.
The ObservableCollection is used as the type for the ListBoxItems. This enables the ListBox to react to any items being added or removed. Whenever the ListBoxItem's Add or Remove methods are called, the ListBox will create ListBoxItems automatically. Notice that the ListBoxItems property has a private setter - there is no reason to make this public since the ObservableCollection will never change after it has been created.
The ViewModel can be created in the FlatRedBall screen class, just like in the previous tutorial. Binding to FlatRedBall.Forms object properties is done the same way as binding to Gum objects - by using the SetBinding method.
Like the previous tutorial, the add and remove buttons do not access any properties on the UI elements - only properties on the ViewModels are accessed.
Notice that the BindingContext is assigned on the Forms object. The Forms.BindingContext property is the same as the GumScreen.BindingContext property, so either can be assigned.
Often times games include custom reusable Forms components. These components may have dedicated ViewModel classes which can also be reused across multiple screens. This walkthrough shows how to create a reusable component, a reusable ViewModel, and how to assign the binding context appropriately.
For this view we will have a simple component which could be used for a game's settings. Note that if you actually need to create a view for settings, you should review the SettingsView control as a starting point. We'll be creating our own for this tutorial to keep it shorter.
Keep in mind the contents of this control are purely to serve as an example. The actual contents of your control could vary. For this example, consider the following control:
This control has two sliders and a checkbox, so our ViewModel should be built to support these controls. The following is an example ViewModel for this control:
To make the CustomSettingsView forms control reusable, we can set the binding on each of the controls in its custom code. To do this:
Open your project in Visual Studio
Open the CustomSettingsViewRuntime.cs file, which is located in the GumRuntimes folder. Note that if you added the control to a subfolder, you will find the runtime code in a subfolder as well.
Add the following code to CustomInitialize:
Note that this does not actually instantiate the CustomSettingsViewModel - it simply sets the binding so that the contained Forms components (sliders and checkbox) will be bound once the BindingContext is assigned. It is up to the parent container or screen to instantiate and assign the ViewModel.
Now our CustomSettingsViewRuntime is set up to bind its contained object's properties to a ViewModel's properties. Next we need to assign the view model.
This assignment can happen either directly by setting the BindingContext, or it can also be done by binding the BindingContext property. We'll cover both scenarios below and discuss why you might want to use one approach or another.
The simplest approach is to directly assign the BindingContext to an instance of the CustomSettingsViewModel.
For this example we'll consider a FlatRedball Screen called MainMenu which has an instance of the CustomSettingsView in its Gum screen. Even though we won't create it in this tutorial, the MainMenu may also have a corresponding MainMenuViewModel which would be assigned to Forms.BindingContext. We can override the BindingContext on the contained CustomSettingsViewInstance as shown in the following code:
This approach will correctly assign the BindingContext to the CustomSettingsView, but it does require explicit assignment of view models at the Screen level. The next section shows how to set up binding in a way that does not require multiple assignments at the screen level.
The BindingContext property is itself bindable. For simple situations this is not used very frequently, but if you have more advanced UI you may benefit from having binding context. For example, consider a situation where the CustomSettingsView is a child of another view, such as a PauseMenu.
In this situation, the PauseMenu may be part of the GameScreen and have its own binding context. Of course, it would be possible to access the internals of the PauseMenu in the GameScreen as shown in the following code:
This code begins to create a maintenance problem - if the Screen must explicitly assign all children BindingContext properties, then the children (PauseMenu) reusability goes down. Rather, we would want the PauseMenu to be responsible for assigning all of its children's binding context.
Therefore, we could modify our code to support this.
The first step is to modify the PauseMenuViewModel (the ViewModel of the parent component) to contain a CustomSettingsViewModel as shown in the following code:
The next step is to modify the PauseMenuRuntime's code to set the CustomSettingsView's BindingContext. As mentioned above, when CustomInitialize is called the view will not yet have a BindingContext assigned. Therefore, we cannot directly assign the CustomSettingViewInstance's BindingContext, but must bind to it:
Now, the GumScreen does not need to directly assign the CustomSettingsViewInstance's BindingContext. The code has become simpler:
By assigning the PauseMenu's BindingContext, the internal CustomSettingsViewInstance looks for a property on the PauseMenuViewModel with the name "CustomSettingsViewModel. Since the PauseMenuViewModel has this property, this will automatically be bound.
For this particular example it may seem like a pointless tradeoff - the binding context assignment was removed from the GameScreen, but additional code was added to the PauseMenuRuntime and PauseMenuViewModel. This extra code becomes more valuable if the PauseMenu (or any other container) were to be reused across multiple screens.
Notice that the code above has reduced the explicit assignments from three to two. We could further reduce this code by moving the PauseMenuViewMode inside the GameScreenViewModel and similarly binding the PauseMenu's BindingContext inside of the GameScreenGumRuntime. The amount of embedded ViewModel assignment you perform depends on the level of reusability your game needs.
FlatRedBall.Forms is a library which provides a full set of UI functionality similar to other UI libraries like Windows Forms, Xamarin.Forms, and WPF.
FlatRedBall.Forms is intended to mimic the syntax of WPF while allowing you to customize the visuals and layout of your UI using the Gum UI tool. Properly using FlatRedBall.Forms requires at least a basic understanding of working with Gum. Developers who are not familiar with Gum should first visit the Gum tutorials, which can be found here:
FlatRedBall.Forms is an easy-to-use UI library for games. This tutorial will walk you through setup of a game project including FlatRedBall.Forms.
First we'll create a Glue project. If you already have a Glue project you can skip this section. To create a new project:
Open Glue
Select File -> New Project
Enter your project name and click Create Project!
On the wizard, pick Standard Platformer, Standard Top Down, or Ui - any of those three will add FlatRedBall.Forms to your project.
Now we have a project that is ready to go. Note that if you already have an existing FlatRedBall project, you can continue on the next step.
If your project does not have Gum (such as if you skipped the wizard), you can it at any point. To do so:
Click the Gum icon in Glue to add a new Gum project
Note that if you already have a Gum project, this icon will open the file in Gum
Click the option to include forms
If you either created your project through the wizard, then all FlatRedBall Screens have an associated Gum screen. In other words, you can start adding FlatRedBall.Forms to any existing screen. You can verify that your FlatRedBall Screens have an associated Gum screen by checking the Files folder and finding a file with the .gusx extension. For example, the GameScreen has an associated Gum screen in the following image.
If you open Gum, it will have a GameScreenGum screen.
To add a Button to your GameScreen:
Expand the Components -> Controls folder
Drag+drop the ButtonStandard component onto the GameScreenGum or MainMenuGum (depending on which project type you created)
The Button should now appear in the Gum.
Running the game will also show a fully-functional button.
If you have used Gum before (or if you have read the Gum tutorials), you may be wondering about the difference between Gum and Forms. A deep dive into this topic would be too long for this tutorial, but we will briefly look compare Gum and Forms using the project we just created earlier in this tutorial. As mentioned earlier, FlatRedBall.Forms is a UI library which provides common UI logic. For example, notice that our button instance automatically reacts to a moue hover and click. We didn't have to write any code to tell it to change its appearance. Technically FlatRedBall.Forms is a collection of classes which can modify Gum objects in response to a variety of interactions such as mouse hover, keyboard actions, an clicks. Gum components may or may not be represented as Forms objects at runtime depending on their behaviors as defined in Gum. All Forms objects have a backing Gum object, which is stored in their Visual property. By contrast, not all Gum objects are wrapped by Forms objects.
To help understand the difference, let's take a look at the Gum button components. The default Gum project contains multiple Controls which begin with the word "Button".
If any of these components are added as instances to a screen, they will be represented by the Button forms type. We can see this by adding an instance of each to our Screen and running the game. For example, consider the following screen:
Each instance in this screen is a different component type: ButtonClose, ButtonConfirm, and so on. However, if we access these buttons at runtime through the Forms property, they are all of type Button.
We can even go to the definition of our Forms object and see that all Buttons are of the same type.
Not all Gum components are represented as Forms controls. For example, the default Gum project contains a component named Icon which can be used to display one of a collection of standard icons in games.
If we add an instance of our Icon to GameScreen, we can see that the Forms object does not contain a property for IconInstance.
Note that Visual Studio's auto complete does not provide a suggestion for IconInstance (but it does provide a suggestion for ButtonIconInstance, which is an instance of a Button).
We can still access the icon through the GumScreen property, which provides typed access to all Gum instances whether they are forms or not.
Ultimately, FlatRedBall decides whether a component should be in Forms or not depending on the Behaviors that the component implements. For example, all of our Button components implement the Button behavior.
The presence of this behavior is all that is needed to mark a component as a Forms object. You can browse other components such as TextBox and ListBox to see the behaviors they implement as well. If you would like to create custom components which should be treated as Forms objects, you can add these behaviors to your controls as well.
The ListBox type includes the ability to customize individual items using templates. Two properties exist on the ListBox for customization:
VisualTemplate - this allows the usage of any Gum object as an item in a list box
FrameworkTemplate - this allows the usage of a FrameworkElement as an item in a list box. Note that the type of FrameworkElement must inherit from ListBoxItem
The following screenshot shows an example of a customized ListBoxItem. It contains a list box which displays levels with images and a check box.
Although the appearance of each item in the ListBox is different from the default appearance, the ListBox itself is still a standard ListBox with a custom VisualTemplate. VisualTemplates can be used for any type of modification to the ListBoxItem. Common examples include:
Adding additional Text instances for more information, such as the name and price of an item as separate Text instances
Adding images or icons to an object, such as a preview image in a level selection list box
Adding additional controls to a list box, such as a check box for multi-selection
Most of the time games only need a VisualTemplate and do not need to use a FrameworkTemplate. Therefore, this tutorial convers the usage of VisualTemplate which should cover almost all cases. FrameworkTemplate can be used for rare situations where the same visual is used for list box items which have different functionality. Most of the time this different behavior can be achieved with a VisualTemplate and data binding.
By default the ListBox creates one ListBoxItem for every instance in its Items property. The ListBoxItem is defined in the Gum project under the Controls folder as shown in the following screenshot:
The first step in replacing this ListBoxItem in our ListBox is to create a new component in Gum. The easiest way to do this is to copy/paste the existing ListBoxItem as a starting point. You can create a copy by using the CTRL+C, CTRL+V hotkeys or by right-clicking on the component and selecting the Duplicate option.
This component can be structured however you want. There are no requirements for what it must contain, how it must be named, or what states it must contain. By copying the existing ListBoxItem, we bring over the ability for the ListBox to be selected. Even this is optional, so if you do not want your ListBoxItem to respond to selections visually, you can remove the states or modify them to have no impact on appearance.
For this tutorial we will be creating a new ListBoxItem which has two Text instances:
InventoryNameTextInstance
InventoryCountTextInstance
Note that by default ListBoxItems attempt to assign a Text object by the name of TextInstance. By changing our Text names, our new ListBoxItem no longer supports default Text display. If you are creating a new ListBoxItem which is intended to be used as a default ListBoxItem throughout your entire game, you should consider keeping a Text instance with th ename TextInstance. In this case we are creating a ListBoxItem to be used in a specific case so this name isn't required.
To use the newly-created ListBoxItem, first you need a screen with a ListBox. For example, the following screenshot shows a single ListBox in the MainMenuGum screen.
Once this ListBox is added, the following code can be used to populate the ListBoxInstance, including using the new InventoryListBoxItem as the template:
Running the game shows something similar to the following screenshot:
The ListBox contains ten (10) instances of the InventoryListBoxItemRuntime Gum component - one for each integer added to listBox.Items. The VisualTemplate assignment tells the ListBox that each Item should result in a new InventoryListBoxItemRuntime being created.
The code above shows how to create instances of the InventoryListBoxItemRuntime component based on the contents of the ListBox.Items collection. This approach is good for testing how a custom ListBoxItem appears in your ListBox, but it won't work for a full game.
Typically each item in a ListBox needs more information than an integer. In the case of inventory, each item in the list box needs at least a name and count.
We'll create a new class which contains this information. We also want to create a class which will notify the ListBoxItem any time a property changes. To make this simpler, FlatRedBall provides a ViewModel
class which can be used as the base for your custom classes. By using ViewModel, and the Get/Set methods, any changes to properties are automatically broadcasted to UI. In other words, this enables changing a property on the ViewModel-inheriting class, and that updating the UI immediately.
The term "ViewModel" comes from the MVVM pattern which is a common way to display and manage data in C# front ends such as WPF, .NET MAUI, and Avalonia. Classes which inherit from ViewModel are often referred to generally as "view models", and should have the "ViewModel" or "VM" suffix for clarity.
We can create a new ViewModel for our new custom list box items as shown in the following code:
Each InventoryItemViewModel will correspond to an individual InventoryListBoxItemRuntime. We also need to create a ViewModel for the entire screen. This ViewModel contains a list of InventoryItemViewModels in an ObservableCollection. Our ViewModel for the entire screen should match the name of the screen, so we will create a class called MainScreenViewModel.
Finally we can modify our MainMenu.cs class to use the MainScreenViewModel for its items, as shown in the following code:
If we run our game now, the ListBox shows four items (one for each InventoryItemViewModel instance). Notice that we do not directly add items to the ListBox.Items. Instead, we bind the listBox.Items to the viewModel.ListBoxItems. This results in the ListBox automatically keeping in sync with the ViewModel.
We can observe this behavior by adding code to the MainMenu's CustomActivith to add a new item whenever the space bar is pressed.
Note that removing items from the view model also results in removal of the matching item in the ListBox.
Next we'll update our binding so that the Text instances in our InventoryListBoxItem display the name and count properties from our InventoryItemViewModel. To do this, open InventoryListBoxItemRuntime.cs and modify the contents as shown in the following code.
Now if we run our application, each item displays the information from each InventoryItemView.
Next we'll keep track of the selected item. We can do this by creating a new property on the MainScreenViewModel which has
We can bind to the SelectedItem in MainMenu.CustomInitialize. Modify the code to add the "New" code shown here:
Just like all other properties, the SelectedObject/SelectedItem properties stay synced, so we can use the ViewModel's SelectedItem in code. For example, we can modify CustomActivity to increase inventory on the selected item when the Enter key is pressed:
If we run the code, select an item, and press Enter, the inventory count increases.
This tutorial shows how to bind a ListBox and ListBoxItems to view models. Interacting with the view models (adding or removing items, modifying properites on the view models) automatically update the view.
This section discusses how to add new element types to FlatRedBall.Forms. This section is for maintainers of FlatRedBall who would like to add new types to be used across all projects and to be included in new projects created with the wizard.
The high-level steps for creating a new Forms object are:
Define the class in FlatRedBall.Forms shared code project
Identify the required and optional Gum objects for your new control
Create a new Gum component in the embedded Gum project in the Gum plugin
Add a behavior for the new control
Implement the new behavior on the new component
Update FormsControlInfo
and GetIfIsCompleteFulfillment
to include your new behavior and control
Test the new control in a new project
You can verify that the Gum plugin code generation logic has picked up on the new behavior by checking a game's GumIdbExtensions
class. for the presence of your new control.
FlatRedBall.Forms is a logical wrapper over standard Gum objects. As such, the Gum objects can be modified in almost any way to fit the styling needs of your game. This document covers the default FlatRedBall.Forms controls and how to make modifications.
FlatRedBall.Forms provides flexibility in regards to Forms component structure, but some objects and states are required so that a Forms object can behave properly.
To identify the requirements you can look at the Forms behaviors in the Behaviors folder in your Gum project.
These behaviors are automatically added to your Gum project by the FlatRedBall Editor when you add a Gum project. If you used the FlatRedBall Wizard to create your project, these are also added automatically.
These behaviors indicate the requirements that a Forms control has. For example, ButtonBehavior requires that a ButtonBehavior-implementing Component must have a category called ButtonCategory, and that it must contain states with specific names.
If you create a Component which satisfies these behaviors, then your component can be used as a FlatRedBall.Forms button.
Some behaviors have additional requirements such as required objects. For example, the ComboBoxBehavior requires a category called ComboBoxCategory which contains a number of states, but it also requires two objects - a ListBoxInstance and a TextInstance.
If you modify any existing component which implements these behaviors, make sure you do not remove or change the name of any required categories, states, or instances.
In general, you will almost always be safe making the following modifications:
Changing variables such as position, size, or color
Modifying values assigned in states
Adding additional instances to a control, such as adding an icon Sprite to a ListBoxItem
Renaming components
The following modifications may introduce problems depending on if they are made on requirements:
Changing the name of states
Changing the name of instances
Changing the type of instances to incompatible types, such as changing an instance from Text to Sprite
Removing behaviors - you can do this if you would like the component to no longer be associated with FlatRedBall.Forms
Projects created through the FlatRedBall Wizard, or which have added Forms when creating a Gum project, should contain a default implementation of FlatRedBall.Forms controls in the Gum project. These can be found in the Components/Controls and Components/Elements folders. Note that as FlatRedBall.Forms is developed, new controls are created, so this list will change over time.
These forms controls are built to support a common, centralized style. You can make changes at the base level so that your styling changes apply to the entire project, or you can choose to modify individual components one-by-one without changing the global style.
To see how the styling is implemented, we'll look at one of the most common controls: ButtonStandard.
As mentioned earlier, a Gum component must implement a Forms behavior to be considered a Forms object. We can confirm that this is the case by checking the behaviors on ButtonStandard in the Behaviors tab.
This means that the ButtonStandard is required to have a ButtonCategory which contains states for displaying the button depending on whether it is enabled/disabled, highlighted, and pressed.
As mentioned above, we should not delete or rename these states, but we are free to make changes to the states to modify the styling so it matches our game.
Selecting a category in Gum shows the variables that are modified by all states in the category. In this case, the ButtonCategory modifies
Background.ColorCategoryState
FocusedIndicator.Visible
TextInstance.ColorCategoryState
If we want to change the state, we can make additional changes to the state by selecting it and modifying variables. For example, we may want to make the Text larger when the cursor is highlighting the button. To do this:
Select the TextInstance
Select the Highlighted state
Change FontScale from 1 to 1.5
The FontScale variable shoudl display with a white background instead of green to indicate that it is explicitly set on this state.
Note that the FontScale variable is now modified by all states in the category, not just the one that you set. All of the other states explicitly set the variable to the default of 1. You can verify this by clicking on the ButtonCategory.
Since these states are used at runtime automatically, we can run the game and see the font size change when the button is highlighted. Notice that the button text shrinks back to its normal size when the cursor moves off of the button. The text also shrinks when the button is pushed since we didn't modify the Pushed state.
In this case our modification to the button added a new variable that was previously unmodified by ButtonCategory. We can also change existing values. For example, when highlighted our Button's background uses the PrimaryLight ColorCategoryState.
In this case, the ColorCategoryState is defined on NineSlice, which is the base type for Background. Therefore, we should not modify the Red, Green, or Blue values on the Background object because they will be in conflict with the values assigned by this state.
If we would like to modify the Background color in the Highlight (or any other) state, we have a few options:
We can go to the NineSlice standard object and modify the PrimaryLight state. Be careful, making modifications here is concpetually similar to making modifications to a style sheet which is used across an entire project. If you would like to change this value globally, feel free to do so, but realize that making changes to the NineSlice styles can modify many components in your project.
Change the state value that is being used on the background. Although this is not recommended, you can change the background to use a different ColorCategoryState, such as changing the value to Success. This is not recommended because the ColorCategoryState states are named to indicate where they should be used throughout your project, and switching to different ColorCategoryStates may result in confusing the user by using the same color for different behaviors and states.
Add a new state to NineSlice for your specific case. This is useful if you have a case that isn't handled by the existing NineSlice category states.
Removing the usage of ColorCategoryStates from your ButtonCategory. The states in the NineSlice exist to make global styling easier, but they are not a requirement. If you would prefer to implement your own styling method, such as direct RGB values on the background, that's okay. Keep in mind that the Background NineSlice isn't even a requirement! You can remove it and replace it with something else like a Sprite if that works better for your game.
As mentioned above, using Gum to style your FlatRedBall.Forms controls provides a lot of flexibility. The existing structure exists to make changes easier to implement, but you are free to experiment and find your own preferred styling.
This tutorial shows you how to access FlatRedBall.Forms controls in code to perform common logic like assigning event handlers and accessing properties.
The previous tutorial showed how to create a new project with a Button control. Most controls require no additional code to work - just drag+drop the control into a screen and it will have full functionality. For this tutorial we'll create an instance of the following controls:
Button (multiple versions exist, but ButtonStandard is the most common)
CheckBox
ListBox
TextBox
All controls are present in the Components/Controls folder.
To create these controls, drag drop them into your current screen, such as GameScreenGum or MainMenuGum. If you have a GameScreen, but would like to create a screen with only UI, you can add a new MainMenu screen to your FlatRedBall project.
You can drag+drop controls into your screen. The following screenshot shows a Gum screen with four controls:
Now we can access all of our controls from the Gum screen in code. Every Gum object can be accessed in its respective screen using the Forms
object. For example, to add a click event to the button:
Open the project in Visual Studio
Go to your screen's code file (GameScreen.cs or MainMenu.cs, for example)
Add the following code to the GameScreen:
This results in the button updating its text to indicate when it was last clicked:
Let's take a look at a few parts of the code. First, we should note that the code above is all compile-time protected. This means:
Any changes in the Gum project may result in a compile error, notifying you that something has to change in the code. For example, we are accessing the ButtonStandardInstance. If this object is removed or renamed, your code will not compile.
Visual Studio provides intellisense (code completion) to help you fill in the code
In the code above we accessed the button through the Forms.ButtonStandardInstance property. Note that ButtonStandardInstance is the same name as the object in Gum.
Notice that we are accessing the object through Forms allows us to interact with the Gum object casted as a FlatRedBall.Forms Button. Once we have access to the button we can interact with it in a standard way, such as by assigning a click event or by setting its Text property.
The CheckBox control can be used to allow the user to set true/false values. Just like with Button , before we'll get a reference to the CheckBox through the screen.
Clicking the CheckBox results in the value being printed to the screen.
The ListBox control is used to display options or a collection of current items to the user (such as active quests). The following code adds items to the list box whenever the user presses a key on the keyboard. Note that this code requires code in CustomActivity to read input from the keyboard.
Typing the A, B, or C characters on the keyboard results in items added to the list box:
The TextBox control provides free-form input support for users to enter string values like a character's name. Note that at the time of this writing the TextBox relies on the MonoGame key bindings which do not consider different keyboard configurations (such as languages other than English). The following code can be used to react to any changes in the TextBox Text property and print it out to the screen.
Note that the TextChanged event will be raised for each new character (including spaces) or whenever a character is deleted.
Your game may require subscribing to various Forms for every instance of a particular type of Forms object. For example, you may want to play a sound effect whenever a Forms button is clicked. Subscribing to every instance of every Forms object can be difficult to maintain. Instead, we can create events inside the Gum runtime classes to handle behavior for every instance.
Every Forms object is backed by a Gum "runtime" object. For information about Forms vs Gum, see the tutorial. For example, the Button class has a number of types of Gum runtimes:
ButtonClose
ButtonConfirm
ButtonDeny
ButtonIcon
ButtonStandard
ButtonStandardIcon
ButtonTab
We can tell that this is the case by looking at the behaviors for each of these controls.
Note that while a full implementation may require handling each of the Runtime classes listed above, for simplicity this tutorial discusses only ButtonStandard.
Every runtime has a code file - even runtimes which correspond to built-in Forms types such as Button. For example, all of the Button Gum components have corresponding Runtime classes in Visual Studio.
Each runtime provides a CustomInitialize method which is similar to CustomInitialize on Screens and Components. The CustomInitialize is called once when a new instance of our component is created. This code runs regardless of how our Gum runtime is created. This includes if:
The component is added to a Gum screen in the Gum tool
The component is instantiated by calling its constructor in custom code. For example, calling new ButtonStandardRuntime();
The component is instantiated through its corresponding Forms control. For example, calling new Button();
Therefore, we can add code to CustomInitialize to perform custom logic on all of our Button instances.
To respond to whenever a Button is clicked, we can modify our ButtonStandardRuntime as shown in the following code:
This code results in printing "Button clicked" when any button (with a backing ButtonStandard Component) has been clicked.
Again, remember that the Button in Forms is a special type of control becuase it has multiple components handling its implementation, so you may need to add similar code to the other Runtime classes.
For more information on the specific syntax of ViewModels (such as Get/Set and DependsOn), see the page.
Besides requirements from categories, some FlatRedBall.Forms can use optional instances if they exist. These are not required. For example, the Button component can optionally include an instance called TextInstance. Since this is an optional requirement, the Behavior does not include it as a requirement. However, you can find out more about optional requirements by looking at the .