Skip to content

Commit 84434cd

Browse files
authored
Control documentation (#2555)
* Added more controls documentation Added TextBoxBase XmlDocs * Implemented 3-state clicking on CheckBox * Added PasswordBox documentation * Added IsDefault/cancel for wpf users
1 parent 13b8a69 commit 84434cd

10 files changed

Lines changed: 194 additions & 18 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Gum.Forms.Controls;
2+
using Shouldly;
3+
using Xunit;
4+
5+
namespace MonoGameGum.Tests.Forms;
6+
7+
public class CheckBoxTests : BaseTestClass
8+
{
9+
[Fact]
10+
public void CheckBox_TwoState_ShouldCycleFalseTrue()
11+
{
12+
var checkBox = new CheckBox();
13+
checkBox.IsThreeState = false;
14+
checkBox.IsChecked = false;
15+
16+
checkBox.Visual.CallClick();
17+
checkBox.IsChecked.ShouldBe(true);
18+
19+
checkBox.Visual.CallClick();
20+
checkBox.IsChecked.ShouldBe(false);
21+
}
22+
23+
[Fact]
24+
public void CheckBox_ThreeState_ShouldCycleFalseTrueNull()
25+
{
26+
var checkBox = new CheckBox();
27+
checkBox.IsThreeState = true;
28+
checkBox.IsChecked = false;
29+
30+
// Cycle 1: False -> True
31+
checkBox.Visual.CallClick();
32+
checkBox.IsChecked.ShouldBe(true);
33+
34+
// Cycle 2: True -> Null (Indeterminate)
35+
checkBox.Visual.CallClick();
36+
checkBox.IsChecked.ShouldBe(null);
37+
38+
// Cycle 3: Null -> False
39+
checkBox.Visual.CallClick();
40+
checkBox.IsChecked.ShouldBe(false);
41+
}
42+
}

MonoGameGum/Forms/Controls/PasswordBox.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,21 @@ protected override void SetTextFromNativeKeyboardInput(string value)
3737
}
3838

3939
SecureString securePassword = new SecureString();
40+
41+
/// <summary>
42+
/// Gets the password as a <see cref="SecureString"/>.
43+
/// </summary>
4044
public SecureString SecurePassword
4145
{
4246
get { return securePassword; }
4347
}
48+
49+
/// <summary>
50+
/// Gets or sets the password as a plain string.
51+
/// </summary>
52+
/// <remarks>
53+
/// For security-sensitive scenarios, consider using <see cref="SecurePassword"/> instead.
54+
/// </remarks>
4455
public string Password
4556
{
4657
get
@@ -80,6 +91,11 @@ String SecureStringToString(SecureString value)
8091
// Update Gum's default to include this first:
8192
//public char PasswordChar { get; set; } = '●';
8293
private char _passwordChar = '*';
94+
95+
/// <summary>
96+
/// Gets or sets the character used to mask the characters entered in the PasswordBox.
97+
/// The default value is an asterisk (*).
98+
/// </summary>
8399
public char PasswordChar {
84100

85101
get => _passwordChar;
@@ -95,6 +111,9 @@ public char PasswordChar {
95111
}
96112
}
97113

114+
/// <summary>
115+
/// Raised when the password changes.
116+
/// </summary>
98117
public event EventHandler PasswordChanged;
99118

100119
protected override string DisplayedText

MonoGameGum/Forms/Controls/TextBoxBase.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,19 @@ public int CaretIndex
156156
}
157157
}
158158

159+
/// <summary>
160+
/// A list of keys that should be ignored by this control.
161+
/// </summary>
159162
public List<Keys> IgnoredKeys => null;
160163

164+
/// <summary>
165+
/// Whether this control is currently capable of receiving input. Always true for TextBoxBase.
166+
/// </summary>
161167
public bool TakingInput => true;
162168

169+
/// <summary>
170+
/// The next control to receive focus when the user presses the tab key.
171+
/// </summary>
163172
public IInputReceiver NextInTabSequence { get; set; }
164173

165174
public override bool IsEnabled
@@ -179,6 +188,10 @@ public override bool IsEnabled
179188
}
180189
}
181190

