Open bozalina opened 3 years ago
Thanks for the catch here - I think you are right that those proposed exception types may be more appropriate. My only concern is if someone is already specifically catching the "NotImplementedException" and then would miss this, but I imagine that scenario is rare. I've opened a bug on our backlog. Thanks!
And based on issue #1422, I would suggest to allow null values, as MVVM binding decoupling might lead to null values being pushed to the browser control.
I would suggest to show the 'about:blank' page, or something similar in case of a null value.
A year later, this issue still causes us headaches. Is there any progress on this issue?
Having same issue
yes one of the main headaches is when using a WPF tabcontrol where unused tabs are automatically "removed"
I am facing the same issue............
Not sure if this will be useful to anyone, but this is my workaround. There are 2 attached dependency properties here.
NullableSourceProperty
will set its value to WebView2.Source
unless its null, in which case it will set it to "about:blank". DisposeOnUnloadedProperty
will subscribe to WebView2.Unloaded
and call WebView2.Dispose
when the WebView2 control is unloaded.The use case for this was with the Telerik RadTabbedWindow with IsContentPreserved set to true. Dispose needs to be called when the control is outright removed because WebView2 will live in the background (a new thread for every tab).
Mileage may vary since (I think) the Loaded/Unloaded event are tied to the control being added/removed from the visual tree. But this solution might give someone a better idea.
using Microsoft.Web.WebView2.Wpf;
using System;
using System.Windows;
using System.Windows.Controls;
namespace WebView2Test;
public static class WebView2Helper
{
#region NullableSource
[AttachedPropertyBrowsableForType(typeof(WebView2))]
public static Uri? GetNullableSource(DependencyObject obj) => (Uri?)obj.GetValue(NullableSourceProperty);
public static void SetNullableSource(DependencyObject obj, Uri? value) => obj.SetValue(NullableSourceProperty, value);
/// <summary>
/// Middle man for binding <see cref="WebView2.Source"/> when the binding might resolve to null.
/// </summary>
/// <remarks>
/// <see cref="WebView2.Source"/> can become null when <see cref="WebView2"/> is used in a <see cref="ItemsControl.ItemTemplate"/>. When an item is removed from the <see cref="ItemsControl.ItemsSource"/>, the Binding to <see cref="WebView2.Source"/> will resolve to null prior to the control being unloaded.
/// </remarks>
public static readonly DependencyProperty NullableSourceProperty = DependencyProperty.RegisterAttached("NullableSource", typeof(Uri), typeof(WebView2Helper), new PropertyMetadata(propertyChangedCallback: NullableSourceChanged));
private static void NullableSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not WebView2 webView2)
return;
webView2.Source = e.NewValue as Uri ?? new Uri("about:blank");
}
#endregion
#region DisposeOnUnloaded
[AttachedPropertyBrowsableForType(typeof(WebView2))]
public static bool GetDisposeOnUnloaded(DependencyObject obj) => (bool)obj.GetValue(DisposeOnUnloadedProperty);
public static void SetDisposeOnUnloaded(DependencyObject obj, bool value) => obj.SetValue(DisposeOnUnloadedProperty, value);
/// <summary>
/// When true, disposes <see cref="WebView2"/> when <see cref="FrameworkElement.Unloaded"/> is fired.
/// </summary>
/// <remarks>
/// By default, <see cref="WebView2"/> does not dispose of its resources when its unloaded, which might be desirable in certain situations.
/// </remarks>
public static readonly DependencyProperty DisposeOnUnloadedProperty = DependencyProperty.RegisterAttached("DisposeOnUnloaded", typeof(bool), typeof(WebView2Helper), new PropertyMetadata(false, propertyChangedCallback: DisposeOnUnloadedChanged));
private static void DisposeOnUnloadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not WebView2 webView2)
return;
//always attempt to unsubscribe in case this get triggered for "true" multiple times
webView2.Unloaded -= WebView2_Unloaded;
if (e.NewValue is true)
webView2.Unloaded += WebView2_Unloaded;
}
private static void WebView2_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is WebView2 webView2)
{
webView2.Dispose();
webView2.Unloaded -= WebView2_Unloaded;
}
}
#endregion
}
<wpf:WebView2
local:WebView2Helper.NullableSource="{Binding SomeUri}"
local:WebView2Helper.DisposeOnUnloaded="True"/>
I can propose better workaround of this issue for those that already subclassing WebView2
public partial class WebView : WebView2
{
static WebView()
{
SourceProperty.OverrideMetadata(typeof(WebView), new PropertyMetadata(defaultValue: new Uri("about:blank")));
DefaultStyleKeyProperty.OverrideMetadata(typeof(WebView), new FrameworkPropertyMetadata(typeof(WebView)));
}
This seems like the incorrect exception type for this error. Perhaps an ArgumentNullException or InvalidOperationException would be more appropriate.
private static void SourcePropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { Microsoft.Web.WebView2.Wpf.WebView2 control = (Microsoft.Web.WebView2.Wpf.WebView2) d; if (control.IsPropertyChangingFromCore(Microsoft.Web.WebView2.Wpf.WebView2.SourceProperty)) return; Uri oldValue = (Uri) e.OldValue; Uri newValue = (Uri) e.NewValue; if (newValue == (Uri) null) throw new NotImplementedException("The Source property cannot be set to null."); if (!newValue.IsAbsoluteUri) throw new ArgumentException("The Source property cannot be set to a relative Uri."); if (control.CoreWebView2 != null && (oldValue == (Uri) null || oldValue.AbsoluteUri != newValue.AbsoluteUri)) control.CoreWebView2.Navigate(newValue.AbsoluteUri); control.WhenInit_Source((Action) (() => control.EnsureCoreWebView2Async())); }
AB#33616111