====== 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
```
===== 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 =====
* [[.:pxaml-pipeline|PXAML-Pipeline]]
* [[.:pack-struktur|Pack-Struktur]]
* [[.:targets|Build-Targets]]
* [[.:meta-api|Meta API]]