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álsucid(Correlation-ID) ogpayloaderu viðhaldshæf og prófanleg. - Rekstrartrygg varanleiki: ein stjórn á
UserDataFolderkemur í 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
Destroysauberremove_*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.