Net-Base Maġazin

03.06.2026

Layouts responsivi fil Delphi FMX: Breakpoints mingħajr kaos tal-disinjatur (bi Layout-Router bħala snippet tal-kodiċi)

Responsive Layouts FMX isiru malajr fragli fil-prattika: serje ta' eventijiet ta' resize, bidliet fil-DPI, rotazzjoni u „Visible-Layouts“ joħolqu stat doppju u reflows li huma diffiċli biex jiġu debugjati. Din il-kariga turi Layout-Router b'Breakpoints li jikkontrolla l-UI-blokki fil-ħin tal-eżekuzzjoni...

03.06.2026

Minn suġġett tar-rivista għall-prattika tal-proġett

Paġni ta' servizz u paġni tekniċi relevanti għall-artiklu

Min jidħol f‘Delphi FireMonkey biex jimmaniġġja diversi formfatti, isib ruħu malajr f‘Responsive Layouts FMX – u kważi immedjatament f’miżgħa ta‘ kaskati ta‘ Align, contenituri tal-layout moħbija u workaround tal-designer li jinqasmu fil-bidla li jmiss ta‘ DPI jew rotazzjoni. F’kliens ta‘ software ta‘ intrapriża li żviluppa maż-żmien dan huwa partikolarment inkwetanti: il-UI tiġi żviluppata, it-timijiet jinbidlu, u f’daqqa waħda l-logika tkun fuq dettalji viżivi.

Il-qalba tal-problema: FireMonkey jipprovdi bosta baċiri (eż. Align, Anchors, TScaledLayout, TFlowLayout, TGridPanelLayout), imma m’hemm l-ebda sistema ta‘ Breakpoints “nativa” bħalma hemm fil-web. Tista‘ tirreaġixxi għall-bidliet fid-daqs, imma mingħajr arkitettura ċara dan jispiċċa f’“if Width < … then …” distribwit fuq ħafna forms.

Dan l-artiklu juri Layout-Router: komponent żgħir li jimmaniġġja breakpoints b’mod ċentrali u jeħoddu Controls (jew blokki sħaħ tal-layout) bejn Slots ippreparati. L-għan: l-istati jinżammu, il-kodiċi jkun manutenzjonabbli, u każi fringe bħar-rotazzjoni, layouts imxerrda u re-entrancy jiġu mitgewati. Hemm ukoll ftit trikks inqas ovvji li fil-prattika jagħmlu d-differenza bejn “jopera fid-demo” u “jopera b’mod stabbli fil-produzzjoni”.

Għaliex Breakpoints f’FMX huma differenti mill-web

Fil-layouts tal-web il-breakpoints huma spiss deklarattivi (CSS Media Queries). F’FMX id-deċiżjonijiet tal-layout jiċċirkolaw tipikament imperattivament fit-tul tal-ħin: fil-OnResize isir il-bidla. Għal dan jżidu l-karatteristiċi speċifiċi tal-pjattaforma:

  • Device-Pixel vs. logische Pixel: ClientWidth/ClientHeight huma f’unitajiet loġiċi (skont is-skalazzjoni). Bidliet fil-DPI (eż. Windows Per-Monitor-DPI) jistgħu jikkawżaw it-twessigħ mill-ġdid tal-layouts anke meta xejn ma nbidel “fisikament”.
  • Rotazzjoni u Safe Areas: Pjattaformi mobili jipprovdu insets (Notch/Safe Area) – skont l-OS u d-device. “Breakpoint biss skont il-wisa’” spiss għandu viżjoni ristretta, għax l-ispazju utilisabbli jista‘ jkun iżgħar mid-daqs totali tal-finestra.
  • Pass tal-Layout: FireMonkey jikkalkula l-layouts f’fażijiet. Jekk jinbdel Parent/Align fil-mument mhux xieraq, jinqalgħu effett sekondarju (eż. ripetut reflow jew tibdil kontinwu fid-daqs li juri ‘flicker’).