191+
/// <summary>
192+
/// The text currently displayed in the text box. This may differ from the actual
193+
/// internal text (e.g. in a PasswordBox).
194+
/// </summary>
182195
protected abstract string? DisplayedText { get; }
183196

184197
TextWrapping textWrapping = TextWrapping.NoWrap;
@@ -349,16 +362,22 @@ public int? MaxLength
349362

350363
/// <summary>
351364
/// Whether pressing the tab key inserts a tab character. If true, then
352-
/// tab characters can be inserted.
365+
/// tab characters can be inserted and tab navigation (moving focus to the next control) is disabled.
353366
/// </summary>
354367
public bool AcceptsTab { get; set; } = false;
355368

369+
/// <summary>
370+
/// Returns true if tab navigation is enabled. This is the inverse of <see cref="AcceptsTab"/>.
371+
/// </summary>
356372
public override bool IsTabNavigationEnabled => AcceptsTab == false;
357373

358374
#endregion
359375

360376
#region Events
361377

378+
/// <summary>
379+
/// Raised when a controller button is pushed while the control is focused.
380+
/// </summary>
362381
public event Action<GamepadButton> ControllerButtonPushed;
363382
/// <summary>
364383
/// Raised before new text is inserted (by typing or pasting). Set
@@ -371,6 +390,9 @@ public int? MaxLength
371390
/// </summary>
372391
public event EventHandler CaretIndexChanged;
373392
protected void RaiseCaretIndexChanged() => CaretIndexChanged?.Invoke(this, EventArgs.Empty);
393+
/// <summary>
394+
/// Raised when the selection (SelectionStart or SelectionLength) changes.
395+
/// </summary>
374396
public event EventHandler SelectionChanged;
375397
protected void RaiseSelectionChanged() => SelectionChanged?.Invoke(this, EventArgs.Empty);
376398
protected virtual TextCompositionEventArgs RaisePreviewTextInput(string newText)
@@ -381,6 +403,9 @@ protected virtual TextCompositionEventArgs RaisePreviewTextInput(string newText)
381403
return args;
382404
}
383405

406+
/// <summary>
407+
/// The parent input receiver for this control.
408+
/// </summary>
384409
public IInputReceiver? ParentInputReceiver =>
385410
this.GetParentInputReceiver();
386411

MonoGameGum/Forms/Controls/ToggleButton.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,22 @@ public class ToggleButton : ButtonBase
2828

2929
/// <summary>
3030
/// Whether this toggle supports three states: checked (true), unchecked (false), and
31-
/// indeterminate (null). When false, clicking cycles only between checked and unchecked.
31+
/// indeterminate (null). When true, clicking the button cycles through Unchecked -> Checked -> Indeterminate.
32+
/// When false, clicking cycles only between Checked and Unchecked.
3233
/// </summary>
3334
public bool IsThreeState { get; set; }
3435

3536
private bool? isChecked = false;
3637

38+
3739
/// <summary>
3840
/// Gets or sets the checked state of the toggle. A value of <c>true</c> means checked,
3941
/// <c>false</c> means unchecked, and <c>null</c> means indeterminate.
4042
/// </summary>
4143
/// <remarks>
4244
/// Setting this property updates the visual state and raises <see cref="Checked"/>,
4345
/// <see cref="Unchecked"/>, or <see cref="Indeterminate"/> as appropriate.
44-
/// The indeterminate state is only reachable programmatically — clicking the button
45-
/// cycles between true and false regardless of <see cref="IsThreeState"/>.
46+
/// If <see cref="IsThreeState"/> is true, clicking the button cycles through all three states.
4647
/// </remarks>
4748
public bool? IsChecked
4849
{
@@ -60,15 +61,15 @@ public bool? IsChecked
6061
if (isChecked == true)
6162
{
6263
OnChecked();
63-
Checked?.Invoke(this, null);
64+
Checked?.Invoke(this, null!);
6465
}
6566
else if (isChecked == false)
6667
{
67-
Unchecked?.Invoke(this, null);
68+
Unchecked?.Invoke(this, null!);
6869
}
6970
else if (isChecked == null)
7071
{
71-
Indeterminate?.Invoke(this, null);
72+
Indeterminate?.Invoke(this, null!);
7273
}
7374

7475
PushValueToViewModel();
@@ -184,13 +185,22 @@ protected virtual void OnChecked()
184185

185186
protected override void OnClick()
186187
{
187-
if (IsChecked == true)
188+
if (IsThreeState)
188189
{
189-
IsChecked = false;
190+
if (IsChecked == false) IsChecked = true;
191+
else if (IsChecked == true) IsChecked = null;
192+
else IsChecked = false; // Indeterminate -> Unchecked
190193
}
191-
else // false or indeterminte
194+
else
192195
{
193-
IsChecked = true;
196+
if (IsChecked == true)
197+
{
198+
IsChecked = false;
199+
}
200+
else // false or indeterminte
201+
{
202+
IsChecked = true;
203+
}
194204
}
195205
}
196206
}

