Troubleshooting Events
Introduction
Gum Forms controls provide built-in responses to user actions such as clicks, typing, and dragging. These responses may be purely visual — such as a Button highlighting when the cursor moves over it — or they may raise events.
If a control is not responding to input, its events may be suppressed for a number of reasons: the control (or one of its parents) could be invisible, disabled, outside the cursor's position, covered by a sibling, blocked by a modal, or have a parent that is not exposing its children's events.
Rather than walking the visual tree by hand, Gum exposes a diagnostic extension method on Cursor called GetEventFailureReason that returns a human-readable string describing why events are not being raised — or null if events should be working.
An Empty Container Can Still "Cover" a Sibling
ContainerRuntime defaults to HasEvents = true, so even one with no visible content or handlers of its own still claims the cursor over its entire bounds — including empty space — and can block a sibling behind it. If a container only groups or positions other controls, set HasEvents = false on it so clicks pass through to what it actually contains.
Quick Check
If a control is not responding, paste one of the following into your update loop, put a breakpoint on the line, and hover the control while the game runs. Inspect reason: if it is null, events should be working; otherwise the string tells you exactly what is blocking them.
The GetEventFailureReason extension methods live in the MonoGameGum.Input namespace, so add this using directive:
using MonoGameGum.Input;When you have a single control of a given type, look it up by type — no field or name required:
// Update
GumUI.Update(gameTime);
var reason = GumUI.Cursor.GetEventFailureReason<Button>();
// reason == null → events should be working
// reason != null → reason describes what is blocking eventsWhen you have several controls of the same type, give the one you are diagnosing a Name and look it up by name:
That is usually all you need. The rest of this page covers the remaining overloads, how to read the diagnostic output, and what to do when the cursor's position does not match the rendered UI.
GetEventFailureReason
GetEventFailureReason lives on the ICursor interface (typically GumService.Default.Cursor) and comes in several overloads:
GetEventFailureReason<T>()
You have exactly one control of a given type (e.g. a single ComboBox). No field reference required.
cursor.GetEventFailureReason<ComboBox>()
GetEventFailureReason(string name)
You named the control and want to look it up by name.
cursor.GetEventFailureReason("ConfirmButton")
GetEventFailureReason<T>(string name)
You have multiple controls of the same type and want to disambiguate by name.
cursor.GetEventFailureReason<Button>("Save")
GetEventFailureReason(FrameworkElement)
You already have a reference to the control.
cursor.GetEventFailureReason(myButton)
GetEventFailureReason(InteractiveGue)
You want to diagnose a raw visual rather than a Forms control.
cursor.GetEventFailureReason(myVisual)
Because the cursor's position affects the result, call this method in your update loop while attempting to interact with the control.
Looking up a control by type
The type-based overload is the easiest starting point — it searches Root, PopupRoot, and ModalRoot recursively for a control of the requested type, so you do not have to promote the control to a field or navigate the visual tree yourself.
Because the button is invisible, the output identifies the problem:
Looking up a control by name
When you have more than one control of the same type, assign each one a Name and look it up by name:
Names also make the diagnostic output more readable, since the message uses the name when one is set:
Combining type and name
For the common case where multiple controls share a name across different types (for example, a Button and a Label both named Save), combine the type and name overloads:
When multiple controls match
If the lookup matches more than one control, GetEventFailureReason runs the diagnostic on each match and returns a combined report. This lets you see at a glance which instance is actually failing, rather than having to rerun the call with a narrower filter.
If no controls match, the return value explains that too:
Passing a control reference directly
When you already have a reference to the control (for example, from a field or a generated partial class), pass it directly. This is the original form of the API and is still the most direct when a reference is convenient.
Reading the Diagnostic Output
For failures that depend on the control's position in the visual tree, GetEventFailureReason appends an ancestor tree so you can see the hierarchy and which element is responsible. The responsible element is marked with <---- THIS:
In the example above, the NineSliceRuntime has its ExposeChildrenEvents property set to false, so its descendants (including the button) cannot receive events. The tree makes it clear which ancestor to fix, even when the control is nested several levels deep.
Controls used as Forms visuals often want ExposeChildrenEvents = false so the control absorbs clicks from its own children (e.g. a Button treats clicks on its inner text as clicks on the button). The diagnostic only flags ancestors, so these intentional cases do not produce false positives.
Detecting Which Control is Under the Cursor
You can inspect the cursor's VisualOver property to see which control the cursor believes it is over. This is useful when the diagnostic suggests the wrong control is intercepting input. The following snippet writes the current visual under the cursor to the output window each frame:
If you want the matching Forms control rather than the raw visual, use FrameworkElementOver:
Cursor Position Does Not Match the Rendered UI
If VisualOver reports the wrong control — or null when the cursor is clearly over a control — the cursor's coordinates may not match where Gum is being drawn on screen. This commonly happens when the game renders Gum to a RenderTarget2D, draws it with a SpriteBatch translation/scale, or otherwise transforms the output.
The Cursor class has a TransformMatrix property that offsets and scales cursor input to match the rendered UI. See TransformMatrix for an example that compensates for a translated SpriteBatch draw.
Last updated
Was this helpful?

