Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Validator4D banner

Validator4D

Fluent form validation for Delphi FMX — rules, presentation, input, and final gate under a single API.

License: MIT Delphi Version

Validator4D is a focused library for form validation in Delphi FMX.

It keeps validation rules, visual feedback, input assistance, and final flow decision explicit, composable, and easy to integrate into existing FMX forms.

The library does not impose an application framework. It is a small, integrable mechanism that fits into any existing screen structure.


Why this project exists

In many Delphi FMX applications, form validation gets scattered across event handlers, isolated labels, manual color changes, ad-hoc masks, duplicated messages, and rules that are hard to maintain.

Validator4D exists to solve one specific problem:

bring order to FMX form validation with explicit contracts, configurable presentation, integrated localization, and a single decision point before saving.

It is intentionally focused. It does not try to replace server-side validation, database constraints, domain rules, or become a complete UI framework. It provides a small and predictable mechanism for bringing consistency to form validation built on FMX controls.


Architecture overview

Validator4D is organized around a validation core and helper layers by responsibility:

  • Core resolves rules, validation lifecycle, and callbacks.
  • FMX Presentation controls error labels, title integration, and inline error rendering.
  • FMX Input applies masks, keyboard types, and return keys.
  • Localization provides embedded languages and JSON overlay loading.
  • Internal tracks controls via FreeNotification to handle lifetime safely.

For a detailed explanation of the internal flow, see Architecture.md.


Table of contents


Quick overview

Minimal setup

FValidator := CreateFormValidator
  .WithAutoTrim(True)
  .WithRequiredTitleMarker(True);

FValidator
  .ForControl(EditName)
    .Required
    .MinLen(2)
  .ForControl(EditEmail)
    .Required
    .Email;

FValidator.AttachOnExit.AttachLive;

Decision before saving

if not FValidator.ValidateAll then
  Exit;

SaveRecord;

Rules, visual presentation, input assistance, and callbacks share the same fluent API. The final decision stays centralized in ValidateAll.


What Validator4D is

Validator4D is a form validation mechanism for Delphi FMX.

It is useful when you need to:

  • declare validation rules clearly, close to the controls they validate;
  • combine text, date/time, number, mask, selection, and custom rules;
  • show error messages with consistent colors and positioning;
  • mark required fields automatically on the title labels;
  • display messages in multiple languages with embedded catalogs or external JSON;
  • preserve existing FMX event handlers on controls;
  • validate by tab or by group of controls before final save;
  • apply masks, keyboard types, and return keys on input controls;
  • centralize the "save / don't save" decision in a single point.

What Validator4D is not

Validator4D is not a complete application framework.

This version does not try to:

  • replace database constraints;
  • replace server-side validation;
  • replace domain or business rules;
  • provide a reporting or template engine;
  • implement every FMX styling detail;
  • guarantee identical visual behavior across all platforms;
  • read or manipulate .fmx files at runtime;
  • replace general-purpose UI tools;
  • generate dynamic forms from metadata.

This boundary is intentional. Validator4D focuses on FMX form validation.


Four responsibilities, one API

Real form validation involves four distinct responsibilities. Validator4D keeps all of them under a coherent fluent API.

1. Validation rules

Define what makes a control valid or invalid.

FValidator.ForControl(EditEmail)
  .Required
  .Email;

2. Error presentation

Defines where and how the message appears visually. Three techniques available:

// Technique 1: label created at runtime from the title
TValidatorPresentationFMX.ForControl(EditName)
  .CreateErrorLabelFromTitle(LblNameTitle, elpRightOfTitle, 4);

// Technique 2: design-time error label
FValidator.ForControl(EditName)
  .WithErrorLabel(LblNameError);

// Technique 3: title as error area
FValidator.ForControl(EditName)
  .WithErrorLabelAsTitle(LblNameTitle);

3. Input assistance

Applies mask, keyboard, and return key to controls.

TValidatorInputFMX.ForControl(EditDate)
  .Mask('9999-99-99')
  .KeyboardTypeNumberPad
  .ReturnKeyNext;

4. Orchestration

Decides when to validate and what to do with the result.

