Form XML Reference

Looking for the runtime? The declarative loader on this page sits on top of the FormControl — read that first for the field semantics, validation model, and value access. This page documents the XML that describes a form.

SharpConsoleUI can build a form from a small declarative XML document instead of imperative builder calls. You describe the fields in a file (or a string constant), and FormXml parses it and drives the existing FormControl API to construct a live, fully-interactive form:

using SharpConsoleUI.Controls.Forms;

var form = FormXml.FromXml(@"
<form title='Contact'>
  <text name='name'  label='Name:'  required='true'/>
  <text name='email' label='Email:' pattern='^[^@]+@[^@]+$' message='Enter a valid email'/>
  <buttons/>
</form>");

window.AddControl(form);

This is the same XAML-family idea used across UI toolkits — markup describes the tree, code supplies the behavior. FormXml is a thin call-through: it parses the XML with the BCL XDocument and calls the FormControl field-add methods. It invents no layout, validation, or submit logic of its own — everything a loaded form does is exactly what the FormControl runtime does. It adds no dependency and is NativeAOT-safe (no reflection, no dynamic code; a pattern= compiles a runtime interpreted Regex).

Contents

The <form> root

The root element must be <form> — any other root throws a FormXmlException. Child elements are added to the form in document order.

<form title='Settings' columnGap='1' rowGap='0'>
  <!-- fields, sections, rows, buttons -->
</form>
Attribute Type Default Description
title string Accepted for readability/labeling. Note: FormControl renders no built-in title band — host the form in a titled window if you want a heading.
columnGap int 0 Sets FormControl.ColumnGap (space between the label and editor columns).
rowGap int 0 Sets FormControl.RowGap (space between rows).

Field elements

Each field element requires a name (the value key returned by GetValues()). A field with no name throws. A label is optional (defaults to empty). Every field also accepts an optional hint that renders a dim line beneath the editor.

<text>

Single-line text field (backed by PromptControl). Supports all validation attributes.

<text name='host' label='Host:' initial='localhost' required='true' hint='e.g. localhost'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Label text
initial string "" Initial text
hint string Dim hint below the editor
width int Fixed input width in columns. Omit to stretch-fill the editor cell (default).
align left|center|right|stretch Overrides the field's default alignment (text stretch-fills). Omit for the smart default. Combines with width.
(validation attrs) See Validation attributes

<multiline>

Multi-line editor (backed by MultilineEditControl).

<multiline name='notes' label='Notes:' height='4' hint='Markdown accepted'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Label text
initial string "" Initial content
height int 3 Editor viewport height in rows
hint string Dim hint
width int Fixed editor width in columns. Omit to stretch-fill the editor cell (default).
align left|center|right|stretch Overrides the field's default alignment (multiline stretch-fills). Omit for the smart default. Combines with width.

Validation attributes on <multiline> are ignored in this version — see non-goals.

<checkbox>

Boolean checkbox (backed by CheckboxControl). Its label rides in the editor column. Value is "true" / "false".

<checkbox name='ssl' label='Use SSL/TLS' initial='true' hint='encrypt the connection'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Checkbox label (editor column)
initial bool false Initial checked state
hint string Dim hint

Single-select dropdown (backed by DropdownControl). Options come from a comma-separated options list.

<dropdown name='driver' label='Driver:' options='PostgreSQL,MySQL,SQLite' initial='PostgreSQL'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Label text
options comma-separated "" Selectable options (empty entries dropped)
initial string Initially selected value
hint string Dim hint
width int Fixed editor width in columns. Omit to stretch-fill the editor cell (default).
align left|center|right|stretch Overrides the field's default alignment (dropdown auto-fits to content, i.e. left). Use stretch to fill the cell. Omit for the smart default.

Validation attributes on <dropdown> are ignored — a dropdown always holds one of its options.

<radio>

Single-select radio group. Options come from a comma-separated options list; initial selects one.

<radio name='mode' label='Mode:' options='Read-write,Read-only,Replica' initial='Read-write'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Label text
options comma-separated "" Option strings (value = label)
initial string Initially selected option

Validation attributes on <radio> are ignored — a radio group always resolves to an option.

width and align attributes are not honoured for <radio> — radios keep their natural, left-packed width (a filled radio group reads oddly). The imperative typed AddRadio<T>(…, width:, align:) overload accepts both, but the XML path uses the params-string overload, which takes neither.

<slider>

Numeric slider (backed by SliderControl). The slider clamps to its min/max range.

<slider name='timeout' label='Timeout (s):' min='0' max='60' initial='30' hint='connection timeout'/>
Attribute Type Default Description
name string — (required) Value key
label string "" Label text
min number 0 Minimum value
max number 100 Maximum value
initial number 0 Initial value
hint string Dim hint
width int Fixed editor width in columns. Omit to stretch-fill the editor cell (default).
align left|center|right|stretch Overrides the field's default alignment (slider stretch-fills). Omit for the smart default. Combines with width.

Validation attributes on <slider> are ignored — the slider already clamps to its range.

Structure elements

<section>

Inserts a full-width section header row. Every field nested inside the <section> element belongs to that section; the section is closed automatically at the element's end.

<section title='Advanced' collapsible='true' collapsed='true'>
  <checkbox name='ssl' label='Use SSL/TLS'/>
  <slider name='timeout' label='Timeout (s):' min='0' max='60' initial='30'/>
</section>
Attribute Type Default Description
title string Section header text
collapsible bool false Adds a ▸/▾ toggle to collapse the section
collapsed bool false Start collapsed (fields hidden). Only meaningful with collapsible='true'

<row>

Packs its child field elements side by side onto a single grid row (each field keeps its own label/editor pair). Children are the same field elements.

<row>
  <text name='first' label='First:'/>
  <text name='last'  label='Last:'/>
</row>

<row> takes no attributes of its own.

<buttons>

Adds the right-aligned action button row. The OK button calls Submit(); the Cancel button raises Cancelled.

<buttons ok='Connect' cancel='Cancel' showCancel='true'/>
Attribute Type Default Description
ok string "OK" OK button caption
cancel string "Cancel" Cancel button caption
showCancel bool true Whether to include the Cancel button

Validation attributes

These attributes apply to <text> fields (and <multiline> in principle, but multiline is unvalidated in this version — see non-goals). Each present attribute contributes one rule; the field's value passes only when every rule passes. Empty values skip all built-in rules; only required fails on empty (so an optional field with a type/pattern/min/max/maxLength constraint is still allowed to be blank).

Attribute Type Rule
required bool Value must be non-empty ("Required").
type int | number Value must parse as a whole number (int) or a number (number).
pattern regex Value must match the regular expression (compiled at load time via new Regex(pattern)).
min number Numeric value must be min.
max number Numeric value must be max.
minLength int String length must be minLength.
maxLength int String length must be maxLength.
message string Overrides the default error text for every rule on the field.
<text name='port' label='Port:' type='int' min='1' max='65535' required='true'
      message='Enter a port between 1 and 65535'/>

Named validators (rule=)

For validation that XML can't express (cross-field checks, DNS lookups, custom parsing), attach a rule='name' to a <text> field and supply the C# implementation through the namedValidators registry passed to FromXml / FromXmlFile. The registry maps a name to a Func<string?, string?> that returns null when the value is acceptable, or an error message otherwise. A rule= whose name is not in the registry throws a FormXmlException.

var registry = new Dictionary<string, Func<string?, string?>>
{
    ["validDsn"] = v =>
        string.IsNullOrEmpty(v) || v.Contains('=')
            ? null
            : "Expected key=value pairs, e.g. Host=localhost;Port=5432",
};

var form = FormXml.FromXml(@"
<form>
  <text name='dsn' label='DSN:' rule='validDsn'/>
  <buttons/>
</form>", registry);

A rule= composes with the built-in validation attributes — the named validator runs alongside required, type, pattern, and the others on the same field.

Loading: FromXml / FromXmlFile

// From a string:
FormControl form = FormXml.FromXml(xml);
FormControl form = FormXml.FromXml(xml, namedValidators);

// From a file (root must be <form>):
FormControl form = FormXml.FromXmlFile("forms/connect.xml");
FormControl form = FormXml.FromXmlFile("forms/connect.xml", namedValidators);

Both return a ready FormControl. Wire submission/cancellation and read values with the runtime API — OnSubmit isn't an XML concept, so subscribe in code:

form.Submitted += (_, values) => Save(values["host"], values["port"]);
form.Cancelled += (_, _) => window.Close();

Errors: FormXmlException

FormXml never fails silently. It throws a FormXmlException (with line/position context where the XML provides it) when:

  • the XML is malformed (wraps the underlying XmlException),
  • the root element is not <form>,
  • an element is unknown (e.g. <slidr>),
  • a field is missing its required name attribute,
  • an integer/number attribute (columnGap, height, min, …) is present but unparseable,
  • a rule='name' references a validator not found in the registry.

Unknown elements throw a FormXmlException, but unknown attributes are silently ignored (for forward compatibility).

try
{
    var form = FormXml.FromXml(xml, registry);
}
catch (FormXmlException ex)
{
    // ex.Message includes "(line N, position M)" where available
    logger.LogError(ex, "Bad form descriptor");
}

v1 non-goals

The current loader deliberately stops here:

  • No <field> custom-editor element. The FormControl.AddField escape hatch (arbitrary editor + value getter) has no XML mapping — custom editors are wired in code.
  • No ToXml / export. The mapping is XML → form only; there's no form → XML serializer.
  • Validation on <dropdown>, <radio>, <slider> is ignored. Those FormControl overloads take no validator, and each type already constrains its own value.
  • <multiline> is unvalidated. The multiline overload takes no validator, so validation attributes on <multiline> have no effect.

Worked examples

1. Simple contact form

var form = FormXml.FromXml(@"
<form title='Contact'>
  <text name='name'  label='Name:'  required='true'/>
  <text name='email' label='Email:' pattern='^[^@\s]+@[^@\s]+\.[^@\s]+$'
        message='Enter a valid email address'/>
  <dropdown name='category' label='Category:' options='Bug,Feature,Question'/>
  <multiline name='message' label='Message:' height='5' hint='Describe the issue'/>
  <buttons/>
</form>");

form.Submitted += (_, values) =>
    windowSystem.NotificationStateService.ShowNotification(
        "Submitted", $"{values["name"]} — {values["category"]}", NotificationSeverity.Success);

window.AddControl(form);

2. Connection form with a collapsed Advanced section, validation, and a named rule

var registry = new Dictionary<string, Func<string?, string?>>
{
    ["validDsn"] = v =>
        string.IsNullOrEmpty(v) || v.Contains('=')
            ? null
            : "Expected key=value pairs (Host=…;Port=…)",
};

var form = FormXml.FromXml(@"
<form title='Connection' columnGap='1'>
  <text name='host' label='Host:' initial='localhost' required='true' hint='e.g. localhost'/>
  <text name='dsn'  label='Extra DSN:' rule='validDsn' hint='key=value;key=value'/>
  <dropdown name='driver' label='Driver:' options='PostgreSQL,MySQL,SQLite' initial='PostgreSQL'/>

  <section title='Advanced' collapsible='true' collapsed='true'>
    <text name='port' label='Port:' type='int' min='1' max='65535' initial='5432'
          message='Enter a port between 1 and 65535'/>
    <checkbox name='ssl' label='Use SSL/TLS'/>
    <radio name='mode' label='Mode:' options='Read-write,Read-only,Replica' initial='Read-write'/>
    <slider name='timeout' label='Timeout (s):' min='0' max='60' initial='30'/>
  </section>

  <buttons ok='Connect' cancel='Cancel'/>
</form>", registry);

form.Submitted += (_, values) => Connect(values["host"], values["port"], values["driver"]);
form.Cancelled += (_, _) => window.Close();

window.AddControl(form);

See also: FormControl · MARKUP_SYNTAX · CONTROLS

Back to Main Documentation