Skip to content

Form

Form is a lookless ContentControl scope root: on submit it clears injected errors, raises ClearErrorsRequested, re-applies its Errors dictionary, reveals every descendant NaviusField, and then either raises Submitted (every field valid) or focuses the first invalid field. There is no ported FormContext register/unregister registry: every orchestration point does a fresh logical-tree walk for descendant NaviusFields, which is always correct and needs no lifecycle management.

xmlns:form="clr-namespace:Navius.Wpf.Primitives.Controls.Form;assembly=Navius.Wpf.Primitives"
xmlns:field="clr-namespace:Navius.Wpf.Primitives.Controls.Field;assembly=Navius.Wpf.Primitives"
<form:NaviusForm x:Name="SignupForm" Errors="{Binding ServerErrors}">
<StackPanel>
<field:NaviusField FieldName="email">
<!-- label / control / description / error -->
</field:NaviusField>
<form:NaviusFormSubmit Content="Create account" IsDefault="True" />
</StackPanel>
</form:NaviusForm>
Property Type Default From Description
Errors IReadOnlyDictionary<string, string[]>? null NaviusForm Validation errors keyed by FieldName; pushed into each matching field’s ExternalErrors on change and cleared from non-matching fields.
Command ICommand null Button (NaviusFormSubmit) Auto-wired to the ancestor form’s SubmitCommand on Loaded, unless the consumer already bound one.
Command Description
SubmitCommand Read-only per-instance ICommand property (not a static RoutedCommand). Bind a Button.Command to it, or use NaviusFormSubmit, which auto-wires it. Executing it clears injected errors, raises ClearErrorsRequested, re-applies Errors, reveals every descendant field, then raises Submitted or focuses the first invalid field. CanExecute always returns true; validity is enforced inside the command body, not via CanExecute/IsEnabled.
Method Description
Reset() Raises ClearErrorsRequested and clears every descendant field’s ExternalErrors.
Event Signature Fires when
Submitted RoutedEventHandler From HandleSubmit, only when every descendant NaviusField is valid after Reveal().
ClearErrorsRequested RoutedEventHandler At the start of a submit attempt (before revalidating), and again on Reset().
Key Behavior
Enter (inside any control, when a NaviusFormSubmit with IsDefault="True" is in scope) Triggers the default button’s Command (the form’s SubmitCommand) via the native WPF Button.IsDefault mechanism; not custom-coded by this family.

Neither NaviusForm nor NaviusFormSubmit overrides OnCreateAutomationPeer: NaviusForm ships no custom peer, and NaviusFormSubmit inherits the native ButtonAutomationPeer (AutomationControlType.Button with InvokePattern), unchanged. No role= or aria-* wiring is asserted in the web contract either. On a blocked submit, focus moves to the first invalid field via NaviusField.FocusRegisteredControl(), a direct element reference replacing the web’s JS-interop focusElementById fallback. The unit suite drives NaviusForm.SubmitCommand.Execute(null) directly against a real Validation-bound field tree (FormTests.cs).

  • FormContext’s register/unregister field registry is not ported: NaviusForm re-walks its own logical tree (LogicalTreeWalker.Descendants<NaviusField>) at each submit/reset, always correct with no lifecycle bookkeeping.
  • PreventDefault is dropped, not stubbed: there is no browser-level default submit navigation to prevent in WPF.
  • FormData (the name-to-value snapshot exposed to a custom MatchFn) is not ported: WPF consumers read values from their own bound view models directly.
  • The JS focusElementById fallback used when a field has no FocusControl is gone: focus always goes through a direct UIElement reference (NaviusField.FocusRegisteredControl), silently no-op if a field has no registered control.
  • OnClearErrors stays argument-less (ClearErrorsRequested is a plain RoutedEventArgs); no richer signature was added.
  • Enter-to-submit is the native WPF idiom (Button.IsDefault) rather than an emulated implicit <form> behavior.
  • Known residual (M6 audit): NaviusFormSubmit’s auto-wire of Command to the ancestor form’s SubmitCommand runs inside a Loaded handler, which never fires for elements outside a live Window; this path is untested by the headless unit suite (which drives SubmitCommand.Execute(null) directly) and is expected, but not verified, to work in a real window.
  • Known residual (M6 audit): SubmitCommand.CanExecute always returns true and CanExecuteChanged is a no-op, so the original strategy note’s idea of a submit button tracking form validity via CanExecute/IsEnabled was not implemented; validity is enforced inside the command body instead. Consistent with the port’s explicit-command model, but not flagged as a delta in the original implementation notes.

The Form Gallery page rendered at the pinned commit, in each theme.

Form Gallery page in the light theme

Form Gallery page in the dark theme

Form Gallery page in the high contrast theme