From 36a83c761a89d336376484e5c61ef0039d35ecd9 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Sat, 10 Feb 2024 01:57:20 -0800 Subject: [PATCH] Update WinForms/Wpf/Direct2D and templates to net6.0 --- .../Wpf/EmbedEtoInWpf/EmbedEtoInWpf.csproj | 22 +- .../Wpf/EmbedWpfInEto/EmbedWpfInEto.csproj | 18 +- src/Eto.Direct2D/Eto.Direct2D.csproj | 18 +- .../App-CSharp/.template.config/template.json | 14 +- .../App-FSharp/.template.config/template.json | 14 +- .../.template.config/template.json | 14 +- src/Eto.Gtk/Eto.Gtk.csproj | 3 +- .../Forms/LinuxTrayIndicatorHandler.cs | 2 +- src/Eto.Gtk/NativeMethods.tt | 2 +- src/Eto.Mac/Eto.Mac64.csproj | 3 +- src/Eto.Mac/Eto.XamMac2.csproj | 4 +- src/Eto.WinForms/Eto.WinForms.csproj | 21 +- .../Forms/Controls/TextStepperHandler.cs | 2 +- src/Eto.WinForms/ScrollMessageFilter.cs | 2 +- src/Eto.Wpf/Eto.Wpf.csproj | 34 +- src/Eto.Wpf/Forms/Controls/CalendarHandler.cs | 4 +- src/Eto.Wpf/Forms/Controls/WebView2Handler.cs | 1096 +++++++++-------- src/Eto/Forms/Binding/ICommand.cs | 36 - src/Eto/Forms/Controls/Control.cs | 5 - src/Eto/PclTypes.cs | 2 +- src/Shared/HttpClientExtensions.cs | 59 + .../Eto.Test.Direct2D.csproj | 6 +- .../Eto.Test.WinForms.csproj | 6 +- test/Eto.Test.Wpf/Eto.Test.Wpf.csproj | 6 +- 24 files changed, 672 insertions(+), 721 deletions(-) delete mode 100644 src/Eto/Forms/Binding/ICommand.cs create mode 100644 src/Shared/HttpClientExtensions.cs diff --git a/samples/Wpf/EmbedEtoInWpf/EmbedEtoInWpf.csproj b/samples/Wpf/EmbedEtoInWpf/EmbedEtoInWpf.csproj index 419945d86e..04a059e35e 100644 --- a/samples/Wpf/EmbedEtoInWpf/EmbedEtoInWpf.csproj +++ b/samples/Wpf/EmbedEtoInWpf/EmbedEtoInWpf.csproj @@ -1,14 +1,9 @@ - + - true - net48;net6.0-windows - - - WinExe Properties @@ -30,18 +25,6 @@ - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - App.xaml @@ -77,7 +60,4 @@ - - - \ No newline at end of file diff --git a/samples/Wpf/EmbedWpfInEto/EmbedWpfInEto.csproj b/samples/Wpf/EmbedWpfInEto/EmbedWpfInEto.csproj index db52235e51..0237a8c5fe 100644 --- a/samples/Wpf/EmbedWpfInEto/EmbedWpfInEto.csproj +++ b/samples/Wpf/EmbedWpfInEto/EmbedWpfInEto.csproj @@ -1,14 +1,9 @@ - + - true - net48;net6.0-windows - - - WinExe Properties @@ -36,20 +31,9 @@ - - - - Designer - MSBuild:Compile - - - - - - \ No newline at end of file diff --git a/src/Eto.Direct2D/Eto.Direct2D.csproj b/src/Eto.Direct2D/Eto.Direct2D.csproj index ea096c7850..3596a4ec39 100755 --- a/src/Eto.Direct2D/Eto.Direct2D.csproj +++ b/src/Eto.Direct2D/Eto.Direct2D.csproj @@ -1,19 +1,12 @@ - + - true - - netcoreapp3.1;net462 - - - - - - + net6.0-windows;net462 + $(TargetFrameworkOverride) Library True $(DefineConstants);WINFORMS - true + true @@ -60,8 +53,5 @@ You do not need to use any of the classes of this assembly (unless customizing t - - - \ No newline at end of file diff --git a/src/Eto.Forms.Templates/content/App-CSharp/.template.config/template.json b/src/Eto.Forms.Templates/content/App-CSharp/.template.config/template.json index 605500c372..abd8e60320 100755 --- a/src/Eto.Forms.Templates/content/App-CSharp/.template.config/template.json +++ b/src/Eto.Forms.Templates/content/App-CSharp/.template.config/template.json @@ -78,20 +78,20 @@ "type": "parameter", "description": "Specify the framework(s) to support", "dataType": "choice", - "defaultValue": "net6.0", + "defaultValue": "net8.0", "replaces": "net6.0", "choices": [ { - "choice": "net6.0", - "description": ".NET 6.0" + "choice": "net8.0", + "description": ".NET 8.0" }, { - "choice": "net5.0", - "description": ".NET 5.0" + "choice": "net7.0", + "description": ".NET 7.0" }, { - "choice": "netcoreapp3.1", - "description": ".NET Core 3.1" + "choice": "net6.0", + "description": ".NET 6.0" }, { "choice": "net48", diff --git a/src/Eto.Forms.Templates/content/App-FSharp/.template.config/template.json b/src/Eto.Forms.Templates/content/App-FSharp/.template.config/template.json index 34f8d489d1..dbcd9a128a 100755 --- a/src/Eto.Forms.Templates/content/App-FSharp/.template.config/template.json +++ b/src/Eto.Forms.Templates/content/App-FSharp/.template.config/template.json @@ -78,20 +78,20 @@ "type": "parameter", "description": "Specify the framework(s) to support", "dataType": "choice", - "defaultValue": "net6.0", + "defaultValue": "net8.0", "replaces": "net6.0", "choices": [ { - "choice": "net6.0", - "description": ".NET 6.0" + "choice": "net8.0", + "description": ".NET 8.0" }, { - "choice": "net5.0", - "description": ".NET 5.0" + "choice": "net7.0", + "description": ".NET 7.0" }, { - "choice": "netcoreapp3.1", - "description": ".NET Core 3.1" + "choice": "net6.0", + "description": ".NET 6.0" }, { "choice": "net48", diff --git a/src/Eto.Forms.Templates/content/App-VisualBasic/.template.config/template.json b/src/Eto.Forms.Templates/content/App-VisualBasic/.template.config/template.json index 4f5731dd21..06940a5b56 100755 --- a/src/Eto.Forms.Templates/content/App-VisualBasic/.template.config/template.json +++ b/src/Eto.Forms.Templates/content/App-VisualBasic/.template.config/template.json @@ -78,20 +78,20 @@ "type": "parameter", "description": "Specify the framework(s) to support", "dataType": "choice", - "defaultValue": "net6.0", + "defaultValue": "net8.0", "replaces": "net6.0", "choices": [ { - "choice": "net6.0", - "description": ".NET 6.0" + "choice": "net8.0", + "description": ".NET 8.0" }, { - "choice": "net5.0", - "description": ".NET 5.0" + "choice": "net7.0", + "description": ".NET 7.0" }, { - "choice": "netcoreapp3.1", - "description": ".NET Core 3.1" + "choice": "net6.0", + "description": ".NET 6.0" }, { "choice": "net48", diff --git a/src/Eto.Gtk/Eto.Gtk.csproj b/src/Eto.Gtk/Eto.Gtk.csproj index fbaf06f4e8..b5931a38c4 100755 --- a/src/Eto.Gtk/Eto.Gtk.csproj +++ b/src/Eto.Gtk/Eto.Gtk.csproj @@ -1,7 +1,8 @@ - netstandard2.0;net6.0 + netstandard2.0;net6.0 + $(TargetFrameworkOverride) True Eto.GtkSharp $(DefineConstants);GTK3;GTKCORE diff --git a/src/Eto.Gtk/Forms/LinuxTrayIndicatorHandler.cs b/src/Eto.Gtk/Forms/LinuxTrayIndicatorHandler.cs index 329f6ede89..6d74b64a0d 100644 --- a/src/Eto.Gtk/Forms/LinuxTrayIndicatorHandler.cs +++ b/src/Eto.Gtk/Forms/LinuxTrayIndicatorHandler.cs @@ -50,7 +50,7 @@ public bool Visible public LinuxTrayIndicatorHandler() { -#if NETCOREAPP +#if NET NativeLibrary.SetDllImportResolver(typeof(LinuxTrayIndicatorHandler).Assembly, (name, assembly, path) => { // Use custom import resolver for libappindicator diff --git a/src/Eto.Gtk/NativeMethods.tt b/src/Eto.Gtk/NativeMethods.tt index 4f20f063f3..883c7b9ce8 100755 --- a/src/Eto.Gtk/NativeMethods.tt +++ b/src/Eto.Gtk/NativeMethods.tt @@ -152,7 +152,7 @@ namespace Eto.GtkSharp static class <#= pclass[i] #> { -#if NETCOREAPP +#if NET static <#= pclass[i] #>() { diff --git a/src/Eto.Mac/Eto.Mac64.csproj b/src/Eto.Mac/Eto.Mac64.csproj index b63143c338..f1854bed12 100644 --- a/src/Eto.Mac/Eto.Mac64.csproj +++ b/src/Eto.Mac/Eto.Mac64.csproj @@ -1,7 +1,8 @@  - netstandard2.0 + netstandard2.0 + $(TargetFrameworkOverride) Eto.Mac $(DefineConstants);OSX;DESKTOP;MONOMAC;Mac64 true diff --git a/src/Eto.Mac/Eto.XamMac2.csproj b/src/Eto.Mac/Eto.XamMac2.csproj index a1c8807a57..b32bb7f38c 100644 --- a/src/Eto.Mac/Eto.XamMac2.csproj +++ b/src/Eto.Mac/Eto.XamMac2.csproj @@ -2,8 +2,8 @@ True - net462;xamarinmac20 - net6.0-macos10.15;$(TargetFrameworks) + net462;xamarinmac20 + $(TargetFrameworkOverride) Eto.Mac $(DefineConstants);OSX;DESKTOP;XAMMAC;XAMMAC2;UNIFIED $(DefineConstants);USE_CFSTRING diff --git a/src/Eto.WinForms/Eto.WinForms.csproj b/src/Eto.WinForms/Eto.WinForms.csproj index 56a8bff7b6..e144fdad3c 100755 --- a/src/Eto.WinForms/Eto.WinForms.csproj +++ b/src/Eto.WinForms/Eto.WinForms.csproj @@ -1,21 +1,13 @@ - - - - - true - - netcoreapp3.1;net461 - - - - + + net6.0-windows;net461 + $(TargetFrameworkOverride) Library True $(DefineConstants);WINFORMS MSB4011;$(NoWarn) - true + true True @@ -116,6 +108,9 @@ You do not need to use any of the classes of this assembly (unless customizing t Forms\KeyboardHandler.cs + + HttpClientExtensions.cs + @@ -132,8 +127,6 @@ You do not need to use any of the classes of this assembly (unless customizing t - - True diff --git a/src/Eto.WinForms/Forms/Controls/TextStepperHandler.cs b/src/Eto.WinForms/Forms/Controls/TextStepperHandler.cs index aadf674704..a0866a796a 100644 --- a/src/Eto.WinForms/Forms/Controls/TextStepperHandler.cs +++ b/src/Eto.WinForms/Forms/Controls/TextStepperHandler.cs @@ -7,7 +7,7 @@ public class EtoUpDown : swf.UpDownBase public event EventHandler DownButtonClicked; public event EventHandler UpButtonClicked; -#if NETCOREAPP +#if NET static FieldInfo DefaultButtonsWidthField = typeof(swf.UpDownBase).GetField("_defaultButtonsWidth", BindingFlags.Static | BindingFlags.NonPublic); static FieldInfo TextBoxField = typeof(swf.UpDownBase).GetField("_upDownEdit", BindingFlags.Instance | BindingFlags.NonPublic); static FieldInfo UpDownButtonsField = typeof(swf.UpDownBase).GetField("_upDownButtons", BindingFlags.Instance | BindingFlags.NonPublic); diff --git a/src/Eto.WinForms/ScrollMessageFilter.cs b/src/Eto.WinForms/ScrollMessageFilter.cs index 675dd8acf1..091185f188 100755 --- a/src/Eto.WinForms/ScrollMessageFilter.cs +++ b/src/Eto.WinForms/ScrollMessageFilter.cs @@ -8,7 +8,7 @@ public static bool IsScrollable(swf.Control control) if (p != null) return p.AutoScroll; return control is swf.DataGridView -#if !NETCOREAPP3_1 +#if NETFRAMEWORK || control is swf.DataGrid #endif || control is swf.TreeView diff --git a/src/Eto.Wpf/Eto.Wpf.csproj b/src/Eto.Wpf/Eto.Wpf.csproj index c74d028b9f..435fc8aa6b 100755 --- a/src/Eto.Wpf/Eto.Wpf.csproj +++ b/src/Eto.Wpf/Eto.Wpf.csproj @@ -1,23 +1,16 @@ - - - - true - - netcoreapp3.1;net462 - - - - + + net6.0-windows;net462 + $(TargetFrameworkOverride) Library Eto.Wpf Eto.Wpf true true $(DefineConstants);WPF - true - true + true + true NU1701;MSB4011;$(NoWarn) @@ -140,6 +133,9 @@ You do not need to use any of the classes of this assembly (unless customizing t Drawing\OpenTypeFontInfo.cs + + HttpClientExtensions.cs + @@ -151,17 +147,6 @@ You do not need to use any of the classes of this assembly (unless customizing t - - - - MSBuild:Compile - Designer - - - $([System.String]::Copy('%(Filename)').Replace('.xaml', ''))%(Extension) - - - @@ -172,7 +157,4 @@ You do not need to use any of the classes of this assembly (unless customizing t - - - diff --git a/src/Eto.Wpf/Forms/Controls/CalendarHandler.cs b/src/Eto.Wpf/Forms/Controls/CalendarHandler.cs index e941f8b407..55c33bdf68 100644 --- a/src/Eto.Wpf/Forms/Controls/CalendarHandler.cs +++ b/src/Eto.Wpf/Forms/Controls/CalendarHandler.cs @@ -72,7 +72,7 @@ public DateTime MinDate var currentMonth = Control.DisplayDate; Control.DisplayDateStart = value == DateTime.MinValue ? (DateTime?)null : value; Control.DisplayDate = currentMonth; - if (SelectedDate != null && SelectedDate < value) + if (SelectedDate < value) SelectedDate = value; } } @@ -83,7 +83,7 @@ public DateTime MaxDate set { Control.DisplayDateEnd = value == DateTime.MaxValue ? (DateTime?)null : value; - if (SelectedDate != null && SelectedDate > value) + if (SelectedDate > value) SelectedDate = value; } } diff --git a/src/Eto.Wpf/Forms/Controls/WebView2Handler.cs b/src/Eto.Wpf/Forms/Controls/WebView2Handler.cs index 4b90975508..54f3e000de 100755 --- a/src/Eto.Wpf/Forms/Controls/WebView2Handler.cs +++ b/src/Eto.Wpf/Forms/Controls/WebView2Handler.cs @@ -5,203 +5,205 @@ using Microsoft.Web.WebView2.Core; using Microsoft.Win32; using System.Net; +using System.Net.Http; + #if WINFORMS using WebView2Control = Microsoft.Web.WebView2.WinForms.WebView2; using BaseHandler = Eto.WinForms.Forms.WindowsControl; -namespace Eto.WinForms.Forms.Controls +namespace Eto.WinForms.Forms.Controls; #elif WPF using WebView2Control = Microsoft.Web.WebView2.Wpf.WebView2; using BaseHandler = Eto.Wpf.Forms.WpfFrameworkElement; using BaseHost = Eto.Wpf.Forms.EtoBorder; -namespace Eto.Wpf.Forms.Controls +namespace Eto.Wpf.Forms.Controls; #endif + +/// +/// Loader class and helper utilities to install the WebView2 runtime. +/// +public static class WebView2Loader { /// - /// Loader class and helper utilities to install the WebView2 runtime. + /// Creates the WetView2Handler /// - public static class WebView2Loader - { - /// - /// Creates the WetView2Handler - /// - /// - /// We create the handler here so that the TypeLoadException happens with this class vs. the calling class (e.g. Platform). - /// - /// A new WebView2Handler instance - public static WebView.IHandler Create() => new WebView2Handler(); - - /// - /// Link to download the bootstrapper to install WebView2 components - /// - public static Uri InstallLink = new Uri("https://go.microsoft.com/fwlink/p/?LinkId=2124703"); - - /// - /// Link for users to download the components manually - /// - public static Uri DownloadLink = new Uri("https://developer.microsoft.com/en-us/microsoft-edge/webview2/"); - - /// - /// Mode to use to install the WebView2 components if not already installed - /// - public static WebView2InstallMode InstallMode = WebView2InstallMode.Manual; - - /// - /// Detects whether WebView2 is installed - /// - /// true if installed, false if not installed - public static bool Detect() - { + /// + /// We create the handler here so that the TypeLoadException happens with this class vs. the calling class (e.g. Platform). + /// + /// A new WebView2Handler instance + public static WebView.IHandler Create() => new WebView2Handler(); + + /// + /// Link to download the bootstrapper to install WebView2 components + /// + public static Uri InstallLink = new Uri("https://go.microsoft.com/fwlink/p/?LinkId=2124703"); + + /// + /// Link for users to download the components manually + /// + public static Uri DownloadLink = new Uri("https://developer.microsoft.com/en-us/microsoft-edge/webview2/"); + + /// + /// Mode to use to install the WebView2 components if not already installed + /// + public static WebView2InstallMode InstallMode = WebView2InstallMode.Manual; + + /// + /// Detects whether WebView2 is installed + /// + /// true if installed, false if not installed + public static bool Detect() + { #if TEST_INSTALL return false; #endif - try - { - var versionInfo = CoreWebView2Environment.GetAvailableBrowserVersionString(); - return versionInfo != null; - } - catch (WebView2RuntimeNotFoundException) - { - return false; - } + try + { + var versionInfo = CoreWebView2Environment.GetAvailableBrowserVersionString(); + return versionInfo != null; } - - /// - /// Detects the WebView2 runtime and installs it based on the current - /// - /// - /// This will throw if the runtime cannot be installed or user opted not to install. - /// - public static void EnsureWebView2Runtime() + catch (WebView2RuntimeNotFoundException) { - if (Detect()) - return; - - switch (InstallMode) - { - case WebView2InstallMode.Manual: - var dlg = new ManualInstallDialog(); - dlg.ShowModal(); - break; - case WebView2InstallMode.Automatic: - InstallWebView2WithUI(); - break; - case WebView2InstallMode.Fallback: - throw new WebView2NotInstalledException(); - } + return false; + } + } - if (!Detect()) + /// + /// Detects the WebView2 runtime and installs it based on the current + /// + /// + /// This will throw if the runtime cannot be installed or user opted not to install. + /// + public static void EnsureWebView2Runtime() + { + if (Detect()) + return; + + switch (InstallMode) + { + case WebView2InstallMode.Manual: + var dlg = new ManualInstallDialog(); + dlg.ShowModal(); + break; + case WebView2InstallMode.Automatic: + InstallWebView2WithUI(); + break; + case WebView2InstallMode.Fallback: throw new WebView2NotInstalledException(); } - static Task RunProcessAsync(ProcessStartInfo startInfo) - { - var tcs = new TaskCompletionSource(); + if (!Detect()) + throw new WebView2NotInstalledException(); + } - var process = new Process - { - StartInfo = startInfo, - EnableRaisingEvents = true - }; + static Task RunProcessAsync(ProcessStartInfo startInfo) + { + var tcs = new TaskCompletionSource(); - process.Exited += (sender, args) => - { - tcs.SetResult(process.ExitCode); - process.Dispose(); - }; + var process = new Process + { + StartInfo = startInfo, + EnableRaisingEvents = true + }; - process.Start(); + process.Exited += (sender, args) => + { + tcs.SetResult(process.ExitCode); + process.Dispose(); + }; - return tcs.Task; - } + process.Start(); - internal static void CenterDialog(Dialog dialog) - { - // this should be in Eto, but alas.. + return tcs.Task; + } + + internal static void CenterDialog(Dialog dialog) + { + // this should be in Eto, but alas.. #if WPF if (dialog.ControlObject is System.Windows.Window w) { w.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; } #elif WINFORMS - if (dialog.ControlObject is System.Windows.Forms.Form f) - { - f.Load += (sender, e) => - { - var screen = System.Windows.Forms.Screen.FromControl(f); - - var workingArea = screen.WorkingArea; - f.Location = new System.Drawing.Point( - Math.Max(workingArea.X, workingArea.X + (workingArea.Width - f.Width) / 2), - Math.Max(workingArea.Y, workingArea.Y + (workingArea.Height - f.Height) / 2) - ); - }; - } -#endif - } - - /// - /// Installs the WebView2 runtime, showing a form while it installs. - /// - public static void InstallWebView2WithUI(Control parent = null) + if (dialog.ControlObject is System.Windows.Forms.Form f) { - var dialog = new Dialog - { - Title = Loc("Installing WebView2 runtime"), - WindowStyle = WindowStyle.Utility, - Minimizable = false, - Maximizable = false, - MinimumSize = new Size(300, 100) - }; - CenterDialog(dialog); - var textLabel = new Label(); - var progressBar = new ProgressBar { MaxValue = 1000 }; - var layout = new DynamicLayout { Padding = 10, DefaultSpacing = new Size(5, 5) }; - layout.Add(textLabel); - layout.Add(progressBar); - dialog.Content = layout; - - void ReportProgress(WebView2ProgressInfo progress) => Application.Instance.Invoke(() => + f.Load += (sender, e) => { - textLabel.Text = progress.Text; - progressBar.Value = (int)(progress.Progress * progressBar.MaxValue); - }); + var screen = System.Windows.Forms.Screen.FromControl(f); - dialog.LoadComplete += async (sender, e) => - { - await Task.Run(() => InstallWebView2(ReportProgress)); - dialog.Close(); + var workingArea = screen.WorkingArea; + f.Location = new System.Drawing.Point( + Math.Max(workingArea.X, workingArea.X + (workingArea.Width - f.Width) / 2), + Math.Max(workingArea.Y, workingArea.Y + (workingArea.Height - f.Height) / 2) + ); }; - dialog.ShowModal(parent); } +#endif + } + + /// + /// Installs the WebView2 runtime, showing a form while it installs. + /// + public static void InstallWebView2WithUI(Control parent = null) + { + var dialog = new Dialog + { + Title = Loc("Installing WebView2 runtime"), + WindowStyle = WindowStyle.Utility, + Minimizable = false, + Maximizable = false, + MinimumSize = new Size(300, 100) + }; + CenterDialog(dialog); + var textLabel = new Label(); + var progressBar = new ProgressBar { MaxValue = 1000 }; + var layout = new DynamicLayout { Padding = 10, DefaultSpacing = new Size(5, 5) }; + layout.Add(textLabel); + layout.Add(progressBar); + dialog.Content = layout; + + void ReportProgress(WebView2ProgressInfo progress) => Application.Instance.Invoke(() => + { + textLabel.Text = progress.Text; + progressBar.Value = (int)(progress.Progress * progressBar.MaxValue); + }); + + dialog.LoadComplete += async (sender, e) => + { + await Task.Run(() => InstallWebView2(ReportProgress)); + dialog.Close(); + }; + dialog.ShowModal(parent); + } - static string Loc(string str) => Application.Instance.Localize(typeof(WebView2Loader), str); - /// - /// Installs the WebView2 runtime by downloading the bootstrapper and running it in elevated permissions - /// - /// delegate to report progress - /// task for async - public static async Task InstallWebView2(Action reportProgress) + static string Loc(string str) => Application.Instance.Localize(typeof(WebView2Loader), str); + /// + /// Installs the WebView2 runtime by downloading the bootstrapper and running it in elevated permissions + /// + /// delegate to report progress + /// task for async + public static async Task InstallWebView2(Action reportProgress) + { + var info = new WebView2ProgressInfo(); + double totalProgress = 220; + double currentProgress = 0; + void Progress(double progress) { - var info = new WebView2ProgressInfo(); - double totalProgress = 220; - double currentProgress = 0; - void Progress(double progress) - { - currentProgress = progress; - info.Progress = currentProgress / totalProgress; + currentProgress = progress; + info.Progress = currentProgress / totalProgress; #if TEST_INSTALL Thread.Sleep(200); #endif - reportProgress?.Invoke(info); - } - // download bootstrapper to temp folder - var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + ".exe"; - try - { - info.Text = Loc("Downloading bootstrapper..."); - Progress(10); + reportProgress?.Invoke(info); + } + // download bootstrapper to temp folder + var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + ".exe"; + try + { + info.Text = Loc("Downloading bootstrapper..."); + Progress(10); #if TEST_INSTALL /* For testing.. */ @@ -211,6 +213,7 @@ void Progress(double progress) return; #endif +#if NETFRAMEWORK using (var client = new WebClient()) { var wait = new ManualResetEvent(false); @@ -218,519 +221,530 @@ void Progress(double progress) client.DownloadProgressChanged += (sender, e) => Progress(startProgress + e.ProgressPercentage); await client.DownloadFileTaskAsync(InstallLink, tempFile); } - info.Text = Loc("Installing WebView2..."); - Progress(120); - // run with elevated privileges - var startInfo = new ProcessStartInfo(tempFile); - startInfo.Verb = "runas"; - startInfo.UseShellExecute = false; - var result = await RunProcessAsync(startInfo); - if (result != 0 && !Detect()) +#else + using (var client = new HttpClient()) + { + var wait = new ManualResetEvent(false); + var startProgress = currentProgress; + using (var file = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None)) { - MessageBox.Show(Loc("Could not install WebView2 runtime. Try installing manually."), MessageBoxType.Error); + await client.DownloadAsync(InstallLink, file, progress => Progress(startProgress + progress)); } } - finally +#endif + info.Text = Loc("Installing WebView2..."); + Progress(120); + // run with elevated privileges + var startInfo = new ProcessStartInfo(tempFile); + startInfo.Verb = "runas"; + startInfo.UseShellExecute = false; + var result = await RunProcessAsync(startInfo); + if (result != 0 && !Detect()) { - info.Text = Loc("Cleaning Up..."); - Progress(210); - // delete bootstrapper - if (File.Exists(tempFile)) - File.Delete(tempFile); - Progress(totalProgress); + MessageBox.Show(Loc("Could not install WebView2 runtime. Try installing manually."), MessageBoxType.Error); } } + finally + { + info.Text = Loc("Cleaning Up..."); + Progress(210); + // delete bootstrapper + if (File.Exists(tempFile)) + File.Delete(tempFile); + Progress(totalProgress); + } } +} - public class WebView2ProgressInfo - { - public bool Complete { get; set; } - public string Text { get; set; } - public double Progress { get; set; } - } +public class WebView2ProgressInfo +{ + public bool Complete { get; set; } + public string Text { get; set; } + public double Progress { get; set; } +} +/// +/// Modes for WebView2 runtime installation when used in Eto. +/// +public enum WebView2InstallMode +{ /// - /// Modes for WebView2 runtime installation when used in Eto. + /// Shows a dialog to the user to install the WebView2 runtime /// - public enum WebView2InstallMode - { - /// - /// Shows a dialog to the user to install the WebView2 runtime - /// - Manual, - /// - /// Automatically installs the WebView2 runtime. Requires the user to elevate permissions. - /// - Automatic, - /// - /// Silently fall back to the old IE webview. - /// - Fallback, - } - - class ManualInstallDialog : Dialog - { - UITimer timer; - public ManualInstallDialog() - { - WebView2Loader.CenterDialog(this); - string Loc(string str) => Application.Instance.Localize(typeof(WebView2Loader), str); + Manual, + /// + /// Automatically installs the WebView2 runtime. Requires the user to elevate permissions. + /// + Automatic, + /// + /// Silently fall back to the old IE webview. + /// + Fallback, +} - Title = Loc("Install WebView2 Runtime"); +class ManualInstallDialog : Dialog +{ + UITimer timer; + public ManualInstallDialog() + { + WebView2Loader.CenterDialog(this); + string Loc(string str) => Application.Instance.Localize(typeof(WebView2Loader), str); - MinimumSize = new Size(300, 200); + Title = Loc("Install WebView2 Runtime"); - // controls + MinimumSize = new Size(300, 200); - var installButton = new Button { Text = Loc("Download && Install Now") }; - installButton.Click += OnInstall; + // controls - var downloadButton = new LinkButton { Text = Loc("Open the WebView2 download page to install manually") }; - downloadButton.Click += OnDownload; + var installButton = new Button { Text = Loc("Download && Install Now") }; + installButton.Click += OnInstall; - var cancelButton = new Button { Text = Loc("Fallback to IE") }; - cancelButton.Click += OnCancel; + var downloadButton = new LinkButton { Text = Loc("Open the WebView2 download page to install manually") }; + downloadButton.Click += OnDownload; - var description = new Label - { - TextAlignment = TextAlignment.Center, - Text = Loc("This application requires the Microsoft Edge WebView2 Runtime.\nPlease install it to continue.") - }; + var cancelButton = new Button { Text = Loc("Fallback to IE") }; + cancelButton.Click += OnCancel; - // layout + var description = new Label + { + TextAlignment = TextAlignment.Center, + Text = Loc("This application requires the Microsoft Edge WebView2 Runtime.\nPlease install it to continue.") + }; - var layout = new DynamicLayout { Padding = 10, DefaultSpacing = new Size(5, 5) }; - layout.AddSpace(); - layout.Add(description); - layout.AddSpace(); + // layout - layout.AddSeparateRow(null, downloadButton); + var layout = new DynamicLayout { Padding = 10, DefaultSpacing = new Size(5, 5) }; + layout.AddSpace(); + layout.Add(description); + layout.AddSpace(); - Content = layout; + layout.AddSeparateRow(null, downloadButton); - DefaultButton = installButton; - PositiveButtons.Add(installButton); - NegativeButtons.Add(cancelButton); + Content = layout; - Shown += (sender, e) => installButton.Focus(); + DefaultButton = installButton; + PositiveButtons.Add(installButton); + NegativeButtons.Add(cancelButton); - timer = new UITimer { Interval = 1.0 }; - timer.Elapsed += timer_Elapsed; - timer.Start(); - } + Shown += (sender, e) => installButton.Focus(); - protected override void OnUnLoad(EventArgs e) - { - base.OnUnLoad(e); + timer = new UITimer { Interval = 1.0 }; + timer.Elapsed += timer_Elapsed; + timer.Start(); + } - timer?.Stop(); - timer = null; - } + protected override void OnUnLoad(EventArgs e) + { + base.OnUnLoad(e); + + timer?.Stop(); + timer = null; + } - private async void timer_Elapsed(object sender, EventArgs e) + private async void timer_Elapsed(object sender, EventArgs e) + { + if (WebView2Loader.Detect()) { - if (WebView2Loader.Detect()) + timer?.Stop(); + // wait for things to percolate + await Task.Delay(2000); + // ensure we didn't get closed already.. + if (timer != null) { - timer?.Stop(); - // wait for things to percolate - await Task.Delay(2000); - // ensure we didn't get closed already.. - if (timer != null) - { - Close(); - } + Close(); } } - - private void OnCancel(object sender, EventArgs e) - { - Close(); - throw new WebView2NotInstalledException(); - } - - private void OnDownload(object sender, EventArgs e) - { - Application.Instance.Open(WebView2Loader.DownloadLink.ToString()); - } - - private void OnInstall(object sender, EventArgs e) - { - WebView2Loader.InstallWebView2WithUI(this); - Close(); - } } + private void OnCancel(object sender, EventArgs e) + { + Close(); + throw new WebView2NotInstalledException(); + } - public class WebView2NotInstalledException : System.Exception + private void OnDownload(object sender, EventArgs e) { - public WebView2NotInstalledException() : base("The WebView2 runtime is not installed") { } + Application.Instance.Open(WebView2Loader.DownloadLink.ToString()); } - - public class WebView2InitializationException : System.Exception + + private void OnInstall(object sender, EventArgs e) { - public WebView2InitializationException(string message, Exception inner) : base(message, inner) - { - } + WebView2Loader.InstallWebView2WithUI(this); + Close(); } - - #if WPF - public class WebView2Host : BaseHost +} + + +public class WebView2NotInstalledException : System.Exception +{ + public WebView2NotInstalledException() : base("The WebView2 runtime is not installed") { } +} + +public class WebView2InitializationException : System.Exception +{ + public WebView2InitializationException(string message, Exception inner) : base(message, inner) { } - #endif +} - public class WebView2Handler : BaseHandler, WebView.IHandler +#if WPF +public class WebView2Host : BaseHost +{ +} +#endif + +public class WebView2Handler : BaseHandler, WebView.IHandler +{ + bool webView2Ready; + protected bool WebView2Ready => webView2Ready; + CoreWebView2Environment _environment; + List delayedActions; + + public WebView2Handler() { - bool webView2Ready; - protected bool WebView2Ready => webView2Ready; - CoreWebView2Environment _environment; - List delayedActions; - - public WebView2Handler() - { - WebView2Loader.EnsureWebView2Runtime(); - Control = new WebView2Control(); - Control.CoreWebView2InitializationCompleted += Control_CoreWebView2Ready; + WebView2Loader.EnsureWebView2Runtime(); + Control = new WebView2Control(); + Control.CoreWebView2InitializationCompleted += Control_CoreWebView2Ready; #if WPF - _host = new WebView2Host(); - _host.Child = Control; - _host.Handler = this; + _host = new WebView2Host(); + _host.Child = Control; + _host.Handler = this; #endif - } + } #if WPF - WebView2Host _host; - public override System.Windows.FrameworkElement ContainerControl => _host; + WebView2Host _host; + public override System.Windows.FrameworkElement ContainerControl => _host; #endif - /// - /// The default environment to use if none is specified with . - /// - public static CoreWebView2Environment CoreWebView2Environment; - - /// - /// Specifies a function to call when we need the default environment, if not already specified - /// - public static Func> GetCoreWebView2Environment; - - /// - /// Gets or sets the environment to use, defaulting to . - /// This can only be set once during construction or with a style for this handler. - /// - /// Environment to use to initialize WebView2 - public CoreWebView2Environment Environment - { - get => _environment ?? CoreWebView2Environment; - set => _environment = value; - } + /// + /// The default environment to use if none is specified with . + /// + public static CoreWebView2Environment CoreWebView2Environment; + + /// + /// Specifies a function to call when we need the default environment, if not already specified + /// + public static Func> GetCoreWebView2Environment; - /// - /// Override to use your own WebView2 initialization, if necessary - /// - /// Task - protected async virtual Task OnInitializeWebView2Async() + /// + /// Gets or sets the environment to use, defaulting to . + /// This can only be set once during construction or with a style for this handler. + /// + /// Environment to use to initialize WebView2 + public CoreWebView2Environment Environment + { + get => _environment ?? CoreWebView2Environment; + set => _environment = value; + } + + /// + /// Override to use your own WebView2 initialization, if necessary + /// + /// Task + protected async virtual Task OnInitializeWebView2Async() + { + var env = Environment; + if (env == null && GetCoreWebView2Environment != null) { - var env = Environment; - if (env == null && GetCoreWebView2Environment != null) - { - env = CoreWebView2Environment = await GetCoreWebView2Environment(); - } - - await Control.EnsureCoreWebView2Async(env); + env = CoreWebView2Environment = await GetCoreWebView2Environment(); } - - async void InitializeAsync() => await OnInitializeWebView2Async(); - - protected override void Initialize() - { - base.Initialize(); - Size = new Size(100, 100); + + await Control.EnsureCoreWebView2Async(env); + } + + async void InitializeAsync() => await OnInitializeWebView2Async(); + + protected override void Initialize() + { + base.Initialize(); + Size = new Size(100, 100); #if WPF - // We need these for HasFocus to return a proper value - HandleEvent(Eto.Forms.Control.GotFocusEvent); - HandleEvent(Eto.Forms.Control.LostFocusEvent); + // We need these for HasFocus to return a proper value + HandleEvent(Eto.Forms.Control.GotFocusEvent); + HandleEvent(Eto.Forms.Control.LostFocusEvent); #endif - } + } - protected override void OnInitializeComplete() - { - base.OnInitializeComplete(); - - // initialize webview2 after styles are applied, since styles might be used to configure the Environment or CoreWebView2Environment - InitializeAsync(); - } + protected override void OnInitializeComplete() + { + base.OnInitializeComplete(); - void Control_CoreWebView2Ready(object sender, CoreWebView2InitializationCompletedEventArgs e) + // initialize webview2 after styles are applied, since styles might be used to configure the Environment or CoreWebView2Environment + InitializeAsync(); + } + + void Control_CoreWebView2Ready(object sender, CoreWebView2InitializationCompletedEventArgs e) + { + if (!e.IsSuccess) { - if (!e.IsSuccess) - { - throw new WebView2InitializationException("Failed to initialze WebView2", e.InitializationException); - } - - // can't actually do anything here, so execute them in the main loop - Application.Instance.AsyncInvoke(RunDelayedActions); + throw new WebView2InitializationException("Failed to initialze WebView2", e.InitializationException); } - private void RunDelayedActions() - { - if (Widget.IsDisposed) - return; - - Control.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; - Control.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; - webView2Ready = true; + // can't actually do anything here, so execute them in the main loop + Application.Instance.AsyncInvoke(RunDelayedActions); + } - if (delayedActions != null) + private void RunDelayedActions() + { + if (Widget.IsDisposed) + return; + + Control.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; + Control.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; + webView2Ready = true; + + if (delayedActions != null) + { + for (int i = 0; i < delayedActions.Count; i++) { - for (int i = 0; i < delayedActions.Count; i++) - { - delayedActions[i].Invoke(); - } - delayedActions = null; + delayedActions[i].Invoke(); } + delayedActions = null; } + } - public override void OnUnLoad(EventArgs e) - { - base.OnUnLoad(e); - - // Fixes crash when shown as a child window and the parent is closed - // https://github.com/MicrosoftEdge/WebView2Feedback/issues/1971 - // See WebViewTests.WebViewClosedAsChildShouldNotCrash for repro + public override void OnUnLoad(EventArgs e) + { + base.OnUnLoad(e); + + // Fixes crash when shown as a child window and the parent is closed + // https://github.com/MicrosoftEdge/WebView2Feedback/issues/1971 + // See WebViewTests.WebViewClosedAsChildShouldNotCrash for repro #if WPF - _host.Child = null; + _host.Child = null; #endif - } - - public override void OnLoad(EventArgs e) - { + } + + public override void OnLoad(EventArgs e) + { #if WPF - if (_host.Child == null) - _host.Child = Control; + if (_host.Child == null) + _host.Child = Control; #endif - } + } - private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e) - { - var args = new WebViewNewWindowEventArgs(new Uri(e.Uri), null); - Callback.OnOpenNewWindow(Widget, args); - e.Handled = args.Cancel; - } + private void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e) + { + var args = new WebViewNewWindowEventArgs(new Uri(e.Uri), null); + Callback.OnOpenNewWindow(Widget, args); + e.Handled = args.Cancel; + } + + private void CoreWebView2_DocumentTitleChanged(object sender, object e) + { + Callback.OnDocumentTitleChanged(Widget, new WebViewTitleEventArgs(CoreWebView2.DocumentTitle)); + } - private void CoreWebView2_DocumentTitleChanged(object sender, object e) + protected void RunWhenReady(Action action) + { + if (webView2Ready) { - Callback.OnDocumentTitleChanged(Widget, new WebViewTitleEventArgs(CoreWebView2.DocumentTitle)); + // already ready, run now! + action(); + return; } - protected void RunWhenReady(Action action) + if (delayedActions == null) { - if (webView2Ready) - { - // already ready, run now! - action(); - return; - } - - if (delayedActions == null) - { - delayedActions = new List(); - } - - delayedActions.Add(action); + delayedActions = new List(); } - public override void AttachEvent(string handler) - { - switch (handler) - { - case WebView.NavigatedEvent: - Control.ContentLoading += Control_ContentLoading; - break; - case WebView.DocumentLoadedEvent: - Control.NavigationCompleted += Control_NavigationCompleted; - break; - case WebView.DocumentLoadingEvent: - Control.NavigationStarting += Control_NavigationStarting; - break; - case WebView.OpenNewWindowEvent: - break; - case WebView.DocumentTitleChangedEvent: - break; + delayedActions.Add(action); + } + + public override void AttachEvent(string handler) + { + switch (handler) + { + case WebView.NavigatedEvent: + Control.ContentLoading += Control_ContentLoading; + break; + case WebView.DocumentLoadedEvent: + Control.NavigationCompleted += Control_NavigationCompleted; + break; + case WebView.DocumentLoadingEvent: + Control.NavigationStarting += Control_NavigationStarting; + break; + case WebView.OpenNewWindowEvent: + break; + case WebView.DocumentTitleChangedEvent: + break; #if WPF - case Eto.Forms.Control.GotFocusEvent: - Control.GotFocus += Control_GotFocus; - break; - case Eto.Forms.Control.LostFocusEvent: - Control.LostFocus += Control_LostFocus; - break; + case Eto.Forms.Control.GotFocusEvent: + Control.GotFocus += Control_GotFocus; + break; + case Eto.Forms.Control.LostFocusEvent: + Control.LostFocus += Control_LostFocus; + break; #endif - default: - base.AttachEvent(handler); - break; - } - + default: + base.AttachEvent(handler); + break; } + } + #if WPF - static readonly object HasFocus_Key = new object(); + static readonly object HasFocus_Key = new object(); - public override bool HasFocus => Widget.Properties.Get(HasFocus_Key); + public override bool HasFocus => Widget.Properties.Get(HasFocus_Key); - private void Control_LostFocus(object sender, System.Windows.RoutedEventArgs e) + private void Control_LostFocus(object sender, System.Windows.RoutedEventArgs e) + { + if (Widget.Properties.TrySet(HasFocus_Key, false)) { - if (Widget.Properties.TrySet(HasFocus_Key, false)) - { - Callback.OnLostFocus(Widget, EventArgs.Empty); - } + Callback.OnLostFocus(Widget, EventArgs.Empty); } + } - private void Control_GotFocus(object sender, System.Windows.RoutedEventArgs e) + private void Control_GotFocus(object sender, System.Windows.RoutedEventArgs e) + { + // IsFocused/IsKeyboardFocused/IsKeyboardFocusWithin doesn't appear to work with WebView2 + if (Widget.Properties.TrySet(HasFocus_Key, true)) { - // IsFocused/IsKeyboardFocused/IsKeyboardFocusWithin doesn't appear to work with WebView2 - if (Widget.Properties.TrySet(HasFocus_Key, true)) - { - Callback.OnGotFocus(Widget, EventArgs.Empty); - } + Callback.OnGotFocus(Widget, EventArgs.Empty); } + } #endif - private void Control_ContentLoading(object sender, CoreWebView2ContentLoadingEventArgs e) - { - var args = new WebViewLoadedEventArgs(Control.Source); - Callback.OnNavigated(Widget, args); - } + private void Control_ContentLoading(object sender, CoreWebView2ContentLoadingEventArgs e) + { + var args = new WebViewLoadedEventArgs(Control.Source); + Callback.OnNavigated(Widget, args); + } - private void Control_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) - { - var args = new WebViewLoadingEventArgs(new Uri(e.Uri), true); - Callback.OnDocumentLoading(Widget, args); - e.Cancel = args.Cancel; - } + private void Control_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) + { + var args = new WebViewLoadingEventArgs(new Uri(e.Uri), true); + Callback.OnDocumentLoading(Widget, args); + e.Cancel = args.Cancel; + } - private void Control_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) + private void Control_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) + { + var args = new WebViewLoadedEventArgs(Control.Source); + Application.Instance.AsyncInvoke(() => { - var args = new WebViewLoadedEventArgs(Control.Source); - Application.Instance.AsyncInvoke(() => { - if (Widget.IsDisposed) - return; - Callback.OnDocumentLoaded(Widget, args); - }); - } + if (Widget.IsDisposed) + return; + Callback.OnDocumentLoaded(Widget, args); + }); + } - public Uri Url - { - get { return Control.Source; } - set { Control.Source = value; } - } + public Uri Url + { + get { return Control.Source; } + set { Control.Source = value; } + } - public string DocumentTitle => CoreWebView2?.DocumentTitle; + public string DocumentTitle => CoreWebView2?.DocumentTitle; - public string ExecuteScript(string script) + public string ExecuteScript(string script) + { + var fullScript = string.Format("var _fn = function() {{ {0} }}; _fn();", script); + var task = Control.ExecuteScriptAsync(fullScript); + while (!task.IsCompleted) { - var fullScript = string.Format("var _fn = function() {{ {0} }}; _fn();", script); - var task = Control.ExecuteScriptAsync(fullScript); - while (!task.IsCompleted) - { - if (!Widget.Loaded) - return null; - Application.Instance.RunIteration(); - Thread.Sleep(10); - } - return Decode(task.Result); + if (!Widget.Loaded) + return null; + Application.Instance.RunIteration(); + Thread.Sleep(10); } + return Decode(task.Result); + } - public async Task ExecuteScriptAsync(string script) - { - var fullScript = string.Format("var _fn = function() {{ {0} }}; _fn();", script); - var result = await Control.ExecuteScriptAsync(fullScript); - return Decode(result); - } + public async Task ExecuteScriptAsync(string script) + { + var fullScript = string.Format("var _fn = function() {{ {0} }}; _fn();", script); + var result = await Control.ExecuteScriptAsync(fullScript); + return Decode(result); + } - string Decode(string result) + string Decode(string result) + { + // result is json-encoded. cool, but why? + if (result == null) + return null; + if (result.StartsWith("\"") && result.EndsWith("\"")) { - // result is json-encoded. cool, but why? - if (result == null) - return null; - if (result.StartsWith("\"") && result.EndsWith("\"")) - { - return result.Substring(1, result.Length - 2); - } - return result; + return result.Substring(1, result.Length - 2); } + return result; + } - public void Stop() => Control.Stop(); + public void Stop() => Control.Stop(); - public void Reload() => Control.Reload(); + public void Reload() => Control.Reload(); - public void GoBack() => Control.GoBack(); + public void GoBack() => Control.GoBack(); - public bool CanGoBack => Control.CanGoBack; + public bool CanGoBack => Control.CanGoBack; - public void GoForward() => Control.GoForward(); + public void GoForward() => Control.GoForward(); - public bool CanGoForward => Control.CanGoForward; + public bool CanGoForward => Control.CanGoForward; - HttpServer server; + HttpServer server; - public void LoadHtml(string html, Uri baseUri) + public void LoadHtml(string html, Uri baseUri) + { + if (!webView2Ready) { - if (!webView2Ready) - { - RunWhenReady(() => LoadHtml(html, baseUri)); - return; - } - if (baseUri != null) - { - if (server == null) - server = new HttpServer(); - server.SetHtml(html, baseUri != null ? baseUri.LocalPath : null); - Control.Source = server.Url; - } - else - { - Control.NavigateToString(html); - } - + RunWhenReady(() => LoadHtml(html, baseUri)); + return; + } + if (baseUri != null) + { + if (server == null) + server = new HttpServer(); + server.SetHtml(html, baseUri != null ? baseUri.LocalPath : null); + Control.Source = server.Url; + } + else + { + Control.NavigateToString(html); } - protected Microsoft.Web.WebView2.Core.CoreWebView2 CoreWebView2 + } + + protected Microsoft.Web.WebView2.Core.CoreWebView2 CoreWebView2 + { + get { - get - { - if (!webView2Ready) - return null; - return Control.CoreWebView2; - } + if (!webView2Ready) + return null; + return Control.CoreWebView2; } + } - public void ShowPrintDialog() => ExecuteScript("print()"); + public void ShowPrintDialog() => ExecuteScript("print()"); - static readonly object BrowserContextMenuEnabled_Key = new object(); + static readonly object BrowserContextMenuEnabled_Key = new object(); - public bool BrowserContextMenuEnabled + public bool BrowserContextMenuEnabled + { + get => CoreWebView2?.Settings.AreDefaultContextMenusEnabled ?? Widget.Properties.Get(BrowserContextMenuEnabled_Key, true); + set { - get => CoreWebView2?.Settings.AreDefaultContextMenusEnabled ?? Widget.Properties.Get(BrowserContextMenuEnabled_Key, true); - set + if (Widget.Properties.TrySet(BrowserContextMenuEnabled_Key, value, true)) { - if (Widget.Properties.TrySet(BrowserContextMenuEnabled_Key, value, true)) + if (!webView2Ready) { - if (!webView2Ready) - { - RunWhenReady(() => CoreWebView2.Settings.AreDefaultContextMenusEnabled = value); - return; - } - CoreWebView2.Settings.AreDefaultContextMenusEnabled = value; + RunWhenReady(() => CoreWebView2.Settings.AreDefaultContextMenusEnabled = value); + return; } + CoreWebView2.Settings.AreDefaultContextMenusEnabled = value; } } + } #if WPF - public override Color BackgroundColor - { - get => Colors.Transparent; - set { } - } + public override Color BackgroundColor + { + get => Colors.Transparent; + set { } + } #endif - } } diff --git a/src/Eto/Forms/Binding/ICommand.cs b/src/Eto/Forms/Binding/ICommand.cs deleted file mode 100644 index f739cdbb29..0000000000 --- a/src/Eto/Forms/Binding/ICommand.cs +++ /dev/null @@ -1,36 +0,0 @@ -#if !NETSTANDARD && !NETCOREAPP -namespace System.Windows.Input -{ -} - -namespace Eto.Forms -{ - /// - /// Interface to define a command to execute. - /// - /// - /// This interface is provided only for .NET 4.0 to mimic the System.Windows.Input.ICommand provided in .NET 4.5 and PCL. - /// It is useful for MVVM scenarios to bind object events to a command in the model. - /// - public interface ICommand - { - /// - /// Determines whether this command can be executed. - /// - /// true if the command can be executed; otherwise, false. - /// Optional data to pass to the command, or null if not required by the command. - bool CanExecute(object parameter); - - /// - /// Executes the command - /// - /// Optional data to pass to the command, or null if not required by the command. - void Execute(object parameter); - - /// - /// Event to handle when the state of the method changes. - /// - event EventHandler CanExecuteChanged; - } -} -#endif \ No newline at end of file diff --git a/src/Eto/Forms/Controls/Control.cs b/src/Eto/Forms/Controls/Control.cs index b519141dd2..5f6debd042 100755 --- a/src/Eto/Forms/Controls/Control.cs +++ b/src/Eto/Forms/Controls/Control.cs @@ -7,11 +7,6 @@ namespace Eto.Forms; /// All visual user interface elements should inherit from this class to provide common functionality like binding, /// load/unload, and common events. /// -#if !NETSTANDARD && !NETCOREAPP - [ToolboxItem(true)] - [DesignTimeVisible(true)] - [DesignerCategory("Eto.Forms")] -#endif [sc.TypeConverter(typeof(ControlConverter))] public partial class Control : BindableWidget, IMouseInputSource, IKeyboardInputSource, ICallbackSource { diff --git a/src/Eto/PclTypes.cs b/src/Eto/PclTypes.cs index d37dff4473..34b533990d 100644 --- a/src/Eto/PclTypes.cs +++ b/src/Eto/PclTypes.cs @@ -1,4 +1,4 @@ -#if NETSTANDARD || NETCOREAPP +#if NETSTANDARD || NET // This file contains type definitions currently needed to compile Eto // as a Portable Class Library, in the project Eto.Pcl.csproj. namespace Eto diff --git a/src/Shared/HttpClientExtensions.cs b/src/Shared/HttpClientExtensions.cs new file mode 100644 index 0000000000..aba31ffea5 --- /dev/null +++ b/src/Shared/HttpClientExtensions.cs @@ -0,0 +1,59 @@ +#if NET +using System.Net.Http; + +namespace Eto; +static class HttpClientExtensions +{ + public static async Task DownloadAsync(this HttpClient client, Uri requestUri, Stream destination, Action progress = null, CancellationToken cancellationToken = default) + { + // Get the http headers first to examine the content length + using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) + { + var contentLength = response.Content.Headers.ContentLength; + + using (var download = await response.Content.ReadAsStreamAsync(cancellationToken)) + { + + // Ignore progress reporting when no progress reporter was + // passed or when the content length is unknown + if (progress == null || !contentLength.HasValue) + { + await download.CopyToAsync(destination); + return; + } + + // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) + var relativeProgress = new Progress(totalBytes => progress.Invoke((float)totalBytes / contentLength.Value)); + // Use extension method to report progress while downloading + await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); + progress.Invoke(1); + } + } + } + + public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new ArgumentException("Has to be readable", nameof(source)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new ArgumentException("Has to be writable", nameof(destination)); + if (bufferSize < 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); + } + } + +} +#endif \ No newline at end of file diff --git a/test/Eto.Test.Direct2D/Eto.Test.Direct2D.csproj b/test/Eto.Test.Direct2D/Eto.Test.Direct2D.csproj index b3f41770b6..cace550451 100644 --- a/test/Eto.Test.Direct2D/Eto.Test.Direct2D.csproj +++ b/test/Eto.Test.Direct2D/Eto.Test.Direct2D.csproj @@ -1,11 +1,7 @@  - net48 - net6.0-windows;$(TargetFrameworks) - - - + net6.0-windows;net48 WinExe Properties ..\Eto.Test\Images\TestIcon.ico diff --git a/test/Eto.Test.WinForms/Eto.Test.WinForms.csproj b/test/Eto.Test.WinForms/Eto.Test.WinForms.csproj index 44e69d19fc..3070f714c2 100644 --- a/test/Eto.Test.WinForms/Eto.Test.WinForms.csproj +++ b/test/Eto.Test.WinForms/Eto.Test.WinForms.csproj @@ -1,11 +1,7 @@  - net48 - net6.0-windows;$(TargetFrameworks) - - - + net6.0-windows;net48 WinExe Properties ..\Eto.Test\Images\TestIcon.ico diff --git a/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj b/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj index b2f16fad62..862a940f67 100644 --- a/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj +++ b/test/Eto.Test.Wpf/Eto.Test.Wpf.csproj @@ -1,11 +1,7 @@  - net6.0-windows;net48 - net48 - - - + net6.0-windows;net48 WinExe Properties TestIcon.ico