Layout-Router jindirizza dan billi (1) jeviti l-„meta“ (Resize/Scale/Rotation) mill-„kif“ (regoli tal-layout) u (2) jikkonċentra r-regoli f’post wieħed. Għal mexxejja tekniċi l-iktar effett importanti hu: ikollhom ċentru ta‘ deċiżjoni ċar u eżaminabbli minflok ħafna każijiet speċjali lokali.

Arkitettura: Layout-Router ma‘ Slots minflok il-ħolqien ta‘ Controls

It-trick nadif għal FMX: mhux terġa‘ toħloq Controls b’mod dinamiku, imma terfa‘ controlli eżistenti bejn Slots. Slot huwa sempliċement container (eż. TLayout) li jirrappreżenta żona tal-UI: kolonna laterali, bar tal-għodda, kontenut, footer, panel tad-dettalji.

Vantaġġi f’software ta‘ intrapriża personalizzata:

  • L-istati jinżammu (Edit-Text, posizzjoni tal-iskroll, oġġetti magħżula), għax l-istanzi ma jerġgħux jinħolqu mill-ġdid.
  • Inqas riskju ta‘ doppja konnessjoni ta‘ events, timers jew bindings.
  • Ir-regoli tal-layout isiru viżibbli: “liema blokk jinsab f’liema slot” jista‘ jiġi segwit u reviżjonat għal kull breakpoint.

Importanti għall-prattika: Aqta‘ l-blokki tal-UI biżżejjed b’mod ġenerali. Jekk tgħawweġ 30 kontroll individwali, il-lista tar-rotta stess saret sors ta‘ żbalji. Aħjar huma kontenituri bħal layFilterBar, layNavigation, layResultList, layDetails.

Snippet tas-sors: Breakpoint-Router għal Responsive Layouts FMX

Il-kodiċi li ġej huwa maħsub bħala unità ta‘ għajnuna li tista‘ tuża f’FMX-Forms. Huwa jikkalkula breakpoint (XS/SM/MD/LG/XL) u jpoġġi kontrolli definiti fi kontenituri ta‘ slot speċifiċi. Dettalji importanti:

  • Debounce permezz ta‘ TThread.ForceQueue: diversi eventi ta‘ Resize jiġu mħallta f’aġġornament wieħed (inqas jitters tal-UI, inqas ċikli ta‘ reflow).
  • Protezzjoni kontra r-re-entrancy: l-aġġornament tal-layout spiss jikkawża l-ħruġ mill-ġdid ta‘ eventi Resize/Layout.
  • Optionali: Orientazzjoni (Portrait/Landscape) tista‘ tidħol fil-loġika tal-breakpoint.
Delphi
unit NB.FMX.LayoutRouter;

interface

uses
  System.Classes, System.SysUtils, System.Types, System.Generics.Collections,
  FMX.Types, FMX.Controls;

type
  TNBLayoutBreakpoint = (bpXS, bpSM, bpMD, bpLG, bpXL);

  // Mappa: liema Control għandu jittieħed f'liema Slot (container) għal kull Breakpoint.
  TNBRoute = record
    Control: TControl;
    TargetSlot: TControl; // tipikament TLayout jew TPresentedControl
    Align: TAlignLayout;
  end;

  TNBRouteList = TList<TNBRoute>;

  TNBGetBreakpointEvent = reference to function(const AClientSize: TSizeF): TNBLayoutBreakpoint;

  TNBLayoutRouter = class(TComponent)
  private
    FRoot: TControl;
    FPending: Boolean;
    FUpdating: Boolean;
    FCurrent: TNBLayoutBreakpoint;
    FOnGetBreakpoint: TNBGetBreakpointEvent;
    FRoutes: TObjectDictionary<Integer, TNBRouteList>;
    function KeyOf(const ABp: TNBLayoutBreakpoint): Integer;
    procedure RootResized(Sender: TObject);
    procedure ApplyPending;
    procedure ApplyRoutes(const ABp: TNBLayoutBreakpoint);
    function DefaultBreakpoint(const AClientSize: TSizeF): TNBLayoutBreakpoint;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure AttachRoot(const ARoot: TControl);
    procedure DefineRoute(const ABp: TNBLayoutBreakpoint; const AControl, ASlot: TControl;
      const AAlign: TAlignLayout = TAlignLayout.Client);

    procedure Invalidate; // iġġedded manwalment
    property Current: TNBLayoutBreakpoint read FCurrent;
    property OnGetBreakpoint: TNBGetBreakpointEvent read FOnGetBreakpoint write FOnGetBreakpoint;
  end;

