feat: initialize WinUI 3 WebView migration

This commit is contained in:
2026-07-10 04:32:15 +09:00
commit 5aa90e4aaa
103 changed files with 10903 additions and 0 deletions

250
MainWindow.xaml.cs Normal file
View File

@@ -0,0 +1,250 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.UI.Windowing;
using Microsoft.Web.WebView2.Core;
using MMoneyCoderSharp.Data;
using Windows.Graphics;
using Windows.System;
using WinRT.Interop;
namespace MBN_STOCK_WEBVIEW;
public sealed partial class MainWindow : Window
{
private const string AppHost = "app.mbn.local";
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
private bool _webViewReady;
public MainWindow()
{
InitializeComponent();
Root.Loaded += OnRootLoaded;
Closed += OnClosed;
}
private async void OnRootLoaded(object sender, RoutedEventArgs e)
{
Root.Loaded -= OnRootLoaded;
ResizeWindow();
await InitializeWebViewAsync();
}
private void ResizeWindow()
{
try
{
var windowHandle = WindowNative.GetWindowHandle(this);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
AppWindow.GetFromWindowId(windowId).Resize(new SizeInt32(1600, 960));
}
catch
{
// The system-selected size is still usable if sizing is unavailable.
}
}
private async Task InitializeWebViewAsync()
{
try
{
var webRoot = Path.Combine(AppContext.BaseDirectory, "Web");
var startPage = Path.Combine(webRoot, "index.html");
if (!File.Exists(startPage))
{
throw new FileNotFoundException("The packaged Web UI was not found.", startPage);
}
await Browser.EnsureCoreWebView2Async();
Browser.CoreWebView2.SetVirtualHostNameToFolderMapping(
AppHost,
webRoot,
CoreWebView2HostResourceAccessKind.DenyCors);
Browser.CoreWebView2.Settings.AreDevToolsEnabled = Debugger.IsAttached;
Browser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = Debugger.IsAttached;
Browser.CoreWebView2.Settings.IsStatusBarEnabled = false;
Browser.CoreWebView2.NavigationStarting += OnNavigationStarting;
Browser.CoreWebView2.NavigationCompleted += OnNavigationCompleted;
Browser.CoreWebView2.HistoryChanged += OnHistoryChanged;
Browser.CoreWebView2.NewWindowRequested += OnNewWindowRequested;
Browser.CoreWebView2.WebMessageReceived += OnWebMessageReceived;
Browser.CoreWebView2.ProcessFailed += OnProcessFailed;
_webViewReady = true;
Browser.Source = new Uri($"https://{AppHost}/index.html");
}
catch (Exception exception)
{
ShowError("WebView2 초기화에 실패했습니다.", exception.Message);
}
}
private void OnNavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs args)
{
if (!Uri.TryCreate(args.Uri, UriKind.Absolute, out var uri))
{
args.Cancel = true;
return;
}
if (uri.Host.Equals(AppHost, StringComparison.OrdinalIgnoreCase) || uri.Scheme == "about")
{
LoadingOverlay.Visibility = Visibility.Visible;
return;
}
args.Cancel = true;
_ = OpenExternalUriAsync(uri);
}
private void OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs args)
{
LoadingOverlay.Visibility = Visibility.Collapsed;
if (!args.IsSuccess)
{
ShowError("운영 화면을 불러오지 못했습니다.", args.WebErrorStatus.ToString());
}
}
private void OnHistoryChanged(object? sender, object args)
{
BackButton.IsEnabled = Browser.CanGoBack;
ForwardButton.IsEnabled = Browser.CanGoForward;
}
private void OnNewWindowRequested(object? sender, CoreWebView2NewWindowRequestedEventArgs args)
{
args.Handled = true;
if (Uri.TryCreate(args.Uri, UriKind.Absolute, out var uri))
{
_ = OpenExternalUriAsync(uri);
}
}
private void OnWebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs args)
{
try
{
var request = JsonSerializer.Deserialize<WebRequest>(args.WebMessageAsJson, JsonOptions);
switch (request?.Type)
{
case "ready":
case "request-app-info":
PostAppInfo();
break;
case "open-external" when request.Payload.ValueKind == JsonValueKind.Object:
if (request.Payload.TryGetProperty("url", out var value) &&
Uri.TryCreate(value.GetString(), UriKind.Absolute, out var uri))
{
_ = OpenExternalUriAsync(uri);
}
break;
case "reload":
Browser.Reload();
break;
}
}
catch (JsonException exception)
{
PostMessage("bridge-error", new { message = exception.Message });
}
}
private void PostAppInfo()
{
var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "1.0.0";
PostMessage("app-info", new
{
name = "MBN Stock WebView",
version,
framework = ".NET 8 / WinUI 3",
windowsAppSdk = "1.8",
architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(),
package = "MSIX",
migratedQueryFiles = 71,
databaseConfigured = DataQueryExecutor.IsConfigured
});
}
private void PostMessage(string type, object payload)
{
if (!_webViewReady)
{
return;
}
Browser.CoreWebView2.PostWebMessageAsJson(
JsonSerializer.Serialize(new { type, payload }, JsonOptions));
}
private static async Task OpenExternalUriAsync(Uri uri)
{
if (uri.Scheme is "https" or "http")
{
await Launcher.LaunchUriAsync(uri);
}
}
private void OnProcessFailed(object? sender, CoreWebView2ProcessFailedEventArgs args)
{
ShowError("WebView2 프로세스가 종료되었습니다.", args.ProcessFailedKind.ToString());
}
private void ShowError(string title, string message)
{
LoadingOverlay.Visibility = Visibility.Collapsed;
ErrorBar.Title = title;
ErrorBar.Message = message;
ErrorBar.IsOpen = true;
}
private void OnBackClicked(object sender, RoutedEventArgs e)
{
if (Browser.CanGoBack)
{
Browser.GoBack();
}
}
private void OnForwardClicked(object sender, RoutedEventArgs e)
{
if (Browser.CanGoForward)
{
Browser.GoForward();
}
}
private void OnReloadClicked(object sender, RoutedEventArgs e)
{
if (_webViewReady)
{
Browser.Reload();
}
}
private void OnClosed(object sender, WindowEventArgs args)
{
if (!_webViewReady)
{
return;
}
Browser.CoreWebView2.NavigationStarting -= OnNavigationStarting;
Browser.CoreWebView2.NavigationCompleted -= OnNavigationCompleted;
Browser.CoreWebView2.HistoryChanged -= OnHistoryChanged;
Browser.CoreWebView2.NewWindowRequested -= OnNewWindowRequested;
Browser.CoreWebView2.WebMessageReceived -= OnWebMessageReceived;
Browser.CoreWebView2.ProcessFailed -= OnProcessFailed;
}
private sealed record WebRequest(string Type, JsonElement Payload);
}