Class FormControl
- Namespace
- SharpConsoleUI.Controls
- Assembly
- SharpConsoleUI.dll
A labeled-input form: a two-column grid (label | editor) that composes real input controls.
public class FormControl : GridControl, IDOMPaintable, INotifyPropertyChanged, IContainer, IContainerControl, IControlHost, IColorRoleableControl, IGridSource, IFillReportsMinimumHeight, IInteractiveControl, IFocusableControl, IMouseAwareControl, IWindowControl, IDisposable, IFocusScope, ILogicalCursorProvider, ICursorShapeProvider
- Inheritance
-
FormControl
- Implements
- Inherited Members
- Extension Methods
Remarks
FormControl is an honest subclass of GridControl — it adds no custom paint or measure. Each field is a real MarkupControl label placed in column 0 and a real input control (e.g. PromptControl, CheckboxControl) placed in column 1, via the inherited Place(IWindowControl, int, int, int, int). The layout, focus, and rendering are the grid's; the form only wires up fields and value getters.
Column 0 is Auto(int?, int?) (sized to the widest label); column 1 is Star(double, int?, int?) (takes the remaining width). One row is added per field.
Value access is AOT-safe: each field carries a plain Func<TResult> getter — no reflection. GetValues() invokes every getter to produce a name→value snapshot.
Constructors
FormControl()
Initializes a new, empty form with a two-column grid: an auto-sized label column and a star-sized editor column, with no gap between rows.
public FormControl()
Properties
ValidateOnChange
When true, each field re-validates itself as soon as its editor's value changes (live
validation). Fields whose editor exposes no value-changed event are validated only by
Validate() / Submit().
public bool ValidateOnChange { get; set; }
Property Value
Remarks
Setting this to true subscribes all existing and future fields to their editor's native
change event where one exists: InputChanged,
ContentChanged, CheckedChanged,
SelectedValueChanged, ValueChanged, and
SelectionChanged.
Methods
AddCheckbox(string, string, bool, string?)
Adds a boolean field backed by a CheckboxControl. The checkbox carries its own label in the editor column, so the form's label column is left empty for this field.
public FormControl AddCheckbox(string name, string label, bool initial = false, string? hint = null)
Parameters
namestringThe field key.
labelstringThe checkbox's own label (shown in the editor column).
initialboolThe initial checked state.
hintstringOptional dim hint text shown beneath the editor.
Returns
- FormControl
This form, for fluent chaining. The value is
"true"/"false".
AddDropdown(string, string, IEnumerable<string>, string?, string?, int?, HorizontalAlignment?)
Adds a single-select field backed by a DropdownControl.
public FormControl AddDropdown(string name, string label, IEnumerable<string> options, string? initial = null, string? hint = null, int? width = null, HorizontalAlignment? align = null)
Parameters
namestringThe field key.
labelstringThe label text.
optionsIEnumerable<string>The selectable options.
initialstringThe initially selected option, or
nullfor none.hintstringOptional dim hint text shown beneath the editor.
widthint?Optional fixed editor width in columns. When
null(the default), the dropdown auto-fits to its content (the ▾ arrow sits snug after the selected value); when set, its width is fixed at this value.alignHorizontalAlignment?Optional explicit horizontal alignment that overrides the field's default (dropdown auto-fits, i.e. left-aligned at natural width). When
null(the default), the smart default applies. When set, it wins over the auto-fit default (e.g. Stretch to fill the cell).
Returns
- FormControl
This form, for fluent chaining.
AddField(string, string, IWindowControl, Func<string?>, Func<string?, string?>?, bool, string?)
Adds a field with a caller-supplied editor and value getter — the escape hatch used by the typed overloads and by callers needing a control this form does not build itself.
public FormControl AddField(string name, string label, IWindowControl editor, Func<string?> valueGetter, Func<string?, string?>? validate = null, bool required = false, string? hint = null)
Parameters
namestringThe field key (used in GetValues() and GetEditor(string)).
labelstringThe label text (escaped before display).
editorIWindowControlThe control to place in the editor column.
valueGetterFunc<string>A delegate that reads the editor's current value.
validateFunc<string, string>Optional validator (reserved; applied in a later task).
requiredboolWhether the field is required (reserved; applied in a later task).
hintstringOptional dim hint text shown beneath the editor.
Returns
- FormControl
This form, for fluent chaining.
AddMultilineEdit(string, string, string, int, string?, int?, HorizontalAlignment?)
Adds a multi-line text field backed by a MultilineEditControl.
public FormControl AddMultilineEdit(string name, string label, string initial = "", int height = 3, string? hint = null, int? width = null, HorizontalAlignment? align = null)
Parameters
namestringThe field key.
labelstringThe label text.
initialstringThe initial content.
heightintThe editor's viewport height in rows.
hintstringOptional dim hint text shown beneath the editor.
widthint?Optional fixed editor width in columns. When
null(the default), the editor stretches to fill the star editor cell; when set, the editor width is fixed at this value and does not stretch.alignHorizontalAlignment?Optional explicit horizontal alignment that overrides the field's default (multiline stretches to fill). When
null(the default), the smart default applies. When set, it wins over the stretch default; an explicitwidthmay be combined with it.
Returns
- FormControl
This form, for fluent chaining.
AddRadio(string, string, params string[])
Adds a string radio field where each option is both its own value and display label.
public FormControl AddRadio(string name, string label, params string[] options)
Parameters
namestringThe field key.
labelstringThe label text.
optionsstring[]The option strings (value = label).
Returns
- FormControl
This form, for fluent chaining.
AddRadio<T>(string, string, IEnumerable<(T Value, string Label)>, string?, int?, HorizontalAlignment?)
Adds a typed single-select field rendered as a group of radios. The radios are hosted in a borderless PanelControl placed in the editor cell, while GetEditor(string) returns the typed RadioGroup<T> so callers can read/write SelectedValue.
public FormControl AddRadio<T>(string name, string label, IEnumerable<(T Value, string Label)> options, string? hint = null, int? width = null, HorizontalAlignment? align = null)
Parameters
namestringThe field key.
labelstringThe label text.
optionsIEnumerable<(T Value, string Label)>The (value, display-label) option pairs.
hintstringOptional dim hint text shown beneath the editor.
widthint?Optional fixed width in columns for the hosting radio panel. When
null(the default), the radio group takes its natural (left-packed) width — radios are not stretched to fill, as a filled radio group reads oddly. When set, the hosting panel width is fixed at this value.alignHorizontalAlignment?Optional explicit horizontal alignment that overrides the field's default (radio panel keeps its natural, left-packed width). When
null(the default), the smart default applies. When set, it is applied to the hosting panel (e.g. Stretch to fill the cell); an explicitwidthmay be combined with it.
Returns
- FormControl
This form, for fluent chaining.
Type Parameters
TThe option value type.
AddRow(params Action<FormControl>[])
Adds several fields onto a single grid row, packed side by side. Each adder runs against this form
while a row-packing context is active, so the field it adds is placed on the shared row in the next
label/editor column pair (field i → columns 2i/2i+1) instead of a new row.
public FormControl AddRow(params Action<FormControl>[] fieldAdders)
Parameters
fieldAddersAction<FormControl>[]The per-field add callbacks (e.g.
f => f.AddText(...)), one per column.
Returns
- FormControl
This form, for fluent chaining.
Remarks
This is the only path that widens the grid beyond its two base columns: extra Auto(int?, int?) label / Star(double, int?, int?) editor column pairs are added as needed. After all adders run, single-field-per-row placement is restored and the row advances. The packed fields are tracked as one row-group (see SharpConsoleUI.Controls.FormControl.RowGroupCountForTest()). Fields stay packed side by side regardless of width; responsive stacking on narrow terminals is a planned follow-up (a wrap layout the form will compose).
AddSection(string?, bool, bool)
Starts a collapsible field group as a full-width header row in the same flat grid. Every field added after this call (until the next AddSection(string?, bool, bool)) belongs to this section, so a collapsible section can hide/show all its fields together.
public FormControl AddSection(string? title, bool collapsible = false, bool startCollapsed = false)
Parameters
titlestringThe section title (escaped and bolded), or
nullto end the current section.collapsibleboolWhen
true, a toggle button is rendered that hides/shows the section's fields.startCollapsedboolWhen
true(and collapsible), the section's fields start hidden and the glyph starts ▸.
Returns
- FormControl
This form, for fluent chaining.
Remarks
A section is not a nested panel: the header is a title MarkupControl in column 0
and (when collapsible) a ▸/▾ toggle ButtonControl in column 1, both on
one grid row. Collapsing toggles Visible on each member field's controls.
Pass a null title to end the current section: following fields belong to none.
AddSlider(string, string, double, double, double, string?, int?, HorizontalAlignment?)
Adds a numeric field backed by a SliderControl.
public FormControl AddSlider(string name, string label, double min, double max, double initial, string? hint = null, int? width = null, HorizontalAlignment? align = null)
Parameters
namestringThe field key.
labelstringThe label text.
mindoubleThe slider minimum.
maxdoubleThe slider maximum.
initialdoubleThe initial value.
hintstringOptional dim hint text shown beneath the editor.
widthint?Optional fixed editor width in columns. When
null(the default), the slider stretches to fill the star editor cell; when set, its width is fixed at this value and does not stretch.alignHorizontalAlignment?Optional explicit horizontal alignment that overrides the field's default (slider stretches to fill). When
null(the default), the smart default applies. When set, it wins over the stretch default; an explicitwidthmay be combined with it.
Returns
- FormControl
This form, for fluent chaining. The value is the number's string form.
AddText(string, string, string, Func<string?, string?>?, bool, string?, int?, HorizontalAlignment?)
Adds a single-line text field backed by a PromptControl.
public FormControl AddText(string name, string label, string initial = "", Func<string?, string?>? validate = null, bool required = false, string? hint = null, int? width = null, HorizontalAlignment? align = null)
Parameters
namestringThe field key.
labelstringThe label text.
initialstringThe initial text value.
validateFunc<string, string>Optional validator run on the field's current text.
requiredboolWhether the field is required (empty text fails validation).
hintstringOptional dim hint text shown beneath the editor.
widthint?Optional fixed input width in columns. When
null(the default), the editor stretches to fill the star editor cell; when set, the input area is fixed at this width and does not stretch.alignHorizontalAlignment?Optional explicit horizontal alignment that overrides the field's default (text stretches to fill). When
null(the default), the smart default applies. When set, it wins over the stretch default; an explicitwidthmay be combined with it (e.g. a fixed width, right-aligned).
Returns
- FormControl
This form, for fluent chaining.
Cancel()
Raises the Cancelled event.
public void Cancel()
GetEditor(string)
Gets the value-editor for a field: the placed input control for most fields, or the typed RadioGroup<T> for radio fields (so callers get the typed selection surface).
public object GetEditor(string name)
Parameters
namestringThe field key.
Returns
- object
The field's value-editor object.
Exceptions
- KeyNotFoundException
Thrown when no field with
nameexists.
GetValues()
Reads every field's current value into a name→value snapshot. AOT-safe: each value comes from the field's plain getter delegate.
public IReadOnlyDictionary<string, string?> GetValues()
Returns
- IReadOnlyDictionary<string, string>
A read-only map of field name to current value.
Submit()
Validates the form and, if valid, raises Submitted with the current values snapshot.
public void Submit()
Validate()
Validates every field: a required field with an empty/null value fails with "Required";
otherwise the field's custom validator (if any) runs on its current value. Each field's error line
is updated (shown with the message, or hidden when valid). Idempotent.
public bool Validate()
Returns
- bool
truewhen every field is valid; otherwisefalse.
WithButtons(string, string, bool)
Adds a final, full-width row of right-aligned action buttons: an OK button (which submits the form)
and, when showCancel is true, a Cancel button (which raises
Cancelled). The buttons live in a right-aligned HorizontalGridControl
placed col-spanning the whole form.
public FormControl WithButtons(string ok = "OK", string cancel = "Cancel", bool showCancel = true)
Parameters
okstringThe OK button caption.
cancelstringThe Cancel button caption.
showCancelboolWhether to include the Cancel button.
Returns
- FormControl
This form, for fluent chaining.
Remarks
Call this LAST, after any AddRow that widens the grid: the button row's col-span is
fixed at call time to the current ColumnDefinitions count, so adding
columns afterwards would leave the button row short of the form's full width.
Events
Cancelled
Raised when the form is cancelled (e.g. by a Cancel button wired in a later task).
public event EventHandler? Cancelled
Event Type
Submitted
Raised by Submit() when validation passes; carries the current values snapshot.
public event EventHandler<IReadOnlyDictionary<string, string?>>? Submitted