implementation

{ TNBLayoutRouter }

constructor TNBLayoutRouter.Create(AOwner: TComponent);
begin
  inherited;
  FRoutes := TObjectDictionary<Integer, TNBRouteList>.Create([doOwnsValues]);
  FCurrent := bpMD;
end;

destructor TNBLayoutRouter.Destroy;
begin
  if Assigned(FRoot) then
    FRoot.OnResize := nil;
  FRoutes.Free;
  inherited;
end;

procedure TNBLayoutRouter.AttachRoot(const ARoot: TControl);
begin
  if FRoot = ARoot then
    Exit;

  if Assigned(FRoot) then
    FRoot.OnResize := nil;

  FRoot := ARoot;
  if Assigned(FRoot) then
    FRoot.OnResize := RootResized;

  Invalidate;
end;

procedure TNBLayoutRouter.DefineRoute(const ABp: TNBLayoutBreakpoint; const AControl,
  ASlot: TControl; const AAlign: TAlignLayout);
var
  LKey: Integer;
  LList: TNBRouteList;
  LRoute: TNBRoute;
begin
  if (AControl = nil) or (ASlot = nil) then
    raise EArgumentNilException.Create('Control/Slot ma jistgħux ikunu nil');

  LKey := KeyOf(ABp);
  if not FRoutes.TryGetValue(LKey, LList) then
  begin
    LList := TNBRouteList.Create;
    FRoutes.Add(LKey, LList);
  end;

  LRoute.Control := AControl;
  LRoute.TargetSlot := ASlot;
  LRoute.Align := AAlign;
  LList.Add(LRoute);
end;

function TNBLayoutRouter.KeyOf(const ABp: TNBLayoutBreakpoint): Integer;
begin
  Result := Ord(ABp);
end;

procedure TNBLayoutRouter.RootResized(Sender: TObject);
begin
  Invalidate;
end;

procedure TNBLayoutRouter.Invalidate;
begin
  if (FRoot = nil) or FUpdating then
    Exit;

  // Debounce: applikat darba biss kull ċiklu tal-messaġġi
  if FPending then
    Exit;

  FPending := True;
  TThread.ForceQueue(nil,
    procedure
    begin
      ApplyPending;
    end);
end;

procedure TNBLayoutRouter.ApplyPending;
var
  LBp: TNBLayoutBreakpoint;
  LSize: TSizeF;
begin
  if (FRoot = nil) then
    Exit;

  if not FPending then
    Exit;

  FPending := False;

  LSize := TSizeF.Create(FRoot.Width, FRoot.Height);

  if Assigned(FOnGetBreakpoint) then
    LBp := FOnGetBreakpoint(LSize)
  else
    LBp := DefaultBreakpoint(LSize);

  if LBp = FCurrent then
    Exit;

  ApplyRoutes(LBp);
  FCurrent := LBp;
end;

procedure TNBLayoutRouter.ApplyRoutes(const ABp: TNBLayoutBreakpoint);
var
  LList: TNBRouteList;
  LRoute: TNBRoute;
begin
  if FUpdating then
    Exit;

  FUpdating := True;
  try
    if not FRoutes.TryGetValue(KeyOf(ABp), LList) then
      Exit;

    // Attenzjoni: bidla fil-Parent tibdel il-Z-Order.
    // Jekk il-ordni hija rilevanti, sejjaħ DefineRoute fil-ordni mixtieqa.
    for LRoute in LList do
    begin
      if (LRoute.Control.Parent <> LRoute.TargetSlot) then
        LRoute.Control.Parent := LRoute.TargetSlot;

      // L-Align għandu jitqiegħed biss wara li jiġu stabbiliti l-Parent, inkella l-Bounds jistgħu jiġu interpretati differenti.
      LRoute.Control.Align := LRoute.Align;
      LRoute.Control.Visible := True;
    end;

  finally
    FUpdating := False;
  end;
