Net-Base Tímarit

14.06.2026

Delphi WebView2 í FMX: hreint frumstilla, byggja JS-brú, stjórna niðurhalum og villuleit

WebView2 í FireMonkey hljómar eins og „bara að innbæta vafra“, en í framkvæmd bregst það oft við við upphafssetningu, Navigation-Events, JS↔Delphi-Bridge, meðhöndlun niðurhala og villuleit. Þessi kóðabútur sýnir traust mynstur með skýrum ábyrgðarhlutverkum...

14.06.2026

Frá tímaritsþema til verkefnaframkvæmdar

Viðeigandi þjónustu- og tæknisíður fyrir greinina

Þeir sem vilja skyndilega „mal eben“ innbyggja nútíma vefefni í til staðarhaldandi Business-Softwaren lenda á Windows með WebView2. Í Delphi WebView2 WebView2 FMX er grunnvandamálið sjaldan að birta URL, heldur hreint innbygging í FireMonkey-viðmót (FMX), áreiðanlegt upphaf (ósamstillt og COM-byggt), ásamt Edge-fallstrikum varðandi User-Data-möppur, niðurhöl, kembiforritun og traust JS↔Delphi-samskiptakerfi.

Þessi Source-Schnipsel sýnir mynstur sem ég kýs fyrir viðhaldsvæn forrit: innkapslað „Host“-objekt sem stýrir WebView2-lífsferlinu, auk skilgreindrar brúar yfir WebMessage (JSON), í stað almenns „ExecuteScript überall“. Markmiðið er ekki demo-kóði, heldur byggingareining sem heldur sér í vaxandi klientum.

Warum WebView2 in FMX anders ist als „Browser-Component drop“

WebView2 er COM/WinRT-nálæg API með ósamstillta upphafsferla. FireMonkey abstraktar Windows-Handles, samt þarfnast WebView2 að lokum raunverulegs parent-window (HWND) og stýringar á Resize-/Focus-framsendingu. Á sama tíma keyra atvik ekki alltaf á þeim þræði sem manni finnst eðlilegast í FMX. Ef þú byrjar hér „quick and dirty“ færðu yfirleitt:

  • sporadískar AVs við lokun forms (Callbacks koma eftir Destroy)
  • Navigation-Events úr röngu þræðissamhengi
  • óáreiðanleg Persistenz/Cache-vandamál vegna óljósrar UserDataFolder-strategíu
  • engin niðurhöl eða „fastir“ niðurhalsgluggar
  • kembiforritun aðeins með heppni í stað markvissrar Remote-Debug-uppsetningar

Gagnráð er skýr lífsferill: Create → InitializeAsync → Attach → Navigate → Detach/Dispose – og skilgreind mörk milli UI og Browser-Engine.

Source-Schnipsel: WebView2Host für Delphi WebView2 FMX

Hinn eftirfarandi kóði rissar upp innkapslaða Host-klassa sem (1) býr til WebView2-Environment-stillingu, (2) bindur Controller-objekt við HWND, (3) tengir Navigation- og Download-atvik og (4) býður upp á JSON-bundna JS-brú gegnum WebMessageReceived. Kóðinn er meðvitað arkitektúrhæfur: hann innkapslar COM-tilvísanir, kemur í veg fyrir að callbacks fylgi eftir Destroy og leyfir rekstrarsviðum eins og „pro User“ eða „pro Maschine“ að hafa aðskildar UserDataFolder-uppsetningar.

unit WebView2Host;

interface

uses
System.SysUtils, System.Classes, System.IOUtils, System.JSON,
Winapi.Windows, Winapi.ActiveX,
FMX.Types,
WebView2, WebView2_TLB; // fer eftir uppsetningu: WebView2.pas eða 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‘, “);

// Payload getur vantað eða verið 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;

// Aftengja atburði áður en COM-hlutum er sleppt.
UnhookEvents;

FWebView := nil;
FController := nil;
FEnvironment := nil;

inherited;
end;

procedure TWebView2Host.EnsureNotDestroyed;
begin
if FDestroyed then
raise EInvalidOperation.Create(‚WebView2Host hefur þegar verið eytt.‘);
end;

function TWebView2Host.MakeUserDataFolder: string;
begin
if FUserDataFolder <> “ then
Exit(FUserDataFolder);

// Í framkvæmd: eitt gagnamappa per forrit og per Windows-notanda, ekki í forritamöppu
Result := TPath.Combine(TPath.GetHomePath, ‚AppDataLocalMyCompanyMyAppWebView2‘);
ForceDirectories(Result);
end;

procedure TWebView2Host.InitializeAsync;
var
UserData: string;
Opt: ICoreWebView2EnvironmentOptions;
begin
EnsureNotDestroyed;

UserData := MakeUserDataFolder;

