Gum Runtime Binding
SkillDev toolsGum runtime data binding, BindingContext, SetBinding on GraphicalUiElement visuals and FrameworkElement Forms controls, binding types (string, Binding, lambda), differences between the two systems.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Gum Runtime Binding skill
What this skill tells your AI
The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-runtime-binding/SKILL.md and read by ahel’s review.
Two Binding Systems
GraphicalUiElement (GumRuntime/GraphicalUiElement.Binding.cs) — basic binding available on all visuals.
FrameworkElement (MonoGameGum/Forms/Controls/) — richer binding on Forms controls, built on top of the GUE system.
FrameworkElement.BindingContext delegates to its Visual.BindingContext — they share one context.
BindingContext
Set on any GraphicalUiElement or FrameworkElement. Cascades automatically to all descendants unless overridden:
root.BindingContext = viewModel; // all children inherit it
child.BindingContext = other; // explicit overrides inherited
Subscribes to INotifyPropertyChanged and updates bound UI properties on change.
GraphicalUiElement Binding (Visuals)
Simple string-only binding. No converters, no modes, no path traversal:
element.SetBinding("X", nameof(vm.Position)); // basic
element.SetBinding("Text", nameof(vm.Name), "{0:N0}"); // with format string
PushValueToViewModel() is called from property setters to write back to the VM (always two-way implicitly).
FrameworkElement Binding (Forms)
Three binding styles, all richer than the GUE version.
1. String-based
textBox.SetBinding(nameof(TextBox.Text), nameof(vm.Name));
Shorthand — wraps the string in a default Binding object internally.
2. Explicit Binding object
var binding = new Binding(nameof(vm.IsEnabled))
{
Mode = BindingMode.OneWay,
Converter = new BoolToVisibilityConverter(),
FallbackValue = false
};
checkBox.SetBinding(nameof(CheckBox.IsChecked), binding);
Binding properties: Path, Mode (OneWay/TwoWay/OneWayToSource), UpdateSourceTrigger (Default/PropertyChanged/LostFocus), Converter, ConverterParameter, StringFormat, FallbackValue, TargetNullValue.
3. Lambda / expression tree
// Typed (preferred — compiler-checked, extracts "Child.Text" path):
textBox.SetBinding<MyVm>(nameof(TextBox.Text), vm => vm.Child.Text);
// Parameterless closure:
textBox.SetBinding(nameof(TextBox.Text), () => vm.Child.Text);
Extension methods in FrameworkElementExt.cs. BinderHelpers.ExtractPath() walks the expression tree to produce a dotted path string, then creates a Binding normally. Nested paths (e.g. vm => vm.A.B.C) are fully supported via PropertyPathObserver.
Index-Based Binding (Forms only)
Paths support integer indexer access via [N] syntax. Works in string paths, Binding objects, and lambdas:
// String path
textBox.SetBinding(nameof(TextBox.Text), new Binding("Items[0].Text"));
// Lambda
textBox.SetBinding<MyVm>(nameof(TextBox.Text), vm => vm.Items[0].Text);
// Nested: index in the middle of a path
textBox.SetBinding(nameof(TextBox.Text), new Binding("Child.Items[1].Text"));
All binding features work with indexed paths: modes, converters, StringFormat, FallbackValue, LostFocus trigger.
Collection change notification: PropertyPathObserver subscribes to INotifyCollectionChanged on collections in indexed path segments. When items are added, removed, replaced, or cleared, the binding re-evaluates. Out-of-bounds indexes resolve to null (triggering FallbackValue if set). Currently reacts to ALL collection changes regardless of whether the specific bound index is affected — this is intentionally broad for correctness; a future optimization could filter by index relevance.
Limitations: Dictionary/string key indexing is not supported.
Implementation: BinderHelpers.ParseSegments() splits paths into PathSegment structs (name + optional int index). BuildGetter/BuildSetter emit indexer calls via Expression.MakeIndex or Expression.ArrayIndex. ExtractPath handles MethodCallExpression (get_Item) and IndexExpression nodes from lambdas. PropertyPathObserver uses GetIndexedValue() after property resolution for indexed segments.
Feature Comparison
| Feature | GraphicalUiElement | FrameworkElement |
|---|---|---|
| String binding | ✓ | ✓ |
| Explicit Binding object | ✗ | ✓ |
| Lambda binding | ✗ | ✓ |
Nested paths (A.B.C) | ✗ | ✓ |
Index paths (Items[0].Text) | ✗ | ✓ |
| Binding modes | Implicit TwoWay | Configurable |
| Converters | ✗ | ✓ |
| FallbackValue / TargetNullValue | ✗ | ✓ |
| UpdateSourceTrigger | Always PropertyChanged | Configurable |
Key Files
| File | Purpose |
|---|---|
GumRuntime/GraphicalUiElement.Binding.cs | GUE binding — BindingContext, SetBinding, PushValueToViewModel |
MonoGameGum/Forms/Data/Binding.cs | Binding config class + BindingMode + UpdateSourceTrigger + IValueConverter |
MonoGameGum/Forms/Data/NpcBindingExpression.cs | Forms binding engine — UpdateTarget, UpdateSource |
MonoGameGum/Forms/Data/PropertyPathObserver.cs | Watches dotted paths, re-hooks on intermediate changes, weak listeners |
MonoGameGum/Forms/Data/BinderHelpers.cs | Lambda path extraction, compiled getter/setter delegates |
MonoGameGum/Forms/Controls/FrameworkElementExt.cs | Lambda SetBinding extension methods |
GumRuntime/BindableGue.cs | Deprecated alias for GraphicalUiElement — do not use |
Non-Obvious Behaviors
BindableGue is deprecated. GraphicalUiElement now owns all binding logic. BindableGue exists only as a legacy alias.
Weak listeners in PropertyPathObserver. Forms binding uses weak references to avoid memory leaks on deep paths. GUE binding does not — callers should unsubscribe when disposing.
Lambda extracts path at call time, not at update time. vm => vm.Child.Text becomes the static path "Child.Text". If Child is replaced, PropertyPathObserver re-hooks the listener chain automatically.
ListBox Items is bindable. listBox.SetBinding(nameof(ListBox.Items), nameof(vm.Items)) works and keeps the list in sync with an ObservableCollection on the VM.
UpdateSourceTrigger.Default is project-configurable. Binding.DefaultUpdateTriggers lets a project register a default trigger per control type + property, resolved in NpcBindingExpression.HookUpdateSource before falling back to PropertyChanged. See Default Update Triggers.
Signals
- GitHub stars
- 620
- Forks
- 80
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gum-runtime-binding- Source
- github.com/vchelaru/gum