end;

function TNBLayoutRouter.DefaultBreakpoint(const AClientSize: TSizeF): TNBLayoutBreakpoint;
var
  W: Single;
begin
  W := AClientSize.cx;
  // Breakpoints intenzjonalment ġeneralizzati, għax il-pjattaformi ta' FMX jvarjaw b'mod sinifikanti.
  if W < 480 then Exit(bpXS);
  if W < 768 then Exit(bpSM);
  if W < 1024 then Exit(bpMD);
  if W < 1440 then Exit(bpLG);
  Result := bpXL;
end;

end.

Kif tuża r-Router f’Form

Tiddedefinixxi slots bħala TLayout (eż. layTop, layLeft, layContent) u mbagħad tirreġistra għal kull Breakpoint fejn jinsabu l-blokki. B’mod tipiku Sidebar u Details-Pane f’breakpoints żgħar jiċċaqilqu waħda taħt l-oħra.

Delphi
procedure TFrmMain.FormCreate(Sender: TObject);
begin
  FRouter := TNBLayoutRouter.Create(Self);
  FRouter.AttachRoot(LayoutRoot); // z. B. ein TLayout, das Client-aligned ist

  // XS: alles untereinander
  FRouter.DefineRoute(bpXS, layToolbar, slotTop, TAlignLayout.Top);
  FRouter.DefineRoute(bpXS, laySidebar, slotContent, TAlignLayout.Top);
  FRouter.DefineRoute(bpXS, layDetails, slotContent, TAlignLayout.Top);
  FRouter.DefineRoute(bpXS, layMain,    slotContent, TAlignLayout.Client);

  // MD: Sidebar links, Details rechts
  FRouter.DefineRoute(bpMD, layToolbar, slotTop,    TAlignLayout.Top);
  FRouter.DefineRoute(bpMD, laySidebar, slotLeft,   TAlignLayout.Client);
  FRouter.DefineRoute(bpMD, layMain,    slotCenter, TAlignLayout.Client);
  FRouter.DefineRoute(bpMD, layDetails, slotRight,  TAlignLayout.Client);

  // Optional: eigene Breakpoint-Logik
  FRouter.OnGetBreakpoint :=
    function(const S: TSizeF): TNBLayoutBreakpoint
    begin
      if (S.cx < 700) or (S.cy < 420) then
        Result := bpSM
      else
        Result := bpMD;
    end;
end;

Kuntest: Waarom „Umhängen“ spiss ikun aktar stabbli minn Visible-Schalten

Ħsieb komuni hu li żżomm siġar tal-layout separati għal kull varjant u biss tagħmel toggle lil Visible. Dan jidher komdu fid-designer, iżda għandu effetti sekondarji tipici:

  • Binding/Events doppju: Żewġ controls simili jeħtieġ li jinżammu sinkronizzati (eż. żewġ barra ta‘ filtru).
  • Ordni tat-Tab u Fokus: Meta tinbidel, titilfu l-fokus jew tasal f’controls moħbija jekk TabStop/HitTest mhux f’kundizzjoni favorevoli.
  • State Drift: Pożizzjonijiet ta‘ scroll, stati ta‘ selezzjoni jew test editjat jistgħu jiddiferenzjaw.

It-„Umhängen“ iżomm l-istanza distint. Huwa importanti li taqta‘ l-blokki tal-layout b’mod li jistgħu jitpoġġew indipendentement (pereż., „Sidebar“ bħala container separat minflok bosta controls individwali). Dan iħallas ruhu fil-manutenzjoni u fl-analiżi tal-iżbalji: inti tidentifika u tissolva żbalji fuq istanza waħda, mhux fuq żewġ UIs skuri paralleli.

Stolperfallen in der Praxis (und wie man sie debuggt)

1) Resize-Stürme und Re-Entrancy

FMX jattiva OnResize mhux biss waqt user-resize, iżda wkoll waqt bidliet ta‘ style, bidliet tal-parent u xi drabi bidliet fil-DPI. Mingħajr mekkanizmu ta‘ debounce l-app tista‘ tinżamm f’loops ta‘ layout. Ir-router juża TThread.ForceQueue biex joffri l-bidliet fil-UI-tick li jmiss.