// Valkostir: hér má bæta við viðbótar vafra-arguméntum, t.d. 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;

// Binda controller við foreldri HWND
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;

// Gera sýnilegt við upphaf
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));

// Athugið: Fyrir trausta aftengingu ættir þú að vista Tokens.
// Í mörgum verkefnum dugar þetta ef host lifir aðeins með Form.
end;

procedure TWebView2Host.UnhookEvents;
begin
// Traust lausn: vista Tokens og kalla á remove_*.
// Hér sem athugasemd þar sem Import-Unit-uppsetning og token-stjórnun getur verið breytileg eftir 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));

// Í framkvæmd: ResultFileName er upphaflega tóm, fer eftir uppsprettu.
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);

// Valfrjálst: eigið niðurhalsviðmót; setja þá 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 hefur enn ekki verið upphafstillt.‘);

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.

Markmið aðferðarinnar

  • Innkapslun lífsferils: FMX-formin þekkir aðeins „Initialize/Navigate/Resize“, ekki COM-smáatriði.
  • Samningsbundin brú: JSON-skilaboð með name, valfrjálsu cid (Correlation-ID) og payload eru viðhaldshæf og prófanleg.
  • Rekstrartrygg varanleiki: ein stjórn á UserDataFolder kemur í veg fyrir cache-átök, réttindavandamál og að lausnin „keyri á þróunartölvum, ekki í rekstri“.

JS↔Delphi-Bridge: hvers vegna WebMessage er stöðugri en ExecuteScript

WebView2 býður upp á margvíslega samskiptaleiðir. Í framkvæmd er ExecuteScript freistandi, en illa hægt að útgáfustýra: þið þrýstið strengjum í túlka án skýrra svarrása og án trausts villukortlagningar. PostWebMessageAsString / WebMessageReceived er á móti skilgreind rás.

Jaðartilvik sem kemur oft upp í fyrirtækjaumhverfum: þið þurfið að ræsa Delphi-vinnuflæði úr vef-viðmóti (t.d. innra vefsvæði) (prentun, tækjaaðgangur, samtenging við eldri kerfi). Þá þurfið þið:

  • hvítlista yfir skilaboðanafn
  • Correlation-IDs fyrir ósamstillt svör
  • miðlæg eining sem sannreynir gagnahluta (t.d. skyldureiti, stærðarmörk)

Í hýslinum er það staðurinn OnWebMessageReceived. Raunveruleg sannprófun á skilaboðum á að vera í yfirliggjandi lagi (t.d. Application-Service), svo þið haldið UI-/WebView2-tækni og viðskiptalógík aðskildri (klassísk Layer-Architektur: UI → Application → Domain → Infrastruktur).

Niðurhal og skjalageymsla: hvað kemur oft á óvart í rekstri

Niðurhal í WebView2 fara í gegnum ICoreWebView2DownloadOperation. Eftir heimild getur ResultFilePath verið autt snemma eða sett síðar. Auk þess vilja mörg fyrirtæki ekki að endanotendur skrifi í óstýranlega möppur.

Sannaðar aðferðir:

  • Grípa DownloadStarting og með args.put_Handled(1) láta UI-ið sjá um framkvæmdina (eigin slóð, nafngiftareglur, sóttkvíarmappa).
  • Takmörk skráarstærðar og MIME-gerðarathuganir, til að forðast að „óvart hlaðist 4 GB log-skrá“.
  • Afturskoðun: Skráið niður niðurhals-metadata (URI, MIME, bytes) í loggið ykkar — ekki efnið.

Ef þið hafið stjórnuð ferli (t.d. samþykktir, rekjanleiki), þá er meðhöndlun í gegnum atburði eina staðsetningin þar sem þið getið samþætt vafraheiminn inn í rekstrarreglur ykkar.

Villuleit: DevTools, Remote Debug Port og endurtekjanleg ástand

Villuleit í WebView2 bregst oft vegna þess að ástand er ekki endurtekjanlegt. Tveir stillingarmöguleikar hjálpa:

  • Kveikja/slökkva á DevTools í gegnum ICoreWebView2Settings (í kóðanum: SetDevToolsEnabled) – í útgáfu oft slökkt, en í stuðningsmálum kveikt skýrt.
  • Stöðugt UserDataFolder: Ef stuðningur á að endurgera villu er skilgreindur slóðar gullsverður. Þið getið afritað/þjappað möppuna (Ath.: persónuvernd/PII) og borið ástand saman markvisst.

Aukalega (eftir þeim wrapper sem notaður er) getið þið bætt EnvironmentOptions með viðbótar vafra-argumentum, t.d. Remote-Debug-Port. Þetta er gagnlegt þegar þið þurfið að greina forrit á prófunarkerfi án staðbundinna þróunartækja. Takmarkanir: Í framleiðslugreiningu þarf að leyfa og skjalfesta þetta vandlega, ella skapist óþarfur árásarflötur.

