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

Ef efnið verður að raunverulegu verkefni, ætti snemma að skoða kerfisarkitektúr, núverandi kerfi og rekstur í sameiningu.

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, aðgangur að gögnum, gáttir og innleiðing verða ekki flutt til síðari tíma sem afleiðingar.
  • Þú sérð snemma hvaða leið er efnahagslega og rekstrarlega framkvæmanleg.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax í boði. Fyrir Instagram undirbúum við tengil og stuttan texta strax.

Tölvupóstur

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