if not FValidator.ValidateAll then
  Exit;

SaveRecord;

The four responsibilities coexist without coupling. You can change the presentation technique without touching the rules, add a mask without changing validation, or change messages (language) without rewriting logic.


When to use each presentation technique

Scenario Recommended technique
You want the library to create the error label automatically CreateErrorLabelFromTitle
You already have an error label in the designer WithErrorLabel
You need to save vertical space WithErrorLabelAsTitle
Messages can be long CreateErrorLabelFromTitle with elpBelowTitle
Layout heavily controlled in the designer WithErrorLabel

For a deeper discussion of each technique and when to apply them, see the Practical Guide.


Requirements

  • Delphi 11 or later is the validated target.
  • FMX as the UI framework.
  • No external runtime dependencies.

Earlier Delphi versions may work, but they have not been officially validated for this initial release.


Installation

Clone the repository and add the src folder to your project's Search Path:

Project → Options → Building → Delphi Compiler → Search Path

To use the public API:

uses
  Validator4D,
  Validator4D.FMX.Input,
  Validator4D.FMX.Presentation;

For additional localization or language switching:

uses
  Validator4D.Localization;

The Validator4D.pas unit is the public facade and exposes IFormValidator, IValidationRule, and the CreateFormValidator factory. The other units are helpers organized by responsibility.


Quick start

Minimal pattern

FValidator := CreateFormValidator
  .WithAutoTrim(True)
  .WithRequiredTitleMarker(True);

FValidator
  .ForControl(EditName)
    .Required
    .MinLen(2)
  .ForControl(EditEmail)
    .Required
    .Email;

FValidator.AttachOnExit.AttachLive;

With presentation and final gate

// Presentation
TValidatorPresentationFMX.ForControl(EditName)
  .CreateErrorLabelFromTitle(LblNameTitle, elpRightOfTitle, 4)
  .InlineError(True);

TValidatorPresentationFMX.ForControl(EditEmail)
  .CreateErrorLabelFromTitle(LblEmailTitle, elpRightOfTitle, 4)
  .InlineError(True);

// Input assistance
TValidatorInputFMX.ForControl(EditEmail)
  .KeyboardTypeEmailAddress
  .ReturnKeyDone;

// Rules
FValidator
  .ForControl(EditName)
    .Required
    .MinLen(2)
  .ForControl(EditEmail)
    .Required
    .Email;

FValidator.AttachOnExit.AttachLive;

// In a save button
if not FValidator.ValidateAll then
  Exit;

SaveRecord;

For a complete, ready-to-compile form example, see examples/QuickStart/. For a broad demonstration with nine tabs covering all features, see examples/Showcase/.


Features

Feature Available
Fluent API ✓
Text, date/time, number, mask, selection, custom rules ✓
Presentation in three techniques (auto-created, design-time, title-as-error) ✓
Automatic required-field marker on titles ✓
Customizable marker symbol ✓
Input assistance (mask, keyboard, return key) ✓
Localization with five embedded languages ✓
Message loading from JSON as overlay ✓
Operating-system locale auto-detection ✓
Live validation (per keystroke) and on-exit ✓
Preservation of existing FMX event handlers ✓
Validation of runtime-created controls ✓
Tab-scoped validation via application iteration ✓
Per-control and global callbacks ✓
Customizable colors for valid/invalid states ✓
Automatic MaxLength application from rules ✓
Safe cleanup of destroyed controls via FreeNotification ✓

Validation rules

Category Rules
Text Required, WhenFilled, MinLen, MaxLen, ExactLen, Pattern
Date/time DateFormat, TimeFormat, DateTimeFormat, MinDate, MaxDate, BetweenDates, MinTime, MaxTime
Number IntegerOnly, Number, Range
Japanese HiraganaOnly, KatakanaOnly, KanaOnly
Internet Email, URL, IPAddress
Mask Mask
Boolean Checked
Selection RequireSelection, RequireNonFirst, DisallowText, MinSelected, MaxSelected, SelectedInRange
Custom Custom

WhenFilled is a modifier, not a rule: it causes the chain to be skipped when the control is empty. Useful for optional fields that, when filled, have a defined format.

