Control-Entwicklung

Workflow zur Erstellung neuer UI-Controls für die WvdS FPC RAD Suite.

Controls müssen für Design-Time (VS Code Designer) und Runtime (Desktop/TUI/Web) funktionieren.

Übersicht

Ein Control besteht aus mehreren Schichten:

Schicht Zweck Pfad
Runtime Model Target-neutrale Logik ~/sources/common/ui/controls/{category}/
Target Renderer Target-spezifische Darstellung ~/sources/common/ui/targets/{target}/renderers/
Pack Manifest PXAML-Tag → Klasse Mapping ~/packs/controls/{control_id}/
Designer Metadata Toolbox, Properties, Events ~/sources/extensions/wvds.vscode.ui.meta/

Variablen

Beim Erstellen eines Controls diese Variablen festlegen:

Variable Beispiel Beschreibung
{CONTROL_ID} dateedit Eindeutiger Identifier (lowercase)
{CONTROL_CLASS} TWvdSDateEdit Pascal-Klassenname
{PXAML_TAG} DateEdit Tag-Name im PXAML
{PXAML_NS} wvds Namespace (wvds für interne Controls)
{TARGETS} tui,desktop,web Unterstützte Targets

Workflow (7 Schritte)

Schritt 1: Contract Freeze

Definiere die öffentliche API des Controls:

Properties:

(* Minimaler Satz öffentlicher Properties *)
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:

(* Erforderliche Events *)
property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged;
property OnValidating: TWvdSValidateEvent read FOnValidating write FOnValidating;

TargetCaps:

Capability TUI Desktop Web
Keyboard Required Required Required
Mouse Optional Required Required
Touch N/A Optional Optional
Popup Optional Required Required

Schritt 2: Runtime Model Unit

Erstelle die target-neutrale Model-Unit:

Pfad: ~/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(Datums-Eingabefeld mit Kalender-Popup)
 
    Target-neutrales Model für DateEdit.
    Enthält KEINE Rendering- oder Host-Abhängigkeiten.
  *)
  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
 
(* Implementierung ohne Target-spezifischen Code *)
 
end.
Keine Target-spezifischen Abhängigkeiten! Kein DOM, kein Terminal-I/O, keine LCL-Units.

Schritt 3: Target Renderer Units

Für jedes Target in {TARGETS} einen Renderer erstellen:

TUI Renderer

Pfad: ~/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
  (* Keyboard-Handling für Datumsänderung *)
end;
 
end.

Desktop Renderer

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

Web Renderer

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

Schritt 4: Pack Manifest

Erstelle das Pack-Manifest für den PXAML-Resolver:

Pfad: ~/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"]
  }
}

Schritt 5: Designer Metadata

Erstelle Designer-Metadaten für Toolbox und Properties:

Descriptor hinzufügen zu: ~/sources/common/core/meta/WvdS.Meta.Controls.Editors.pas

procedure RegisterDateEditMeta(ARegistry: TMetaRegistry);
var
  TypeMeta: TTypeMeta;
begin
  TypeMeta := CreateTypeMeta('DateEdit', 'TWvdSDateEdit', 'Editors');
  TypeMeta.Description := 'Datums-Eingabefeld mit Kalender-Popup';
  TypeMeta.Icon := 'calendar';
 
  (* Properties *)
  TypeMeta.AddProperty(CreatePropertyMeta('Value', pkDateTime, 'Aktueller Datumswert'));
  TypeMeta.AddProperty(CreatePropertyMeta('Format', pkString, 'Anzeigeformat (z.B. yyyy-mm-dd)'));
  TypeMeta.AddProperty(CreatePropertyMeta('MinDate', pkDateTime, 'Frühestes erlaubtes Datum'));
  TypeMeta.AddProperty(CreatePropertyMeta('MaxDate', pkDateTime, 'Spätestes erlaubtes Datum'));
  TypeMeta.AddProperty(CreatePropertyMeta('ReadOnly', pkBoolean, 'Nur-Lesen Modus'));
 
  (* Events *)
  TypeMeta.AddEvent(CreateEventMeta('OnValueChanged', 'Wird ausgelöst wenn Value sich ändert'));
  TypeMeta.AddEvent(CreateEventMeta('OnValidating', 'Wird vor Wertänderung ausgelöst'));
 
  ARegistry.RegisterType(TypeMeta);
end;

Schritt 6: Registry-Registrierung

Registriere das Control in den Runtime-Registries:

PXAML Resolver

Der pxamlc Compiler liest pack.wvds.json automatisch aus ~/packs/.

Target Runtime Registry

Für jedes Target die Renderer-Registry aktualisieren:

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

Schritt 7: Dokumentation

API-Dokumentation für das Control erstellen:

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

# TWvdSDateEdit
 
Datums-Eingabefeld mit optionalem Kalender-Popup.
 
## Wann verwenden
 
- Benutzer soll ein Datum eingeben
- Datumsbereich einschränken (MinDate/MaxDate)
- Formatierte Datumsanzeige erforderlich
 
## Properties
 
| Property | Typ | Beschreibung |
|----------|-----|--------------|
| Value | TDateTime | Aktueller Datumswert |
| Format | string | Anzeigeformat |
| MinDate | TDateTime | Frühestes erlaubtes Datum |
| MaxDate | TDateTime | Spätestes erlaubtes Datum |
| ReadOnly | Boolean | Nur-Lesen Modus |
 
## Events
 
| Event | Beschreibung |
|-------|--------------|
| OnValueChanged | Wird ausgelöst wenn Value sich ändert |
| OnValidating | Erlaubt Validierung vor Wertänderung |
 
## Beispiel
 
```xml
<DateEdit Value="{Binding BirthDate}"
          Format="dd.MM.yyyy"
          MinDate="1900-01-01"
          MaxDate="2100-12-31" />
```

Completion Criteria

Ein Control ist fertig wenn:

[ ] VS Code Designer kann {PXAML_NS}:{PXAML_TAG} platzieren
[ ] Properties im Designer editierbar
[ ] Preview im Designer funktioniert
[ ] Target Build (tui/desktop/web) kompiliert erfolgreich
[ ] Pack Manifest vorhanden und vom Resolver erkannt
[ ] API-Dokumentation geschrieben

Siehe auch

Zuletzt geändert: den 29.01.2026 um 15:13