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 jixtieq jdaħħal b’mod „mal-ewwel“ kontenut modern tal-web f’software tan-negozju eżistenti, jispiċċa fuq Windows ma‘ WebView2. F‘ Delphi WebView2 FMX il-problema bażika rari tkun il-wiri ta‘ URL, iżda l-integrazzjoni nadifa f’interfaċċa FireMonkey (FMX), l-inizjalizzazzjoni affidabbli (asimkrona u bbażata fuq COM), kif ukoll il-pitfalls ta‘ Edge madwar il-fowlders tal-User-Data, downloads, debugging u komunikazzjoni robusta JS↔Delphi.
Dan il-fragment tal-kodiċi juri mudell li nippreferi għall-applikazzjonijiet faċli għall-manutenzjoni: oġġett “Host” kapsulat li jikkontrolla l-lifecycle ta‘ WebView2, u bridge definit permezz ta‘ WebMessage (JSON), minflok “ExecuteScript” arbitrju kullimkien. L-għan mhuwiex kodiċi ta‘ demo, imma komponent li jiflaħ fil-clients żviluppati.
Għaliex WebView2 f’FMX huwa differenti minn „Browser-Component drop“
WebView2 huwa API viċin COM/WinRT b’inizjalizzazzjoni asinkrona. FireMonkey jastratti l-handles ta‘ Windows, madankollu għall-WebView2 finalment ikollok bżonn parent-window reali (HWND) u inoltrazzjoni kontrollata ta‘ resize/focus. Fl-istess ħin, l-avvenimenti mhux dejjem jonqsu fejn wieħed jistenna f’FMX. Jekk tibda hawn „quick and dirty“, tipikament tikseb:
- AVs sporadiċi meta tagħlaq il-form (il-callbacks jaslu wara Destroy)
- avvenimenti ta‘ navigazzjoni f’kuntest ta‘ thread żbaljat
- problemi ta‘ persistenza/cache mhux affidabbli minħabba strategija mhux ċara tal-fowlders tal-User-Data
- l-ebda downloads jew dialoghi ta‘ download „imxekkla“
- debugging biss fuq każ minflok konfigurazzjoni mirata ta‘ remote-debug
Ir-rimedju huwa lifecycle ċar: Create → InitializeAsync → Attach → Navigate → Detach/Dispose – u limit definit bejn l-UI u l-browser-engine.
Source-Schnipsel: WebView2Host für Delphi WebView2 FMX
Il-kodiċi li ġej jispeċifika klassi Host kapsulata li (1) toħloq konfigurazzjoni tal-WebView2-Environment, (2) tgħaqqad l-objett controller ma‘ HWND, (3) tintegra l-avvenimenti ta‘ navigazzjoni u download u (4) tipprovdi bridge JS ibbażata fuq JSON permezz ta‘ WebMessageReceived. Il-kodiċi hu maħsub b’mod „arkitetturali“: jikkapsula referenzi COM, jipprevjeni callbacks li jaslu wara Destroy, u jippermetti kantunieri operattivi bħal fowlders tal-User-Data separati per-user jew per-machine.
unit WebView2Host;
interface
uses
System.SysUtils, System.Classes, System.IOUtils, System.JSON,
Winapi.Windows, Winapi.ActiveX,
FMX.Types,
WebView2, WebView2_TLB; // skond il-konfigurazzjoni: WebView2.pas jew Import-TLB
type
TWebView2JsonMessage = record
Name: string;
CorrelationId: string;
Payload: TJSONObject;
class function TryParse(const Json: string; out Msg: TWebView2JsonMessage): Boolean; static;
end;
TOnWebMessage = reference to procedure(const Msg: TWebView2JsonMessage);
TOnDownload = reference to procedure(const FileName, MimeType: string; TotalBytes: Int64);
TWebView2Host = class
private
FParentHwnd: HWND;
FUserDataFolder: string;
FEnvironment: ICoreWebView2Environment;
FController: ICoreWebView2Controller;
FWebView: ICoreWebView2;
FDestroyed: Boolean;
FOnWebMessage: TOnWebMessage;
FOnDownload: TOnDownload;
procedure EnsureNotDestroyed;
function MakeUserDataFolder: string;
// Event handler
procedure HookEvents;
procedure UnhookEvents;
procedure OnWebMessageReceived(
const sender: ICoreWebView2;
const args: ICoreWebView2WebMessageReceivedEventArgs);
procedure OnDownloadStarting(
const sender: ICoreWebView2;
const args: ICoreWebView2DownloadStartingEventArgs);
public
constructor Create(AParentHwnd: HWND; const AUserDataFolder: string = '');
destructor Destroy; override;
procedure InitializeAsync;
procedure Navigate(const Url: string);
procedure Resize(const Bounds: TRect);
procedure PostJsonToWeb(const Obj: TJSONObject);
procedure SetDevToolsEnabled(const Enabled: Boolean);
property WebView: ICoreWebView2 read FWebView;
property OnWebMessage: TOnWebMessage read FOnWebMessage write FOnWebMessage;
property OnDownload: TOnDownload read FOnDownload write FOnDownload;
end;
implementation
{ TWebView2JsonMessage }
class function TWebView2JsonMessage.TryParse(const Json: string; out Msg: TWebView2JsonMessage): Boolean;
var
V: TJSONValue;
O: TJSONObject;
begin
Result := False;
Msg.Name := '';
Msg.CorrelationId := '';
Msg.Payload := nil;
V := TJSONObject.ParseJSONValue(Json);
try
if not (V is TJSONObject) then Exit;
O := TJSONObject(V);
Msg.Name := O.GetValue('name', '');
Msg.CorrelationId := O.GetValue('cid', '');
// Il-payload jista' jonqos jew ikun null
if O.TryGetValue('payload', Msg.Payload) then
Msg.Payload := TJSONObject(Msg.Payload.Clone)
else
Msg.Payload := TJSONObject.Create;
Result := Msg.Name <> '';
finally
V.Free;
end;
end;
{ TWebView2Host }
constructor TWebView2Host.Create(AParentHwnd: HWND; const AUserDataFolder: string);
begin
inherited Create;
FParentHwnd := AParentHwnd;
FUserDataFolder := AUserDataFolder;
FDestroyed := False;
end;
destructor TWebView2Host.Destroy;
begin
FDestroyed := True;
// Neħħi l-event handlers qabel ma' jiġu rilaxxati l-oġġetti COM
UnhookEvents;
FWebView := nil;
FController := nil;
FEnvironment := nil;
inherited;
end;
procedure TWebView2Host.EnsureNotDestroyed;
begin
if FDestroyed then
raise EInvalidOperation.Create('WebView2Host diġà ġie mħassar.');
end;
function TWebView2Host.MakeUserDataFolder: string;
begin
if FUserDataFolder <> '' then
Exit(FUserDataFolder);
// Prattika: per-app u għal kull user ta' Windows, mhux fil-katalgu tal-programm
Result := TPath.Combine(TPath.GetHomePath, 'AppDataLocalMyCompanyMyAppWebView2');
ForceDirectories(Result);
end;
procedure TWebView2Host.InitializeAsync;
var
UserData: string;
Opt: ICoreWebView2EnvironmentOptions;
begin
EnsureNotDestroyed;
UserData := MakeUserDataFolder;
// Options: hawnhekk jistgħu jiġu inklużi argumenti addizzjonali tal-browser, pereżempju Remote-Debug
Opt := TCoreWebView2EnvironmentOptions.Create;
// Async CreateEnvironment
OleCheck(CreateCoreWebView2EnvironmentWithOptions(
nil, PWideChar(UserData), Opt,
TCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler.Create(
procedure (errorCode: HRESULT; const createdEnvironment: ICoreWebView2Environment)
begin
if FDestroyed then Exit;
OleCheck(errorCode);
FEnvironment := createdEnvironment;
// Rabta tal-controller ma' HWND parent
OleCheck(FEnvironment.CreateCoreWebView2Controller(
FParentHwnd,
TCoreWebView2CreateCoreWebView2ControllerCompletedHandler.Create(
procedure (errorCode2: HRESULT; const createdController: ICoreWebView2Controller)
begin
if FDestroyed then Exit;
OleCheck(errorCode2);
FController := createdController;
OleCheck(FController.get_CoreWebView2(FWebView));
HookEvents;
// Agħmilha viżibbli b'mod inizjali
FController.put_IsVisible(1);
end)));
end)));
end;
procedure TWebView2Host.HookEvents;
var
TokenMsg, TokenDl: EventRegistrationToken;
begin
if (FWebView = nil) then Exit;
// WebMessageReceived (JS->Delphi)
TokenMsg.value := 0;
OleCheck(FWebView.add_WebMessageReceived(
TCoreWebView2WebMessageReceivedEventHandler.Create(
procedure(const sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs)
begin
if FDestroyed then Exit;
OnWebMessageReceived(sender, args);
end), TokenMsg));
// DownloadStarting
TokenDl.value := 0;
OleCheck(FWebView.add_DownloadStarting(
TCoreWebView2DownloadStartingEventHandler.Create(
procedure(const sender: ICoreWebView2; const args: ICoreWebView2DownloadStartingEventArgs)
begin
if FDestroyed then Exit;
OnDownloadStarting(sender, args);
end), TokenDl));
// Nota: Għal unhooking robust, għandek iżżomm il-valuri tal-token.
// F'ħafna proġetti dan ikun biżżejjed jekk il-host jgħix biss flimkien mal-form.
end;
procedure TWebView2Host.UnhookEvents;
begin
// Varjant robust: ħażżen it-tokens u sejjaħ remove_*.
// Hawnhekk bħala kumment, għax is-setup tal-import-unit u l-immaniġġjar tal-tokens jvarjaw skont il-wrapper.
end;
procedure TWebView2Host.OnWebMessageReceived(
const sender: ICoreWebView2;
const args: ICoreWebView2WebMessageReceivedEventArgs);
var
Json: PWideChar;
S: string;
Msg: TWebView2JsonMessage;
begin
Json := nil;
OleCheck(args.TryGetWebMessageAsString(Json));
try
S := Json;
finally
CoTaskMemFree(Json);
end;
if Assigned(FOnWebMessage) and TWebView2JsonMessage.TryParse(S, Msg) then
begin
try
FOnWebMessage(Msg);
finally
Msg.Payload.Free;
end;
end;
end;
procedure TWebView2Host.OnDownloadStarting(
const sender: ICoreWebView2;
const args: ICoreWebView2DownloadStartingEventArgs);
var
Dl: ICoreWebView2DownloadOperation;
Uri, Mime, ResultFile: PWideChar;
Total: Int64;
FileName: string;
begin
Uri := nil;
Mime := nil;
ResultFile := nil;
OleCheck(args.get_DownloadOperation(Dl));
OleCheck(Dl.get_TotalBytesToReceive(Total));
// Fil-prattika: ResultFileName inizjalment vojt, skont is-sors.
OleCheck(Dl.get_ResultFilePath(ResultFile));
OleCheck(Dl.get_MimeType(Mime));
OleCheck(Dl.get_Uri(Uri));
try
FileName := ExtractFileName(string(ResultFile));
if FileName = '' then
FileName := 'download.bin';
if Assigned(FOnDownload) then
FOnDownload(FileName, string(Mime), Total);
// Opsjonali: UI ta' download proprja, imbagħad issettja Handled
// args.put_Handled(1);
finally
CoTaskMemFree(Uri);
CoTaskMemFree(Mime);
CoTaskMemFree(ResultFile);
end;
end;
procedure TWebView2Host.Navigate(const Url: string);
begin
EnsureNotDestroyed;
if FWebView = nil then
raise EInvalidOperation.Create('WebView2 għadu ma ġiex inizjalizzat.');
OleCheck(FWebView.Navigate(PWideChar(Url)));
end;
procedure TWebView2Host.Resize(const Bounds: TRect);
var
R: tagRECT;
begin
if FController = nil then Exit;
R.Left := Bounds.Left;
R.Top := Bounds.Top;
R.Right := Bounds.Right;
R.Bottom := Bounds.Bottom;
OleCheck(FController.put_Bounds(R));
end;
procedure TWebView2Host.PostJsonToWeb(const Obj: TJSONObject);
var
S: string;
begin
EnsureNotDestroyed;
if FWebView = nil then Exit;
S := Obj.ToJSON;
OleCheck(FWebView.PostWebMessageAsString(PWideChar(S)));
end;
procedure TWebView2Host.SetDevToolsEnabled(const Enabled: Boolean);
var
Settings: ICoreWebView2Settings;
begin
if (FWebView = nil) then Exit;
OleCheck(FWebView.get_Settings(Settings));
OleCheck(Settings.put_AreDevToolsEnabled(Ord(Enabled)));
end;
end.
Skop tal-approċċ
- Kapsulazzjoni tal-ciklu tal-ħajja: Il-forma FMX taf biss „Initialize/Navigate/Resize“, mhux id-dettalji tal-COM.
- Bridge bi-kuntratt: Messaġġi JSON bi
name, opzjonalmentcid(Correlation-ID) upayloadhuma mmanutenibbli u jistgħu jittestjaw. - Persistenza affidabbli għall-operat: folder kontrollat
UserDataFolderjipprevjeni kolliżjonijiet tal-cache, problemi ta‘ permessi u l-kwistjoni li s-servizz „jagħmel fuq il-magni tal-iżviluppatur, mhux fil-produzzjoni“.
JS↔Delphi-Bridge: għaliex WebMessage huwa aktar stabbli minn ExecuteScript
WebView2 joffri diversi kanali għall-komunikazzjoni. Fil-prattika ExecuteScript jista‘ jkun attraenti, imma huwa diffiċli biex jiġu verżjonati: qed tgħaddi strings f’interpreter mingħajr kanali ta‘ tweġiba ċari u mingħajr mappjar robust ta‘ żbalji. PostWebMessageAsString / WebMessageReceived min-naħa l-oħra huwa kanal definit.
Każ limiti, li spiss joħroġ f’ambjenti korporattivi: trid tibda workflow Delphi minn frontend web (eż. portali interni) — eż. stampa, aċċess għal apparat, integrawjoni legacy. F’dak il-każ għandek bżonn:
- whitelist ta‘ ismijiet tal-messaġġi
- Correlation-IDs għal risposti asincroni
- punt ċentrali li jivvalida l-payloads (eż. kampi obbligatorji, limiti ta‘ daqs)
Fil-host dan hu l-post OnWebMessageReceived. Il-verifika vera għandha tiġi f’saff ogħla (eż. Application-Service), sabiex iżżomm it-teknoloġija UI/WebView2 u l-loġika tan-negozju separati (arkitettura ta‘ saff klassika: UI → Application → Domain → Infrastruktur).
Downloads u ħażna tal-fajls: dak li spiss jassustaw fil-produzzjoni
Downloads f’WebView2 imorru permezz ta‘ ICoreWebView2DownloadOperation. Skont is-sors il-ResultFilePath jista‘ jibqa‘ vojt bikri jew issettjat biss aktar tard. Aktar minn hekk, ħafna kumpaniji ma jridx li utenti finali jaħżnu f’folders mhux kontrollati.
Prattiki stabbiliti:
- Interċetta
DownloadStartingu billi tużaargs.put_Handled(1)tieħu kontroll tal-UI (path proprju, konvenzjoni ta‘ isim, folder ta‘ kwarantina). - Limiti fuq id-daqs tal-fajl u verifiki tal-MIME-Type, biex tevita sitwazzjonijiet bħal fajl ta‘ log ta‘ 4 GB b’żball.
- Auditing: ikteb il-metadata tal-download (URI, MIME, bytes) fil-log tiegħek, mhux il-kontenut stess.
Jekk għandek proċessi regolati (eż. approvazzjonijiet, traċċabbiltà), il-manipulazzjoni permezz tal-events hi l-unika post fejn tista‘ tintegra d-dinja tal-browser fir-regoli operattivi tiegħek.
Debugging: DevTools, Remote Debug Port u stati riproduċibbli
Id-debugging ta‘ WebView2 spiss jaqa‘ meta s-stati ma jkunux riproduċibbli. Żewġ aġġustamenti jgħinu:
- Attiva/deattiva DevTools permezz ta‘
ICoreWebView2Settings(fil-kodiċi:SetDevToolsEnabled) – fil-release spiss mitfi, fil-każ ta‘ support jiġi miżbugħ b’mod mirat. - UserDataFolder stabbli: jekk il-support tiegħek irid jirriproduċi żball, path definit huwa ta‘ valur kbir. Tista‘ issalva/zipja l-folder (attenzjoni: protezzjoni tad-dejta/PII) u tqabbel l-istati b’mod mirat.
Bħala għażla (skont il-wrapper) tista‘ tipprovdi EnvironmentOptions b’argumenti addizzjonali għall-browser, pereżempju Remote-Debug-Port. Dan għandu sens meta tkun qed tanalizza applikazzjoni fuq sistema tat-test mingħajr għodod tal-iżviluppatur lokali. Limitazzjonijiet: f’ambjenti produttivi dan irid jiġi abilitati u dokumentati b’mod ċar, inkella toħloq vulerabbiltà mhux meħtieġa.
Snagli f’Delphi WebView2 FMX: COM, Threads u Form-Lifecycle
1) Callbacks wara l-għeluq
Is-CompletedHandler asinkroni jistgħu jaslu wara li l-forma tkun diġà għalqet. Fil-snippet FDestroyed jipprevjeni aċċess għal oġġetti rilaxxati. Aktar robust huwa wkoll:
- Żomm tokens għall-avvenimenti u fi
Destroysejjaħ b’mod nadifremove_* - Permetti
InitializeAsyncdarba biss (State-Machine: Created/Initializing/Ready/Disposed)
2) Kuntest tat-Thread
Ħafna handler jiġu «qrib tal-UI», imma tgħaqqadx fuqhekk li tista‘ tikteb direttament f’kontrolli FMX. Jekk taġġorna l-UI f‘OnWebMessage, TThread.Queue(nil, ...) hija l-verżjoni sigura. Jiena nissepara: il-host jikkollezjona l-avveniment, is-servizz tal-applikazzjoni jiddeċiedi, u l-UI tiġi aġġornata esklussivament permezz ta‘ Queue.
3) DPI/Resize und FMX-Layouts
FMX jaħseb f’unitajiet loġiċi, WebView2 jistenna Pixel-Rects. Fil-prattika għandek bżonn punt ċar fejn tittraduċi l-bounds mill-kontrolli FMX għal pixels reali. Is-snippet jaċċetta TRect; fil-form tiegħek għandek estratti l-koordinati WinAPI minn dan (per eż., permezz ta‘ FMX.Platform.Win u l-Handle-APIs). Jekk l-app tħaddem scaling skond il-DPI tal-monitor, ittestja l-bidla bejn monitors: WebView2 huwa hawn aktar sensittiv minn kontrolli FMX puri.
Meta WebView2 f’FMX jagħmel sens – u meta le
WebView2 jagħmel sens meta trid tuża teknoloġija web f’applikazzjoni klijent Delphi li evolviet: view amministrattivi imdaħħla, flussi ta‘ login OAuth/OIDC, rapporti HTML, portali interni jew «Micro-Frontends» ikkontrollati. Kif ukoll bħala bridge ta‘ modernizzazzjoni huwa prattiku, sakemm tistabbilixxi responsabbiltajiet b’mod ċar u l-bridge ma jsirx bibien ta‘ wara mhux kkontrollati għall-logika tan-negozju.
Limiti tal-approċċ:
- Pjattaforma: Il-mudell huwa ċentriku fuq Windows. FMX huwa multi-pjattaforma, WebView2 mhux. Għall-macOS/iOS/Android għandek bżonn WebViews oħra jew saff ta‘ astrazzjoni.
- Security/Hardening: Ladarba jittellgħu kontenuti esterni, trid tillimita b’mod aktar strett in-navigazzjoni, id-domini permessi u l-miri tal-download. Dan għandu jkun parti mir-rekwiżiti, mhux «wara».
- Support: UserDataFolder u d-dipendenzi runtime (WebView2 Runtime) għandhom ikunu parti mill-kunċett tal-operazzjoni/rollout tiegħek.
Konklużjoni
Delphi WebView2 FMX mhuwiex biss gadget tal-UI, imma komponent ta‘ integrazjoni b’lifecycle proprju. Jekk tikkapsula b’mod strutturat l-inizjalizzazzjoni, l-eventing, il-UserDataFolder u l-JS-Bridge, WebView2 jsir element stabbli għal soluzzjonijiet korporattivi diġitali: Web-UI fejn għandu sens, u loġika Delphi fejn tidħol. Jekk min-naħa l-oħra tnaddaf skripts mingħajr kontroll, tagħti r-rotot lil-paths skont ix-xorti u ma tiddikoppjax l-events, tikseb eżatt it-tip ta‘ żbalji sporadiċi fil-produzzjoni li jieħdu żmien u jnaqqsu l-fiduċja.
Jekk trid tintegra WebView2 b’mod nadif f’applikazzjoni Delphi eżistenti jew tivvaluta teknikanament punt ta‘ modernizzazzjoni, tkellem magħna:
Fil-qasam professjonali, Webview2 Firemonkey u Delphi Fmx Edge Browser għandhom rwol importanti wkoll, meta l-integrazzjonijiet, il-flussi tad-dejta u l-iżvilupp aktar għandhom jaħdmu flimkien b’mod nadif.
Diskuti 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.