MinDate, MaxDate, and BetweenDates reject non-empty text that cannot be parsed as a date. MinTime and MaxTime reject non-empty text that cannot be parsed as a time. Use DateFormat, TimeFormat, or DateTimeFormat when you need to enforce an explicit textual format such as yyyy-mm-dd.

Email, URL, and IPAddress are practical regex-based rules covering the most common formats. URL accepts addresses with or without scheme prefix and TLDs up to 6 characters; IPAddress validates IPv4 only. For stricter or specialized formats (IPv6, RFC-compliant URLs, internationalized domains), use the Custom rule with application logic.

Custom is the most important extension point. It allows the application to enforce any business rule without Validator4D needing to know the domain.

For discussion on when to use each rule, see the Practical Guide.


Error presentation

Three configurable techniques, chosen according to the form's layout:

Automatically created label — you provide the title label; Validator4D creates a second label at runtime for the error. Two positions: elpRightOfTitle (compact) or elpBelowTitle (more space).

Design-time error label — you create the label in the .fmx; Validator4D only writes the message into it.

Title as error area — a single TLabel plays two roles: title when valid, title + message when invalid.

The required marker (* by default, customizable) is applied automatically to the title when the control has a required-style rule and Validator4D knows the title label.

FValidator := CreateFormValidator
  .WithRequiredTitleMarker(True, '※');  // custom symbol

Input assistance

TValidatorInputFMX configures input aspects without mixing with validation rules:

TValidatorInputFMX.ForControl(EditDate)
  .Mask('9999-99-99')
  .KeyboardTypeNumberPad
  .ReturnKeyNext;

Keyboard type options: Default, NumberPad, NumbersAndPunctuation, PhonePad, EmailAddress, URL.

Return key options: Default, Next, Done, Go, Search, Send.

Mask placeholders:

Symbol Meaning
9 digit
A uppercase letter
a lowercase letter
X alphanumeric character
* any character

Input assistance complements but does not replace the rules. A mask facilitates typing; the DateFormat rule defines what is accepted.


Localization

Embedded languages

Validator4D_SetLanguage(vlEN);  // English
Validator4D_SetLanguage(vlDE);  // German
Validator4D_SetLanguage(vlFR);  // French
Validator4D_SetLanguage(vlJA);  // Japanese
Validator4D_SetLanguage(vlPT);  // Portuguese

Auto-detection from the operating system locale

Validator4D_AutoDetectLanguage;

JSON overlay

Validator4D_LoadLanguage('validator4d_es.json');

The JSON works as a layer over the active catalog. Keys not present in the file continue to come from the base catalog. This allows partial files — you translate only the messages you want to customize.


Event preservation

Validator4D attaches validation bridges to FMX events without overwriting handlers already assigned by the application:

// User's handler, assigned earlier
EditName.OnChange := MySearchHandler;

// Validator4D attaches its bridge without removing the previous handler
FValidator.AttachOnExit.AttachLive;

Both handlers run when the event fires. Mask normalization (live) happens before invoking the original handler, so the application sees the already-normalized text.

Detach removes the bridges when needed (for example, during bulk data loading without triggering validation).


Tab-scoped validation

Validator4D has no concept of "tab". The application implements that scope by iterating over relevant controls:

function TFrmCustomer.ValidateAddressTab: Boolean;
var
  LControl: TControl;
begin
  Result := True;
  for LControl in [EditZip, EditAddress, EditCity] do
    Result := FValidator.Validate(LControl) and Result;
end;

The final decision before saving still uses ValidateAll:

if not FValidator.ValidateAll then
  Exit;

DataSet.Post;

Examples

The repository ships two examples under examples/:

QuickStart

Minimal one-form example with two validated fields, intended as a starting point.

examples/QuickStart/
├── project/
│   ├── Validator4D.Quickstart.dpr
│   └── Validator4D.Quickstart.dproj
└── src/
    ├── Validator4D.Quickstart.Main.pas
    └── Validator4D.Quickstart.Main.fmx

Showcase