docs/code/about/for-wpf-users.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ For the full list of color roles and what they affect, see the [Styling Using Ac
681681
| `Image` | `SpriteRuntime` |
682682
| `TextBlock` | `TextRuntime` |
683683
| `Rectangle` with `SolidColorBrush` | `ColoredRectangleRuntime` |
684+
| `Button.IsDefault` / `IsCancel` | Not supported (handle Enter/Esc globally in your game loop) |
684685

685686
## What Gum Does Not Have
686687

@@ -696,6 +697,7 @@ Some WPF concepts do not exist in Gum:
696697
* **RelativeSource binding** -- bindings reference property names directly
697698
* **Measure/Arrange layout passes** -- Gum uses its own layout engine with unit types
698699
* **Margin / Padding properties** -- spacing is achieved through layout values, `Spacing`, and container nesting
700+
* **Button.IsDefault / IsCancel** -- Game UIs typically handle default/cancel actions via gamepad or global input state machines rather than localized button properties.
699701

700702
## Summary
701703

docs/code/controls/checkbox.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,25 @@ checkBox3.IsChecked = null;
5353

5454
[Try on XnaFiddle.NET](https://xnafiddle.net/#snippet=H4sIAAAAAAAAA6tW8ix2L81VsiopKk3VUcrMyyzJTMzJrEpVslIqSyxSKC5JTM4OSMxLzVGwVchLLVcIhgtoaFrH5CHk9RxTUkLyg_LzS8ASIM3JGanJ2U75FVCtzlAuFo3OGZk5KRow9SB5GFvPsxisLzUFaArIkWhmG5FquBGy6UYoxqcl5hSjm29MqvnGyOYbo5ifV5qTY61UCwD-QP0DdAEAAA)
5555

56+
### Three State CheckBox (IsThreeState)
57+
58+
By default, a CheckBox only cycles between `Checked` and `Unchecked` when clicked. Setting `IsThreeState` to `true` allows the user to cycle through all three states by clicking.
59+
60+
When `IsThreeState` is true, clicking the CheckBox follows this cycle:
61+
**Unchecked -> Checked -> Indeterminate -> Unchecked**
62+
63+
The following code creates a CheckBox that supports three states:
64+
65+
```csharp
66+
// Initialize
67+
var checkBox = new CheckBox();
68+
checkBox.AddToRoot();
69+
checkBox.Text = "Three State CheckBox";
70+
checkBox.IsThreeState = true;
71+
```
72+
73+
[Try on XnaFiddle.NET](...)
74+
5675
## CheckBox Width and Height
5776

5877
Default CheckBoxes have the following default values:

docs/code/controls/listbox.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,25 @@ listBox.SelectionMode = SelectionMode.Multiple;
202202

203203
[Try on XnaFiddle.NET](https://xnafiddle.net/#snippet=H4sIAAAAAAAAA1VPwWrDMAz9FaNTCsVk3S1hh6WHUlgZbB07LDtkjbqqOHJxZDZW-u-zmpRSH_SkJ-n56QjLfhE7KCREnELsib97KD4gkfadAm5D0yF8ToGYhBpHfwgFPFEvlf81bsQHw_iTTcqaR8Y-tu3av3gvtyxvdj5kA9g5smDQ_jaRxGLIJKm8TFjHPL-fmzstNJ9VQ5zUfKzZpDd6WAp2hjRcTWhbKft2aBvBtX_-2uNGsrPE7LwxpANUhi5bF6M60-sRmepo93S94hVdUiPPK99i-vamtqvohA4OSzj9Aw4ZK_xeAQAA)
204204

205+
### Accessing Multiple Selections
206+
207+
When using `SelectionMode.Multiple` or `SelectionMode.Extended`, you can access all currently selected items using the `SelectedItems` property. This property returns a `System.Collections.IList` containing the selected objects.
208+
209+
The `SelectedObject` and `SelectedIndex` properties are still available, but they will only return the **first** item in the selection.
210+
211+
The following code shows how to iterate over all selected items:
212+
213+
```csharp
214+
// Loop through all selected items
215+
foreach (var item in listBox.SelectedItems)
216+
{
217+
System.Diagnostics.Debug.WriteLine($"Selected: {item}");
218+
}
219+
220+
// Check how many items are selected
221+
int count = listBox.SelectedItems.Count;
222+
```
223+
205224
## Reordering With Drag+Drop
206225

207226
The `DragDropReorderMode` controls whether the user can automatically reorder `ListBoxItems` by pushing on an item and dragging it to a new location. By default this value is set to `NoReorder`, but it can be changed to enable reordering.

docs/code/controls/passwordbox.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,19 @@ button.Click += (_, _) => button.Text = passwordBox.Password;
2929
```
3030
[Try on XnaFiddle.NET](https://xnafiddle.net/#snippet=H4sIAAAAAAAACm2PTQvCMAyG7wP_Q-hpooj4cVCZoCLTm8hAhcKYtrjibGXLVBT_u92sX9Nb8rwJeXItWQBkmrjpnnQB45RXcyKkQBFE4sI1JscghkOQJCcVs6E6gwOSn2D2Jna5R-XHRG3AmKfmSuFPstTb7XoBrv7BhWAY6qBRLyYTLrYh6qjZKiSzKNjwUEWMxzqmZCxRV09TSvQ4ldk76xRRSfPJMG9y1Qcv-Bv4Ujd9Zt356D1-zqQocTl-3zQDo0hsdlBxwPar4JfB6RuP5-rXK6bukZJ1uwOuo6mUqAEAAA)
3131

32+
## PasswordChar
33+
34+
The `PasswordChar` property can be used to customize the character that masks the actual text entered by the user. By default, this is an asterisk (`*`).
35+
36+
The following changes the `PasswordChar`:
37+
38+
```csharp
39+
// Initialize
40+
var passwordBox = new PasswordBox();
41+
passwordBox.PasswordChar = '#';
42+
passwordBox.AddToRoot();
43+
```
44+
45+
[Try on XnaFiddle.NET](https://xnafiddle.net/#snippet=H4sIAAAAAAAAA6tW8ix2L81VsiopKk3VUSotzsxLL1ayilYCCuqFZxalphUl5qYqxeooZeZllmQm5mRWpSpZKZUlFikUJBYXl-cXpTjlVyjYKuSllisEIEQ0NK1j8pBU6MHknDOAWm0VYkoNDIzMlSEUmlLHlJSQ_KD8_BKgIUq1ADyYlTeiAAAA)
46+
3247
<figure><img src="../../.gitbook/assets/13_22 15 31.gif" alt=""><figcaption><p>Password entered in a PasswordBox</p></figcaption></figure>

docs/code/controls/textbox.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ panel.AddChild(textBox);
9898

9999
<figure><img src="../../.gitbook/assets/13_09 59 12.gif" alt=""><figcaption><p>TextBox with IsReadOnly set to true responding to mouse click+drag and double-click</p></figcaption></figure>
100100

101+
## MaxLength
102+
103+
The `MaxLength` property can be used to restrict the number of characters that can be entered into a `TextBox`. This limit applies to both user typing and pasting. If a user attempts to type more characters than allowed, the extra characters are ignored. If a user pastes a string that would exceed the limit, the string is truncated to fit.
104+
105+
The following code creates a `TextBox` with a `MaxLength` of 10:
106+
107+
```csharp
108+
// Initialize
109+
var textBox = new TextBox();
110+
textBox.AddToRoot();
111+
textBox.MaxLength = 10;
112+
// only 10 characters show:
113+
textBox.Text = "abcdefghijklmnopqrs";
114+
```
115+
116+
[Try on XnaFiddle.NET](https://xnafiddle.net/#snippet=H4sIAAAAAAAAA1WMsQrCMBRFfyW8SaGU2LHFQRcRdJGCg3WI5rV92iaavGhR_HcrddDxnsO5T1j6RWghZRcwguDJVB7SHfQw3pLD0qkWYR8BGWJSDT0QUrgpJxg7nttOTIXBu8iHNRpnhfmaeKZ1bjfW8h9dq26FpuK6LyfyR3wuelYEKZNEHY4ay6qm07lpjb1cnR9EBq83c7Gb4LUAAAA)
117+
101118
## Selection
102119

103120
Selection can be performed programmatically or by the user using the cursor.
@@ -251,6 +268,12 @@ wrappedTextBox.TextWrapping = TextWrapping.Wrap;
251268
252269
<figure><img src="../../.gitbook/assets/14_06 11 31.gif" alt=""><figcaption><p><code>TextWrapping.Wrap</code> causes text to wrap</p></figcaption></figure>
253270

271+
## Enter Key Behavior
272+
273+
The behavior of the Enter key depends on the `AcceptsReturn` property and whether the TextBox is bound to a data source.
274+
275+
### Multi-Line Entry (AcceptsReturn)
276+
254277
`AcceptsReturn` can be set to true to add newlines when the return (enter) key is pressed.
255278

256279
```csharp
@@ -267,10 +290,6 @@ wrappedTextBox.AcceptsReturn = true;
267290
268291
<figure><img src="../../.gitbook/assets/14_06 12 55.gif" alt=""><figcaption><p>Manually adding newlines by pressing the return (enter) key</p></figcaption></figure>
269292

270-
## Enter Key Behavior
271-
272-
The enter key behavior can be customized for TextBoxes. By default the enter key only applies the Text property to a bound view model. The enter key can also insert multiple lines, but this is usually accompanied with a larger TextBox.
273-
274293
### Enter and Binding
275294

276295
The `TextBox.Text` property can be bound to a ViewModel's property. By default this property is updated immediately when text changes but the `UpdateSourceTrigger` can be set to `UpdateSourceTrigger.LostFocus`.
@@ -299,11 +318,11 @@ mainPanel.AddChild(textBox);
299318

300319
## Tab Key Behavior
301320

302-
The tab key behavior is controlled by the `AccetsTab` property. This value is false by default.
321+
The tab key behavior is controlled by the `AcceptsTab` property. This value is false by default.
303322

304323
If this value is false, pressing the Tab key does not insert a tab `'\t'` character.
305324

306-
If this value is true, pressing the Tab key inserts a tab `'\t'` character. Note that if this value is true, then tabbing to the next control by pressing tab is disabled. In other words, the TextBox keeps keeps focus after the tab key is pressed.
325+
If this value is true, pressing the Tab key inserts a tab `'\t'` character. Note that if this value is true, then tabbing to the next control by pressing tab is disabled. In other words, the TextBox keeps focus after the tab key is pressed. For more information on how focus navigation works, see the [Tabbing (Moving Focus)](../events-and-interactivity/tabbing-moving-focus.md) page.
307326

308327
## Extended Character Sets and Keyboards
309328

docs/code/events-and-interactivity/tabbing-moving-focus.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ The keyboard can be used to interact with controls. Keyboards can be used to:
1616
* To click controls by pressing enter
1717
* To perform control-specific actions such as changing the value of a slider
1818

19-
To enable gamepad control, add the following code. This code only needs to run once, so add it to your game's Initialize or other code which runs at startup.
19+
### Tab Key Capture (AcceptsTab)
20+
21+
By default, the Tab key is used for navigation. However, certain controls like `TextBox` and `PasswordBox` can be configured to capture the Tab key to insert a tab character instead of moving focus.
22+
23+
When a `TextBox` has its `AcceptsTab` property set to `true`, focus navigation via the Tab key is disabled for that control. You can still move focus using the mouse or other input methods. For more details, see the [TextBox Tab Key Behavior](../controls/textbox.md#tab-key-behavior) section.
24+
25+
To enable keyboard tabbing, add the following code. This code only needs to run once, so add it to your game's Initialize or other code which runs at startup.
2026

2127
```csharp
2228
// Initialize

0 commit comments

Comments
 (0)