Parir għall-debugging: logging (pereż. permezz ta‘ OutputDebugString) b’Breakpoint, daqs u kontatur ta‘ aġġornamenti jgħin biex jinstabu loopijiet ta‘ reflow. Jekk teżegwixxi wkoll log tal-ħin meta ApplyRoutes tibda u tispiċċa, tara malajr jekk resize wieħed qed jikkawża kaskata.

2) Z-Order, HitTest und „unsichtbare“ Klick-Blocker

Bidliet tal-parent jibdlu l-Z-Order. Jekk overlays (pereż., Flyouts) ma jġibu aktar klikks, spiss huwa minħabba li container client-aligned jinsab fuqhom u HitTest huwa attiv. Varjanti: għall-oqsma ta‘ overlay ipproġetta slot separat fuq nett u parentja biss dawk il-controls hemmhekk. Fil-FMX, HitTest (jekk control jinterċetta eventi tal-maws/touch) spiss ikun il-kawża aktar milli l-viżibilità.

3) TGridPanelLayout und prozentuale Größen

TGridPanelLayout jista‘ jħalli reberekalkuli mhux mistennija meta jintuża ma‘ kolonni/linji procentwali flimkien ma‘ Align=Client u b’ċirkostanzi ta‘ reparenting dinamiku. Jekk trid tuża Grid, poġġih f’slot u imbagħad ibiddlu biss blokki sħaħ tal-Grid, mhux it-tfal tal-Grid. Dan jnaqqas il-kombinatorika tal-passi tal-layout.

4) Fokus, tastiera virtwali u kampjiet ta‘ input li „jitilgħu“

Każ limiti li jseħħ f’apps FMX fuq mobbli u anke fuq Windows-tablets: waqt ir-reparenting control ta‘ edit li għandu fokus jista‘ jitlef il-parent temporanjament. Dan jista‘ jagħlaq it-tastiera virtwali jew issettja mill-ġdid il-kursor. Prattiċi li ħadmu tajjeb: qabel il-routing issalva temporanjament il-fokus attwali (Focused/IFMXFocusControl), u wara r-routing (fl-istess UI-tick) irrestawralu. Dan huwa partikolarment utli f’formoli tal-input li jibdlu bejn “żewġ kolonni” (Tablet/PC) u “kolonna waħda” (Phone).

Varjanti: Breakpoints skont il-form factor minflok biss skont il-wisa‘

F’clients multiplatform reali, il-„wisa'“ waħda biss spiss mhix is-sinjal korrett. Varjanti sensibbli:

  • Wisa‘ u għoli: twieqi ħafna rqiqa (eż. terminals tal-kassa, skrins maqsuma) jeħtieġu regoli differenti.
  • Oriëntazzjoni: Landscape fuq tablets spiss huwa simili għall-desktop, while Portrait huwa aktar simili għall-mobile.
  • Zona utilisabbli tal-safe-area: fuq iOS/Android l-għoli effettiv disponibbli jista‘ jonqos b’mod sinifikanti minħabba l-barijiet tas-sistema. Min jikkonsidra biss Height kultant jirruta ‚tard‘.

Il-router huwa maħdum b’mod deliberat sabiex tkun tista‘ tbiddel il-funzjoni tal-breakpoint. Dan huwa utli wkoll f’sitwazzjonijiet legacy, meta l-istess form tkun qed tħaddem f’hosts multipli (eż., darba bħala tieqa normali, darba f’kontenitur imqiegħed ġewwa).

Straordinarjament nadif: Layout-Routing bħala „Transazzjoni”

F’screens kbar il-problema spiss ma tinsabx biss fil-breakpoints, iżda fl-ordni tal-operazzjonijiet tal-UI. Mudell prattiku huwa li trattah il-routing bħala Transazzjoni: l-ewwel iddeċiedi, imbagħad tagħmel ir-reparenting, u mbagħad twettaq l-effetti sekondarji (viżibilità, fokus, rifreš tad-dejta) b’ordni.