Broad reference demo covering most of the mechanism's features, with nine tabs and validation logic split across separate units.

Validator4D Showcase main window

Demonstrates:

  • fluent validation rules across nine categories;
  • three presentation techniques side by side;
  • input assistance (mask, keyboard, return key);
  • automatic required marker on titles;
  • per-control and global callbacks;
  • localization with embedded languages;
  • message loading from JSON;
  • preservation of existing FMX events;
  • tab-scoped validation;
  • controls created and destroyed at runtime;
  • safe lifetime via FreeNotification.

Tests

The test suite uses DUnitX.

Current coverage includes:

  • basic rule behavior;
  • custom rules;
  • presentation helpers;
  • required-title markers;
  • regressions in FMX input (masks and keyboard);
  • JSON loading and overlay;
  • localization and embedded languages;
  • lifetime and cleanup of destroyed controls;
  • event handler preservation;
  • regressions discovered while building the Showcase.

Tests are part of the architecture because they define what cannot regress when internals change.


Architecture

The project is organized around a validation core and helper layers:

Validator4D.Core
  Rules, validators, validation lifecycle, callbacks.

Validator4D.FMX.Input
  Masks, keyboard type, return key helpers.

Validator4D.FMX.Presentation
  Error labels, title integration, inline error rendering.

Validator4D.Localization
  Active language management and JSON overlay loading.

Validator4D.Localization.BuiltIn
  Embedded language catalogs (EN, DE, FR, JA, PT).

Validator4D.Internal.ControlRegistry
  Control tracking via FreeNotification.

Validator4D.Internal.Rtti
  RTTI application on input properties.

Validator4D
  Public facade. Exposes IFormValidator, IValidationRule, and CreateFormValidator.

The validation cycle is:

ValidateAll
  ↓
for each registered control:
  resolve rule chain
  apply modifiers (AutoTrim, WhenFilled)
  execute rules
  update visual state via presentation
  invoke control's OnValidate (if assigned)
  invoke global OnValidated (if assigned)
  ↓
return Boolean result

For detailed diagrams of each flow, see Architecture.md.


Repository layout

Validator4D/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── README_pt-BR.md
│
├── assets/
│   ├── banner.png
│   ├── banner_pt-BR.png
│   └── screenshots/
│       └── showcase-main-window.png
│
├── docs/
│   ├── Architecture.md
│   ├── Architecture_pt-BR.md
│   ├── Guide.md
│   └── Guide_pt-BR.md
│
├── examples/
│   ├── QuickStart/
│   │   ├── project/
│   │   │   ├── Validator4D.Quickstart.dpr
│   │   │   └── Validator4D.Quickstart.dproj
│   │   └── src/
│   │       ├── Validator4D.Quickstart.Main.pas
│   │       └── Validator4D.Quickstart.Main.fmx
│   │
│   └── Showcase/
│       ├── assets/
│       │   └── localization/
│       │       ├── validator4d_custom.json
│       │       └── validator4d_es.json
│       ├── project/
│       │   ├── Validator4D.Showcase.dpr
│       │   └── Validator4D.Showcase.dproj
│       └── src/
│           ├── FormMain.pas
│           ├── FormMain.fmx
│           ├── Validator4D.Showcase.Validation.DateTimeNumber.pas
│           ├── Validator4D.Showcase.Validation.EventsLifetime.pas
│           ├── Validator4D.Showcase.Validation.Internet.pas
│           ├── Validator4D.Showcase.Validation.Localization.pas
│           ├── Validator4D.Showcase.Validation.MasksInput.pas
│           ├── Validator4D.Showcase.Validation.Presentation.pas
│           ├── Validator4D.Showcase.Validation.QuickStart.pas
│           ├── Validator4D.Showcase.Validation.Selection.pas
│           └── Validator4D.Showcase.Validation.TextRules.pas
│
├── src/
│   ├── Validator4D.pas
│   ├── Validator4D.Core.pas
│   ├── Validator4D.FMX.Input.pas
│   ├── Validator4D.FMX.Presentation.pas
│   ├── Validator4D.Localization.pas
│   ├── Validator4D.Localization.BuiltIn.pas
│   ├── Validator4D.Internal.ControlRegistry.pas
│   └── Validator4D.Internal.Rtti.pas
│
└── tests/
    ├── project/
    │   ├── Validator4D.Tests.dpr
    │   └── Validator4D.Tests.dproj
    └── src/
        ├── Validator4D.Tests.All.pas
        ├── Validator4D.Tests.Callbacks.pas
        ├── Validator4D.Tests.CatalogIntegrity.pas
        ├── Validator4D.Tests.CustomRules.pas
        ├── Validator4D.Tests.DateTimeRules.pas
        ├── Validator4D.Tests.Events.pas
        ├── Validator4D.Tests.Exceptions.pas
        ├── Validator4D.Tests.FluentApi.pas
        ├── Validator4D.Tests.FMX.Input.pas
        ├── Validator4D.Tests.FMX.InputRegression.pas
        ├── Validator4D.Tests.FMX.Presentation.pas
        ├── Validator4D.Tests.Helpers.pas
        ├── Validator4D.Tests.InternetRules.pas
        ├── Validator4D.Tests.JapaneseRules.pas
        ├── Validator4D.Tests.Lifetime.pas
        ├── Validator4D.Tests.ListBoxSelection.pas
        ├── Validator4D.Tests.Localization.JsonErrors.pas
        ├── Validator4D.Tests.Localization.pas
        ├── Validator4D.Tests.MaskRules.pas
        ├── Validator4D.Tests.MaxLength.pas
        ├── Validator4D.Tests.NumberRules.pas
        ├── Validator4D.Tests.SelectionRules.pas
        ├── Validator4D.Tests.Smoke.pas
        ├── Validator4D.Tests.TextRules.pas
        └── Validator4D.Tests.ValidateAll.pas

