Sviluppo controlli

Workflow per la creazione di nuovi controlli UI per la suite WvdS FPC RAD.

I controlli devono funzionare sia in design-time (VS Code Designer) che in runtime (Desktop/TUI/Web).

Panoramica

Un controllo e composto da diversi strati:

Strato Scopo Percorso
Runtime Model Logica target-neutrale ~/sources/common/ui/controls/{category}/
Target Renderer Rappresentazione target-specifica ~/sources/common/ui/targets/{target}/renderers/
Pack Manifest Mapping tag PXAML → classe ~/packs/controls/{control_id}/
Designer Metadata Toolbox, properties, eventi ~/sources/extensions/wvds.vscode.ui.meta/

Variabili

Definire queste variabili durante la creazione di un controllo:

Variabile Esempio Descrizione
{CONTROL_ID} dateedit Identificatore univoco (minuscolo)
{CONTROL_CLASS} TWvdSDateEdit Nome classe Pascal
{PXAML_TAG} DateEdit Nome tag nel PXAML
{PXAML_NS} wvds Namespace (wvds per controlli interni)
{TARGETS} tui,desktop,web Target supportati

Workflow (7 passaggi)

Passaggio 1: Contract Freeze

Definire l'API pubblica del controllo:

Properties:

(* Set minimo di properties pubbliche *)
property Value: TDateTime read FValue write SetValue;
property Format: string read FFormat write SetFormat;
property MinDate: TDateTime read FMinDate write FMinDate;
property MaxDate: TDateTime read FMaxDate write FMaxDate;
property ReadOnly: Boolean read FReadOnly write FReadOnly;

Events:

(* Eventi richiesti *)
property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged;
property OnValidating: TWvdSValidateEvent read FOnValidating write FOnValidating;

TargetCaps:

Capability TUI Desktop Web
Keyboard Richiesto Richiesto Richiesto
Mouse Opzionale Richiesto Richiesto
Touch N/A Opzionale Opzionale
Popup Opzionale Richiesto Richiesto

Passaggio 2: Unit Runtime Model

Creare la unit model target-neutrale:

Percorso: ~/sources/common/ui/controls/{category}/WvdS.UI.Controls.{Category}.{ControlId}.pas

unit WvdS.UI.Controls.Editors.DateEdit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  WvdS.UI.Core.Base,
  WvdS.UI.Core.Events;
 
type
  (*
    @abstract(Campo di input data con popup calendario)
 
    Model target-neutrale per DateEdit.
    NON contiene dipendenze di rendering o host.
  *)
  TWvdSDateEdit = class(TWvdSControl)
  private
    FValue: TDateTime;
    FFormat: string;
    FOnValueChanged: TWvdSNotifyEvent;
    procedure SetValue(AValue: TDateTime);
  protected
    procedure DoValueChanged; virtual;
  public
    constructor Create(AOwner: TWvdSComponent); override;
    property Value: TDateTime read FValue write SetValue;
    property Format: string read FFormat write FFormat;
    property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged;
  end;
 
implementation
 
(* Implementazione senza codice target-specifico *)
 
end.
Nessuna dipendenza target-specifica! No DOM, no Terminal I/O, no unit LCL.

Passaggio 3: Unit Target Renderer

Creare un renderer per ogni target in {TARGETS}:

Renderer TUI

Percorso: ~/sources/common/ui/targets/tui/renderers/WvdS.UI.TUI.Renderers.DateEdit.pas

unit WvdS.UI.TUI.Renderers.DateEdit;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  WvdS.UI.TUI.Renderer.Base,
  WvdS.UI.Controls.Editors.DateEdit;
 
type
  TWvdSTUIDateEditRenderer = class(TWvdSTUIRenderer)
  public
    procedure Measure(AControl: TWvdSControl; out AWidth, AHeight: Integer); override;
    procedure Paint(AControl: TWvdSControl; ACanvas: TWvdSTUICanvas); override;
    procedure HandleInput(AControl: TWvdSControl; AEvent: TWvdSInputEvent); override;
  end;
 
implementation
 
procedure TWvdSTUIDateEditRenderer.Measure(AControl: TWvdSControl; out AWidth, AHeight: Integer);
begin
  AWidth := 12;  (* [YYYY-MM-DD] *)
  AHeight := 1;
end;
 
procedure TWvdSTUIDateEditRenderer.Paint(AControl: TWvdSControl; ACanvas: TWvdSTUICanvas);
var
  DateEdit: TWvdSDateEdit;
begin
  DateEdit := AControl as TWvdSDateEdit;
  ACanvas.DrawText(0, 0, FormatDateTime(DateEdit.Format, DateEdit.Value));
end;
 
procedure TWvdSTUIDateEditRenderer.HandleInput(AControl: TWvdSControl; AEvent: TWvdSInputEvent);
begin
  (* Gestione tastiera per modifica data *)
end;
 
end.

Renderer Desktop

Percorso: ~/sources/common/ui/targets/gui/renderers/WvdS.UI.Desktop.Renderers.DateEdit.pas

