I’m new to Xamarin.Forms and tried using WebView on my Windows 10 x64 v1803 machine with UWP but I can’t see how to get it to work with WebGL.
Sites which use WebGL either display a message that “Your video card does not support WebGL or just don’t display and graphical content at all.
Is this a limitation of UWP or WebView itself?
Is it a WebView configuration issue?
WebGL works in all other browsers on this machine.
UWP WebView control is support WebGL. There is similar issue case in msdn you could refer. Please try to use SeparateProcess mode WebView to replace the default one.
public MainPage()
{
this.InitializeComponent();
var MyWebView = new WebView(WebViewExecutionMode.SeparateProcess);
MyWebView.Source = new Uri("http://cycleblob.com/");
this.RootGrid.Children.Add(MyWebView);
}
I had the same problem, but with the newer Xamarin Forms it took a little more poking around to get this took work right. However, I do like that they moved the native WebView resolver back to the responsibility of the UWP/iOS/Android project (as a native XAML object) instead of using code branching with compiler directives in the Shared project.
Start by creating a HybridWebView class in the shared project to use as your WebForm view object:
public class HybridWebView : Xamarin.Forms.WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
Then in the UWP project, create a custom renderer, which will construct the native WebView and relay the events back to the WebForms object in the Shared project:
Put this at the top of the namespace, to link the HybridWebView with the Custom Renderer:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(WebViewRenderer2))]
Then create the renderer class (for the IOS and android projects, if you leave this class out, it defaults to the standard native controls which seem to work fine for me):
public class WebViewRenderer2 : ViewRenderer<Xamarin.Forms.WebView, Windows.UI.Xaml.Controls.WebView>, IWebViewDelegate
{
Windows.UI.Xaml.Controls.WebView _control;
public void LoadHtml(string html, string baseUrl)
{
}
public void LoadUrl(string url)
{
}
protected override void Dispose(bool disposing)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
if (_control == null) {
_control = new Windows.UI.Xaml.Controls.WebView(WebViewExecutionMode.SeparateProcess);
SetNativeControl(_control);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var xamWebView = sender as HybridWebView;
switch(e.PropertyName.ToLower())
{
case "source":
var urlSource = xamWebView.Source as Xamarin.Forms.UrlWebViewSource;
_control.Source = new Uri(urlSource.Url);
break;
case "width":
_control.Width = xamWebView.Width;
break;
case "height":
_control.Height = xamWebView.Height;
break;
case "isfocused":
var focused = xamWebView.IsFocused;
if (focused)
_control.Focus(FocusState.Programmatic);
else
_control.Focus(FocusState.Unfocused);
break;
}
}
}
You can also use the Custom Renderer to inject scripts, and you can use it to communicate from the native webview back to the Xamarin App, as seen here: HybridWebView Communication
Related
I am using xamarin forms SearchBar control. I want to remove clear button x icon without using custom renderer.
<controls:ExSearchBar
x:Name="entrySearch"
BackgroundColor="White"
CornerRadius="6"
BorderWidth="1"
HeightRequest="45"
Text="{Binding SearchText}"
HorizontalOptions="FillAndExpand"
Placeholder="search">
</controls:ExSearchBar>
This is ExSearchBar control in shared project
public class ExSearchBar : SearchBar
{
public static readonly BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(ExFrame), default(float));
public float Elevation
{
get { return (float)GetValue(ElevationProperty); }
set { SetValue(ElevationProperty, value); }
}
}
How can I do that?
The situation you are describing is the exact reason why Xamarin Forms ships with the ability to create custom renderers. The forms team define the UI elements in abstract (seperate from their native implementation) and when there is a specific feature that is not defined in their API, you must go down to the platform level to change it.
You can also use an Effect to achieve the same result, I have provided a custom renderer for iOS & Android to show you how you would go about achieving the UI you desire:
iOS:
public class SearchBarButtonRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.SearchTextField.ClearButtonMode = UITextFieldViewMode.Never;
}
}
}
Really simple, just remove the clear button from the underlying UITextField
Android
public class SearchBarButtonRenderer : SearchBarRenderer
{
private readonly Context _context;
public SearchBarButtonRenderer(Context context)
: base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
// Get Search Close Button Drawable
var closeButtonId = Resources.GetIdentifier("android:id/search_close_btn", null, null);
var searchEditText = Control.FindViewById<ImageView>(closeButtonId);
// Get Close Button Drawable To Replace Existing Drawable
var closeDrawable = GetCloseButtonDrawable() as VectorDrawable;
if (closeDrawable is null) return;
// Apply Transparent Color To Drawable (To Make Invisible)
var buttonColor = Xamarin.Forms.Color.Transparent.ToAndroid();
closeDrawable.SetTint(buttonColor);
// Set Drawable On Control
searchEditText.SetImageDrawable(closeDrawable);
}
}
private Drawable GetCloseButtonDrawable()
{
return ContextCompat.GetDrawable(_context, Resource.Drawable.abc_ic_clear_material);
}
}
A little bit of a fiddle, find the close button drawable and replace it with a custom styled drawable
Here is the code for the custom renderer i used to assign a custom icon as my back button.
namespace MyProjectName.Droid.Renderers
{
public class MyNavigationRenderer: PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.bbutton_nav);
}
}
}
This code successfully replaces the native back arrow icon with my custom bbutton_nav. When i navigate forward(Navigate.PushAsync()), the custom icon appears on all the upcoming screens. But when i click on the back icon to go back one page(Navigate.PopAsync()), the old native back arrow reappears instead of the new custom icon that was set by the renderer. When i tried debugging , i found out that the renderer class was not getting called when navigating back(Navigation.PopAsync()).
Any help on how to mitigate this issue is appreciated. Thanks
Create a custom renderer for NavigationPage instead of Page , and override the OnLayout method .
Android will change the detault icon back in UpdateToolbar method , and OnLayout method is triggered every time while current page is changed.
Android Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyNavigationRenderer))]
namespace FormsApp.Droid
{
public class MyNavigationRenderer : NavigationPageRenderer
{
Context _context;
AndroidX.AppCompat.Widget.Toolbar _toolbar;
public MyNavigationRenderer(Context context) : base(context)
{
_context = context;
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(AndroidX.AppCompat.Widget.Toolbar))
{
_toolbar = (AndroidX.AppCompat.Widget.Toolbar)child;
_toolbar.SetNavigationIcon(Resource.Drawable.bbutton_nav);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (_toolbar != null)
{
if (_toolbar.NavigationIcon != null)
{
_toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(_context, Resource.Drawable.bbutton_nav);
}
}
}
}
}
Refer to https://forums.xamarin.com/discussion/183344/how-to-change-navigation-back-button-icon .
iOS Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : NavigationRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (this.NavigationBar.TopItem.BackBarButtonItem == null)
{
this.NavigationBar.BackIndicatorImage = UIImage.FromFile("dots.png");
this.NavigationBar.BackIndicatorTransitionMaskImage = UIImage.FromFile("dots.png");
this.NavigationBar.TopItem.BackBarButtonItem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null);
}
}
}
}
I'm trying to use a library that doesn't has a .Net SDK, but as I want to use it only to return a string, I thought I could use it's JS SDK by creating a custom WebView that returns strings (https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/).
The first problem that I faced was that a CustomRenderer is not called in Xamarin.Forms until the View is added to a Page (or at least I couldn't make it be called). To fix this I added a call to Platform.CreateRenderer in each platform.
It did the trick and the CustomRenderer executed. But when I tried to call a JS function to retrieve a string, the app just hung and stayed that way.
I didn't try to insert the WebView in a Page because I want it to be independent of the page that the app is current on, and as I want a "code-only" html, I don't see the point of adding it somewhere.
My classes:
JSEvaluator
namespace MyNamespace.Views
{
public class JSEvaluator : WebView
{
public static BindableProperty EvaluateJavascriptProperty = BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(JSEvaluator), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
public JSEvaluator()
{
}
}
}
UWP Renderer
[assembly: ExportRenderer(typeof(JSEvaluator), typeof(JSEvaluatorRenderer))]
namespace MyNamespace.UWP.Renderers
{
public class JSEvaluatorRenderer : WebViewRenderer
{
public JSEvaluatorRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as JSEvaluator;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Creation and use
if (jsEvaluator == null)
{
jsEvaluator = new JSEvaluator { Source = new HtmlWebViewSource { Html = HTML.html } };
#if __ANDROID__
Xamarin.Forms.Platform.Android.Platform.CreateRenderer(jsEvaluator);
#elif __IOS__
Xamarin.Forms.Platform.iOS.Platform.CreateRenderer(jsEvaluator);
#elif WINDOWS_UWP
Xamarin.Forms.Platform.UWP.Platform.CreateRenderer(jsEvaluator);
#endif
}
Thanks for the help :)
I had to add the WebView to a page, as #SushiHangover said in the comment. With this done, it worked as expected.
Is there a custom circular activity indicator for UWP/ Win10 apps using Xamarin?
Is there a custom circular activity indicator for UWP/ Win10 apps using Xamarin?
You need to create your own View for UWP's ProgressRing:
Shared Project\MyProgressRing.cs:
public class MyProgressRing:View
{
}
UWP Project\MyProgressRingRenderer.cs:
[assembly:ExportRenderer(typeof(MyProgressRing),typeof(MyProgressRingRenderer))]
namespace CircularActivityDemo.UWP
{
public class MyProgressRingRenderer:ViewRenderer<MyProgressRing,ProgressRing>
{
ProgressRing ring;
protected override void OnElementChanged(ElementChangedEventArgs<MyProgressRing> e)
{
base.OnElementChanged(e);
if (Control == null)
{
ring = new ProgressRing();
ring.IsActive = true;
ring.Visibility = Windows.UI.Xaml.Visibility.Visible;
ring.IsEnabled = true;
SetNativeControl(ring);
}
}
}
}
Notes: I hardcoded the properties of ProgressRing Control. You can create DependencyProperties for your custom ProgressRing control.
I'm working on porting a Windows Phone 8 application to tablet, and I've bumped into a problem with the WebView API. In Windows Phone 8 and Windows 8.1, the WebBrowser and WebView controls both have a GoBack() method. However, I need my application to be compatible for Windows 8, whose WebView API does not have such a method. Are there any alternatives/workarounds that anyone's used for Windows 8 apps?
In the end I just ended up writing a wrapper for the WebView to manage the navigation stack. Here's the relevant code, for anyone who's interested. Note that I only needed to handle backwards navigation, so I used a Stack. If forwards navigation is also required, it'd probably make sense to replace the Stack with a List and store the index of the current page instead.
public class WebViewWrapper
{
private Stack<Uri> _navigationStack;
private Uri _currentUri;
public WebView WebView { get; private set; }
public bool CanGoBack
{
get { return _navigationStack.Count > 0; }
}
public WebViewWrapper(WebView _webView)
{
_navigationStack = new Stack<Uri>();
WebView = _webView;
WebView.LoadCompleted += (object s, NavigationEventArgs e) => {
if (_currentUri != null)
{
_navigationStack.Push(_currentUri);
}
_currentUri = e.Uri;
};
}
public void GoBack()
{
if (CanGoBack)
{
_currentUri = null;
WebView.Navigate(_navigationStack.Pop());
}
}
}
An example of usage would be as follows:
// Code behind for a view called WebBrowserPage
public sealed partial class WebBrowserPage : Page
{
private WebViewWrapper _webViewWrapper;
public WebBrowserPage()
{
// webView is a WebView in the xaml with x:Name="webView"
_webViewWrapper = new WebViewWrapper(webView);
}
// Other code for navigating to a Uri specified in a ViewModel.
// Event handler for a back button press
private void BackButton_Click(object sender, RoutedEventArgs e)
{
if (_webViewWrapper.CanGoBack)
{
_webViewWrapper.GoBack();
}
else
{
// Code that executes a command in the ViewModel to leave the WebBrowserPage
}
}
}
WinRT XAML Toolkit has a WebBrowser control that does some of that, but I haven't used it in any app, so I can't vouch for its quality.