Xamarin Forms中BindableProperty变更时Custom Control的OnElementChanged未触发
Got it, let's tackle this problem. The core issue here is that OnElementChanged only triggers when your custom WebView is first initialized or replaced—it won't fire when your bindable URL property updates later. The solution is to leverage the OnElementPropertyChanged method in your renderers to listen for property changes, plus ensure your bindable property is set up correctly.
Step 1: Define Your Custom WebView with a Proper Bindable Property
First, make sure your custom WebView's URL property includes change tracking (this is optional but adds flexibility, though the renderer will handle property changes regardless):
public class CustomWebView : WebView { // Define the bindable URL property with change tracking public static readonly BindableProperty UrlProperty = BindableProperty.Create( nameof(Url), typeof(string), typeof(CustomWebView), defaultValue: null, propertyChanged: OnUrlPropertyChanged); public string Url { get => (string)GetValue(UrlProperty); set => SetValue(UrlProperty, value); } private static void OnUrlPropertyChanged(BindableObject bindable, object oldValue, object newValue) { // Optional: Notify listeners or handle control-level logic var webView = bindable as CustomWebView; webView?.OnUrlUpdated((string)oldValue, (string)newValue); } protected virtual void OnUrlUpdated(string oldUrl, string newUrl) { // Empty method for renderers to override if needed } }
Step 2: Update Your Renderers with OnElementPropertyChanged
The magic happens in OnElementPropertyChanged—this method fires every time a bindable property on your element changes. We'll check if the changed property is our URL, then refresh the native WebView.
Android Renderer Example
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))] namespace YourAppNamespace.Droid { public class CustomWebViewRenderer : WebViewRenderer { public CustomWebViewRenderer(Context context) : base(context) { } protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) { base.OnElementChanged(e); // Load initial URL when the element is first attached if (e.NewElement != null) { var customWebView = (CustomWebView)e.NewElement; LoadUrlIfValid(customWebView.Url); } } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); // Check if the changed property is our URL if (e.PropertyName == CustomWebView.UrlProperty.PropertyName) { var customWebView = (CustomWebView)Element; LoadUrlIfValid(customWebView.Url); } } private void LoadUrlIfValid(string url) { if (Control != null && !string.IsNullOrWhiteSpace(url)) { // Android's native WebView uses LoadUrl Control.LoadUrl(url); // Use Control.Reload() if you need to refresh the same URL } } } }
iOS Renderer Example
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))] namespace YourAppNamespace.iOS { public class CustomWebViewRenderer : WebViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) { base.OnElementChanged(e); if (e.NewElement != null) { var customWebView = (CustomWebView)e.NewElement; LoadUrlIfValid(customWebView.Url); } } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); if (e.PropertyName == CustomWebView.UrlProperty.PropertyName) { var customWebView = (CustomWebView)Element; LoadUrlIfValid(customWebView.Url); } } private void LoadUrlIfValid(string url) { if (Control != null && !string.IsNullOrWhiteSpace(url)) { if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) { // iOS uses LoadRequest with NSUrlRequest Control.LoadRequest(new NSUrlRequest(uri)); } else { // Handle local HTML files (adjust path as needed) var localPath = NSBundle.MainBundle.PathForResource(url.Replace(".html", ""), "html"); if (!string.IsNullOrEmpty(localPath)) { Control.LoadRequest(new NSUrlRequest(new NSUrl(localPath, true))); } } } } } }
UWP Renderer Example
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))] namespace YourAppNamespace.UWP { public class CustomWebViewRenderer : WebViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) { base.OnElementChanged(e); if (e.NewElement != null) { var customWebView = (CustomWebView)e.NewElement; NavigateToUrlIfValid(customWebView.Url); } } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); if (e.PropertyName == CustomWebView.UrlProperty.PropertyName) { var customWebView = (CustomWebView)Element; NavigateToUrlIfValid(customWebView.Url); } } private void NavigateToUrlIfValid(string url) { if (Control != null && !string.IsNullOrWhiteSpace(url)) { if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) { // UWP's WebView uses Navigate Control.Navigate(uri); } else { // Handle local assets (adjust path to your Assets folder) var localUri = new Uri($"ms-appx-web:///Assets/{url}", UriKind.Absolute); Control.Navigate(localUri); } } } } }
Key Takeaways
- Don’t rely only on
OnElementChanged: It’s for initial setup or element replacement, not property updates. - Use
OnElementPropertyChanged: This is the standard Xamarin.Forms way to track bindable property changes in renderers. - Platform-specific URL loading: Each native WebView has its own method for loading URLs—make sure you use the right one for Android/iOS/UWP.
- Validate URLs: Always check for null/empty strings and handle local vs. remote URLs appropriately.
内容的提问来源于stack exchange,提问作者Sharath