Renderer Web

Percorso: ~/sources/common/ui/targets/web/renderers/WvdS.UI.Web.Renderers.DateEdit.pas

Passaggio 4: Pack Manifest

Creare il manifesto pack per il resolver PXAML:

Percorso: ~/packs/controls/{control_id}/pack.wvds.json

{
  "id": "dateedit",
  "vendor": "wvds",
  "version": "0.1.0",
  "xml": {
    "namespace": "wvds",
    "tag": "DateEdit"
  },
  "model": {
    "unit": "WvdS.UI.Controls.Editors.DateEdit",
    "class": "TWvdSDateEdit"
  },
  "renderers": {
    "tui": {
      "unit": "WvdS.UI.TUI.Renderers.DateEdit",
      "class": "TWvdSTUIDateEditRenderer"
    },
    "desktop": {
      "unit": "WvdS.UI.Desktop.Renderers.DateEdit",
      "class": "TWvdSDesktopDateEditRenderer"
    },
    "web": {
      "unit": "WvdS.UI.Web.Renderers.DateEdit",
      "class": "TWvdSWebDateEditRenderer"
    }
  },
  "targetCaps": {
    "requires": ["keyboard"],
    "optional": ["mouse", "popup"]
  }
}

Passaggio 5: Designer Metadata

Creare metadati designer per toolbox e properties:

Aggiungere descriptor a: ~/sources/common/core/meta/WvdS.Meta.Controls.Editors.pas

procedure RegisterDateEditMeta(ARegistry: TMetaRegistry);
var
  TypeMeta: TTypeMeta;
begin
  TypeMeta := CreateTypeMeta('DateEdit', 'TWvdSDateEdit', 'Editors');
  TypeMeta.Description := 'Campo di input data con popup calendario';
  TypeMeta.Icon := 'calendar';
 
  (* Properties *)
  TypeMeta.AddProperty(CreatePropertyMeta('Value', pkDateTime, 'Valore data corrente'));
  TypeMeta.AddProperty(CreatePropertyMeta('Format', pkString, 'Formato visualizzazione (es. yyyy-mm-dd)'));
  TypeMeta.AddProperty(CreatePropertyMeta('MinDate', pkDateTime, 'Data minima consentita'));
  TypeMeta.AddProperty(CreatePropertyMeta('MaxDate', pkDateTime, 'Data massima consentita'));
  TypeMeta.AddProperty(CreatePropertyMeta('ReadOnly', pkBoolean, 'Modalita sola lettura'));
 
  (* Events *)
  TypeMeta.AddEvent(CreateEventMeta('OnValueChanged', 'Attivato quando Value cambia'));
  TypeMeta.AddEvent(CreateEventMeta('OnValidating', 'Attivato prima del cambio valore'));
 
  ARegistry.RegisterType(TypeMeta);
end;

Passaggio 6: Registrazione Registry

Registrare il controllo nelle registry runtime:

PXAML Resolver

Il compilatore pxamlc legge automaticamente pack.wvds.json da ~/packs/.

Target Runtime Registry

Aggiornare la registry renderer per ogni target:

(* In WvdS.UI.TUI.Render.Registry.pas *)
procedure RegisterTUIRenderers;
begin
  (* ... altri renderer ... *)
  RegisterRenderer(TWvdSDateEdit, TWvdSTUIDateEditRenderer);
end;

Passaggio 7: Documentazione

Creare documentazione API per il controllo:

Percorso: ~/docs/api/controls/dateedit.md

# TWvdSDateEdit
 
Campo di input data con popup calendario opzionale.
 
## Quando usare
 
- L'utente deve inserire una data
- Limitare l'intervallo date (MinDate/MaxDate)
- Richiesta visualizzazione data formattata
 
## Properties
 
| Property | Tipo | Descrizione |
|----------|------|-------------|
| Value | TDateTime | Valore data corrente |
| Format | string | Formato visualizzazione |
| MinDate | TDateTime | Data minima consentita |
| MaxDate | TDateTime | Data massima consentita |
| ReadOnly | Boolean | Modalita sola lettura |
 
## Events
 
| Evento | Descrizione |
|--------|-------------|
| OnValueChanged | Attivato quando Value cambia |
| OnValidating | Permette validazione prima del cambio valore |
 
## Esempio
 
```xml
<DateEdit Value="{Binding BirthDate}"
          Format="dd.MM.yyyy"
          MinDate="1900-01-01"
          MaxDate="2100-12-31" />
```

Criteri di completamento

Un controllo e completo quando:

[ ] VS Code Designer puo posizionare {PXAML_NS}:{PXAML_TAG}
[ ] Properties modificabili nel designer
[ ] Preview nel designer funzionante
[ ] Build target (tui/desktop/web) compila con successo
[ ] Pack manifest presente e riconosciuto dal resolver
[ ] Documentazione API scritta

Vedi anche

Zuletzt geändert: il 29/01/2026 alle 22:31