Design decisions

Why one validator per form?

Because a single validator provides a central decision point. Rules can be registered from many helper units, but ownership stays centralized. Splitting a form across multiple IFormValidator instances would fragment the final decision.

Why separate rules from presentation?

Because real forms have different layout needs. Some need automatically created labels; others need design-time labels; others need compact display using the title itself as the error area. Keeping presentation configurable avoids locking in a single visual model.

Why separate input assistance from rules?

Because mask, keyboard, and return key help the user type, but they are not the validation contract. A mask restricts format during typing; a rule defines what is accepted as valid. Mixing the two concepts locks in the mechanism's flexibility.

Why JSON as overlay instead of requiring complete files?

Because partial JSON files are easier to maintain. A project can override only the messages that matter and let the rest come from the active embedded catalog.

Why not include CPF, CNPJ, ZIP, VAT, etc. in the core?

Because regional validation rules can grow without bound and may require jurisdiction-specific maintenance. The core provides Pattern for simple formats and Custom for rules with algorithms. Regional packs can be optional and maintained separately.

Why preserve existing FMX event handlers?

Because real FMX forms frequently already have events associated with the screen (search, navigation, synchronization). Attaching validation bridges without erasing previous handlers allows Validator4D to be added to existing forms without breaking behavior.

Why lifetime tracking via FreeNotification?

Because controls can be created and destroyed dynamically at runtime. Destroying a control should not break the validator. FreeNotification is the natural FMX mechanism for that observation, and Validator4D uses it across three layers (Core, FMX Input, FMX Presentation).


Scope and limitations

This version of Validator4D focuses on:

  • FMX form validation;
  • explicit contracts via fluent API;
  • configurable visual presentation;
  • integrated localization;
  • preservation of existing events;
  • safe control lifetime.

It does not intend to:

  • replace server-side validation or domain rules;
  • replace database constraints;
  • implement every FMX styling detail;
  • guarantee identical visual behavior across all platforms;
  • generate dynamic forms from metadata;
  • include jurisdiction-specific regional rules in the core;
  • replace complete UI frameworks.

This scope boundary is intentional.


Documentation

Additional documentation:


Versioning

The project follows Semantic Versioning.


License

MIT License — see LICENSE.

Copyright (c) 2026 Eduardo P. Araujo

Releases

Packages

Contributors

Languages