Algengar gildrur í Delphi WebView2 FMX: COM, þræðir og form-lífsferill

1) Callbacks nach dem Schließen

Die asynchronen CompletedHandler können eintreffen, nachdem die Form schon schließt. Im Snippet verhindert FDestroyed den Zugriff auf freigegebene Objekte. Robuster ist zusätzlich:

  • Tokens für Events speichern und in Destroy sauber remove_* aufrufen
  • InitializeAsync nur einmal zulassen (State-Machine: Created/Initializing/Ready/Disposed)

2) Thread-Kontext

Viele Handler kommen zwar „UI-nah“, aber verlassen Sie sich nicht darauf, dass Sie direkt in FMX-Controls schreiben können. Wenn Sie in OnWebMessage UI aktualisieren, ist TThread.Queue(nil, ...) die sichere Variante. Ich trenne gern: Host sammelt Ereignis, Application-Service entscheidet, UI wird ausschließlich per Queue aktualisiert.

3) DPI/Resize und FMX-Layouts

FMX rechnet in logischen Einheiten, WebView2 erwartet Pixel-Rects. In der Praxis brauchen Sie eine klare Stelle, an der Sie aus FMX-Controls Bounds in echte Pixel übersetzen. Das Snippet nimmt ein TRect an; in Ihrer Form sollten Sie daraus die WinAPI-Koordinaten ableiten (z. B. über FMX.Platform.Win und Handle-APIs). Wenn die App per Monitor-DPI skaliert, testen Sie den Wechsel zwischen Monitoren: WebView2 ist hier empfindlicher als reine FMX-Controls.

Wann sich WebView2 in FMX lohnt – und wann nicht

WebView2 lohnt sich, wenn Sie in einer gewachsenen Delphi-Client-Anwendung Web-Technik gezielt einsetzen wollen: eingebettete Admin-Views, OAuth/OIDC-Login-Flows, HTML-Reports, interne Portale oder kontrollierte „Micro-Frontends“. Auch als Modernisierungsbrücke ist es praktikabel, solange Sie die Zuständigkeiten sauber schneiden und die Bridge nicht zur unkontrollierten Hintertür für Business-Logik machen.

Grenzen des Ansatzes:

  • Plattform: Das Muster ist Windows-zentriert. FMX ist multiplattformfähig, WebView2 ist es nicht. Für macOS/iOS/Android brauchen Sie andere WebViews oder eine Abstraktionsschicht.
  • Security/Hardening: Sobald externe Inhalte geladen werden, müssen Sie Navigation, erlaubte Domains und Download-Ziele härter einschränken. Das gehört in Requirements, nicht „später“.
  • Support: UserDataFolder und Runtime-Abhängigkeiten (WebView2 Runtime) müssen Teil Ihres Betriebs-/Rollout-Konzepts sein.

Fazit

Delphi WebView2 FMX ist weniger ein UI-Gadget als eine Integrationskomponente mit eigenem Lifecycle. Wenn Sie Initialisierung, Eventing, UserDataFolder und JS-Bridge strukturiert kapseln, wird WebView2 ein stabiler Baustein für digitale Unternehmenslösungen: Web-UI dort, wo es Sinn ergibt, und Delphi-Logik dort, wo sie hingehört. Wenn Sie dagegen unkontrolliert Skripte feuern, Pfade dem Zufall überlassen und Events nicht entkoppeln, bekommen Sie genau die Sorte „sporadisch im Feld“-Fehler, die Zeit frisst und Vertrauen kostet.

Wenn Sie bei einer bestehenden Delphi-Anwendung WebView2 sauber integrieren oder eine Modernisierungskante technisch bewerten wollen, sprechen Sie mit uns:

Im fachlichen Umfeld spielen auch Webview2 Firemonkey und Delphi Fmx Edge Browser eine wichtige Rolle, wenn Integrationen, Datenflüsse und Weiterentwicklung sauber zusammenspielen müssen.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Næsta skref

Þegar úr málinu verður raunverulegt verkefni ber að skoða arkitektúr, núverandi kerfi og rekstur snemma saman.

Við styðjum ekki aðeins við einstakar spurningar, heldur einnig þegar úr kóðabútum, eldri kerfum eða gáttahugmyndum þarf að verða traust fyrirtækjaverkefni.

  • Núverandi staða, markmynd og tæknileg áhætta eru metin saman.
  • REST, gagnaaðgangur, gáttir og innleiðing eru ekki skildir eftir til síðar.
  • Það sést snemma hvaða leið er fjárhagslega og rekstrarlega sjálfbær.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax tiltækir. Fyrir Instagram undirbúum við hlekk og stuttan texta beint.

Tölvupóstur

Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.