B’mod konkrett: evita li controls individwali waqt ir-reparenting joħolqu events proprji li minnhom jinbdew operazzjonijiet ta‘ layout jew aċċess tad-data. F’FMX dan jista‘ jiġri, pereżempju, meta waqt il-bidla tal-parent jiġu sparati OnEnter/OnExit jew meta espressjoni ta‘ LiveBinding tiġi r-evalwata mill-ġdid minħabba bounds-update. Jekk tara effetti bħal dawn, jgħin swiċċ ċentrali „Updating“ (bħal dak fil-router) flimkien ma‘ pass post ċar: biss wara ApplyRoutes għandhom jitħaddmu operazzjonijiet kostużi (eż., terġa‘ tinżamm il-lista, tirrebindja d-dettalji).

Dan għandu importanza speċjali fuq clients b’aċċess REST: reload mhux mixtieq waqt resize jista‘ jwassal għal requests bla bżonn. Dan mhux innota f’LAN, imma jidher immedjatament fuq VPN jew fuq netwerks mobbli.

Meta l-approċċ jiswa – u fejn għandu limiti

Ir-router tal-layout jiswa jekk:

  • applikazzjoni FMX tibqa‘ tintuża għal snin u diversi żviluppaturi jaħdmu fuq l-istess screens,
  • blokkijiet tal-UI jistgħu jiġu separati b’mod ċar (Sidebar/Details/Content),
  • jeħtieġu regoli ta‘ breakpoint riproduċibbli minflok tuning ad-hoc ta‘ Align.

Tista‘ tara limitazzjonijiet meta skrin għandu jkun ħafna “fluid” (ħafna kaxxi dinamici, Masonry-Layouts reali). F’dawk il-każijiet TFlowLayout/TGridPanelLayout jew klassijiet ta‘ layout proprji huma aktar xierqa. Anki meta ħafna kontrolli individwali jgħaddu bejn slots, il-manutenzjoni tar-routi ssir mhux ċara – f’dak il-punt aħjar taqta‘ blokki ikbar jew tuża saff ta‘ konfigurazzjoni deklarattiva (eż. konfigurazzjoni JSON għall-asignazzjonijiet tal-slots li titla‘ waqt l-avviż).

Konklużjoni: Għall-Responsive Layouts f’FMX, “tqabbil mill-ġdid bl-użu ta‘ Breakpoints” huwa mezz pragmatiku: inqas kaos tad-disinn, regoli ċari, stati stabbli. Ma jemendax struttura tal-UI mħejjija sew, imma jagħtik qafas affidabbli biex tiżviluppa b’mod kontrollat il-klijenti FMX fi soluzzjonijiet korporattivi diġitali maż-żmien u fuq diversi formfactors.

Jekk f’applikazzjoni eżistenti Delphi jew FMX trid timplementa b’mod nadif arkitettura tal-layout bħal din mingħajr ma tirriskja regressjonijiet tal-UI f’ssenarji operattivi, tista‘ tiddefinixxi dan teknika magħna: iddiskutew proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Fil-kuntest professjonali, Delphi Fmx Breakpoints u Firemonkey Layout jilagħbu wkoll rwol importanti meta integrazzjonijiet, flussi tad-data u żvilupp kontinwu jeħtieġu jilagħbu tajjeb flimkien.

Iddiskuti proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Pass li jmiss

Meta suġġett jissarraf f’proġett reali, l-arkitettura, is-sistema eżistenti u l-operazzjoni għandhom jiġu kkunsidrati flimkien kmien.

Aħna nappoġġjaw mhux biss f'kwistjonijiet puntwali, iżda wkoll meta biċċiet ta' kodiċi sors, temi legacy jew ideat għal portali jridu jsiru proġett korporattiv stabbli u affidabbli.

  • L-istat attwali, l-istat tal-mira u r-riskji tekniċi jiġu vvalutati flimkien.
  • REST, aċċess għad-dejta, portali u Rollout mhux se jiġu posposti bħala konsegwenzi tardivi.
  • Tara kmieni liema triq hija sostenibbli kemm mill‑punt ta’ vista ekonomiku kif ukoll mill‑punt ta’ vista operattiv.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.