I have a HybridWebView in my app and i need to change the uri dynamically (When the user click at some menu the uri changes), but it does not change at all... I use WebView and it works great, but when I use the HybridWebView the Uri is not changing.
This ContentView where is my HybridWebView is inside an AbsoluteLayout...
<ContentView AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1"
x:Name="contentView">
<local1:HybridWebView x:Name="hybridWebView"/></ContentView>
This is the code behind (Where i handle the click on the menus to change the url, every click is a different content):
public async void ChangeURL() { hybridWebView.Uri = MyUrl; }
This is my HybridWebView class:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: nameof(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);
}
public HybridWebView()
{
}
}
I just resolved it like this:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control.Url != null)
{
var idn = new System.Globalization.IdnMapping();
var dotnetUri = new System.Uri(Element.Uri);
_url = ((HybridWebView)Element).Uri;
NSUrl nsUrl = new NSUrl(dotnetUri.Scheme, idn.GetAscii(dotnetUri.DnsSafeHost), dotnetUri.PathAndQuery);
Control.LoadRequest(new NSUrlRequest(nsUrl));
}
}
I had to add the OnElementPropertyChanged to change the url dynamically by the CustomRenderer
Related
WPF GeckoBrowser Not able to load url in the windows browser
I am using gecko web browser for my WPF windows application. We have a particular URL which is opening in all the system browser but not working on the windows browser.But we use any other it loads. When contacted the support team they were saying need to enable javascript. So please help me how to enable the javascript in gecko browser.
The URL load is the partial load. we are getting the background related UI for the URL. The body part of the url is not loading
I have used inbuilt .net browser but that URL is not loading in that application also.
If you want to use WebView2 in your project you can do the following:
Add nuget package Microsoft.Web.WebView2
Create WPF view with WebView2:
<wpf:WebView2 x:Name="WebView2">
<i:Interaction.Behaviors>
<behaviors:WebView2NavigateBehavior Url="{Binding Url}" RefreshInterval="{Binding RefreshInterval}" />
</i:Interaction.Behaviors>
</wpf:WebView2>
With code behind:
public partial class BrowserView : IDisposable
{
private bool disposed;
static BrowserView()
{
string loaderPath = ServiceLocator.Current.Resolve<IPathResolver>().GetWebView2LoaderDllDirectory(RuntimeInformation.ProcessArchitecture);
CoreWebView2Environment.SetLoaderDllFolderPath(loaderPath);
}
public BrowserView()
{
this.InitializeComponent();
this.InitializeAsync();
}
private async void InitializeAsync()
{
try
{
await this.WebView2.EnsureCoreWebView2Async();
}
catch (Exception ex)
{
//Log exception here
}
}
public void Dispose()
{
if (!this.disposed)
{
this.WebView2?.Dispose();
this.disposed = true;
}
}
}
Here's the code for view behavior:
public sealed class WebView2NavigateBehavior : BehaviorBase<WebView2>
{
public static readonly DependencyProperty UrlProperty =
DependencyProperty.Register(nameof(Url), typeof(WebsiteUrl), typeof(WebView2NavigateBehavior),
new PropertyMetadata(default(WebsiteUrl), PropertyChangedCallback));
public static readonly DependencyProperty RefreshIntervalProperty =
DependencyProperty.Register(nameof(RefreshInterval), typeof(TimeSpan), typeof(WebView2NavigateBehavior),
new PropertyMetadata(default(TimeSpan), PropertyChangedCallback));
private DispatcherTimer? timer;
public WebsiteUrl? Url
{
get => (WebsiteUrl?)this.GetValue(UrlProperty);
set => this.SetValue(UrlProperty, value);
}
public TimeSpan RefreshInterval
{
get => (TimeSpan)this.GetValue(RefreshIntervalProperty);
set => this.SetValue(RefreshIntervalProperty, value);
}
protected override void OnSetup()
{
base.OnSetup();
this.AssociatedObject.CoreWebView2InitializationCompleted += this.OnCoreWebView2InitializationCompleted;
}
protected override void OnCleanup()
{
base.OnCleanup();
this.StopRefresh();
}
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (WebView2NavigateBehavior)d;
if (e.Property == UrlProperty && e.NewValue is WebsiteUrl url)
behavior.Navigate(url);
else if (e.Property == RefreshIntervalProperty && e.NewValue is TimeSpan interval)
{
behavior.StopRefresh();
if (interval != TimeSpan.Zero)
behavior.StartRefresh(interval);
}
}
private void Navigate(WebsiteUrl? url)
{
if (this.AssociatedObject.IsInitialized && this.AssociatedObject.CoreWebView2 != null && url != null)
this.AssociatedObject.CoreWebView2.Navigate(url.ToString());
}
private void OnCoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
this.AssociatedObject.CoreWebView2InitializationCompleted -= this.OnCoreWebView2InitializationCompleted;
if (e.IsSuccess)
this.Navigate(this.Url);
}
private void StartRefresh(TimeSpan interval)
{
this.timer = new DispatcherTimer { Interval = interval };
this.timer.Tick += this.OnTick;
this.timer.Start();
}
private void StopRefresh()
{
if (this.timer != null)
{
this.timer.Stop();
this.timer.Tick -= this.OnTick;
}
this.timer = null;
}
private void OnTick(object sender, EventArgs e)
{
if (this.AssociatedObject.IsInitialized)
this.AssociatedObject.CoreWebView2?.Reload();
}
}
The code for the ViewModel:
public class BrowserViewModel : ViewModelBase<BrowserViewModel>
{
private WebsiteUrl? url;
private string? title;
private TimeSpan refreshInterval;
public WebsiteUrl? Url
{
get => this.url;
set => this.SetProperty(ref this.url, value);
}
public string? Title
{
get => this.title;
set => this.SetProperty(ref this.title, value);
}
public TimeSpan RefreshInterval
{
get => this.refreshInterval;
set => this.SetProperty(ref this.refreshInterval, value);
}
}
We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
However, we'd also like to reuse this behaviors when a submit button is tapped.
So I need to fire the TextChanged event manually, any ideas how I can do this, now sure if it's possible ?
public class NotEmptyEntryBehaviour : Behavior<Entry>
{
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(bindable);
}
void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (args == null)
return;
var oldString = args.OldTextValue;
var newString = args.NewTextValue;
}
}
If you want an alternative you can use one of the pre-built validation behaviors that comes with Xamarin.CommunityToolkit package, like TextValidationBehavior (by specifying a Regexp) or any more specific derived ones (example NumericValidationBehavior) that may fit your needs or even create a custom one by sub-classing ValidationBehavior.
It let you define custom styles for Valid and InValid states, but more important for the question has an async method called ForceValidate().
Also the Flags property could be interesting.
NotEmptyEntryBehaviour seems closer to TextValidationBehavior with MinimumLenght=1
xaml
<Entry Placeholder="Type something..." x:Name="entry">
<Entry.Behaviors>
<xct:TextValidationBehavior Flags="ValidateOnValueChanging"
InvalidStyle="{StaticResource InvalidEntryStyle}"
ValidStyle="{StaticResource ValidEntryStyle}"/>
</Entry.Behaviors>
</Entry>
Code
await (entry.Behaviors[0] as TextValidationBehavior)?.ForceValidate();
Docs
https://learn.microsoft.com/en-us/xamarin/community-toolkit/behaviors/charactersvalidationbehavior
Repo Samples
https://github.com/xamarin/XamarinCommunityToolkit/tree/main/samples/XCT.Sample/Pages/Behaviors
EDIT
If you want to run the validation from the ViewModel you need to bind ForceValidateCommand as explained in this GitHub discussion/question.
We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
You can create custom Entry with behavior to get.
The first I’m going to do is to create a new control that inherits from Entry and will add three properties: IsBorderErrorVisible, BorderErrorColor, ErrorText.
public class ExtendedEntry : Entry
{
public static readonly BindableProperty IsBorderErrorVisibleProperty =
BindableProperty.Create(nameof(IsBorderErrorVisible), typeof(bool), typeof(ExtendedEntry), false, BindingMode.TwoWay);
public bool IsBorderErrorVisible
{
get { return (bool)GetValue(IsBorderErrorVisibleProperty); }
set
{
SetValue(IsBorderErrorVisibleProperty, value);
}
}
public static readonly BindableProperty BorderErrorColorProperty =
BindableProperty.Create(nameof(BorderErrorColor), typeof(Xamarin.Forms.Color), typeof(ExtendedEntry), Xamarin.Forms.Color.Transparent, BindingMode.TwoWay);
public Xamarin.Forms.Color BorderErrorColor
{
get { return (Xamarin.Forms.Color)GetValue(BorderErrorColorProperty); }
set
{
SetValue(BorderErrorColorProperty, value);
}
}
public static readonly BindableProperty ErrorTextProperty =
BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(ExtendedEntry), string.Empty);
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set
{
SetValue(ErrorTextProperty, value);
}
}
}
Then creating custom render to android platform.
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
namespace FormsSample.Droid
{
public class ExtendedEntryRenderer : EntryRenderer
{
public ExtendedEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null || e.NewElement == null) return;
UpdateBorders();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null) return;
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName)
UpdateBorders();
}
void UpdateBorders()
{
GradientDrawable shape = new GradientDrawable();
shape.SetShape(ShapeType.Rectangle);
shape.SetCornerRadius(0);
if (((ExtendedEntry)this.Element).IsBorderErrorVisible)
{
shape.SetStroke(3, ((ExtendedEntry)this.Element).BorderErrorColor.ToAndroid());
}
else
{
shape.SetStroke(3, Android.Graphics.Color.LightGray);
this.Control.SetBackground(shape);
}
this.Control.SetBackground(shape);
}
}
}
Finally, Creating an Entry Behavior, handle the error to provide ui feedback to the user when validation occurs
public class EmptyEntryValidatorBehavior : Behavior<ExtendedEntry>
{
ExtendedEntry control;
string _placeHolder;
Xamarin.Forms.Color _placeHolderColor;
protected override void OnAttachedTo(ExtendedEntry bindable)
{
bindable.TextChanged += HandleTextChanged;
bindable.PropertyChanged += OnPropertyChanged;
control = bindable;
_placeHolder = bindable.Placeholder;
_placeHolderColor = bindable.PlaceholderColor;
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
ExtendedEntry customentry = (ExtendedEntry)sender;
if (!string.IsNullOrEmpty(customentry.Text))
{
((ExtendedEntry)sender).IsBorderErrorVisible = false;
}
else
{
((ExtendedEntry)sender).IsBorderErrorVisible = true;
}
}
protected override void OnDetachingFrom(ExtendedEntry bindable)
{
bindable.TextChanged -= HandleTextChanged;
bindable.PropertyChanged -= OnPropertyChanged;
}
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName && control != null)
{
if (control.IsBorderErrorVisible)
{
control.Placeholder = control.ErrorText;
control.PlaceholderColor = control.BorderErrorColor;
control.Text = string.Empty;
}
else
{
control.Placeholder = _placeHolder;
control.PlaceholderColor = _placeHolderColor;
}
}
}
}
Update:
You can change custom entry's IsBorderErrorVisible in button.click, to call this from submit button.
private void btn1_Clicked(object sender, EventArgs e)
{
if(string.IsNullOrEmpty(entry1.Text))
{
entry1.IsBorderErrorVisible = true;
}
}
<customentry:ExtendedEntry
x:Name="entry1"
BorderErrorColor="Red"
ErrorText="please enter name!">
<customentry:ExtendedEntry.Behaviors>
<behaviors:EmptyEntryValidatorBehavior />
</customentry:ExtendedEntry.Behaviors>
</customentry:ExtendedEntry>
I am new at Xamarin forms and renderers, Can you please help me out in this,
Thank you in advance.
I have created a checkbox but I want to change its background color like in picture.
Now you can see in image default checkbox is there I want to change its background and style how can I do that?
<local:Checkbox x:Name="chkBrand" AutomationId="AutoIdCheckBox"
Content="{Binding LblBrand}"
Checked="{Binding Chk,Mode=TwoWay}">
</local:Checkbox>
Checkbox.cs
public class Checkbox : Xamarin.Forms.View
{
public static readonly BindableProperty CheckedProperty =
BindableProperty.Create("Checked",
typeof(bool),
typeof(Checkbox),
false, BindingMode.TwoWay, propertyChanged: OnCheckedPropertyChanged);
public static readonly BindableProperty ContentProperty =
BindableProperty.Create("Content",
typeof(string),
typeof(Checkbox),
"Content", BindingMode.OneWay);
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("FontSize",
typeof(double),
typeof(Checkbox),
default(double), BindingMode.OneWay);
public bool Checked
{
get
{
return (bool)GetValue(CheckedProperty);
}
set
{
if (this.Checked != value)
{
SetValue(CheckedProperty, value);
if (CheckedChanged != null)
CheckedChanged.Invoke(this, new CheckedChangedEventArgs(value));
}
}
}
public string Content
{
get
{
return (string)GetValue(ContentProperty);
}
set
{
SetValue(ContentProperty, value);
}
}
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var checkBox = (Checkbox)bindable;
checkBox.Checked = newvalue != null ? (bool)newvalue : false;
}
}
and platform wise renderer are used. Adding UWP renderer for reference
public class CheckboxRenderer : ViewRenderer<Checkbox, CheckBox>
{
public new static void Init()
{
var temp = DateTime.Now;
}
protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
{
var checkBox = new CheckBox();
checkBox.Checked += CheckBox_Checked;
checkBox.Unchecked += CheckBox_Unchecked;
SetNativeControl(checkBox);
}
if (e.NewElement != null)
{
Control.Content = e.NewElement.Content;
Control.IsChecked = e.NewElement.Checked;
Control.IsEnabled = e.NewElement.IsEnabled;
if (default(double) != e.NewElement.FontSize)
Control.FontSize = e.NewElement.FontSize;
}
}
private void CheckBox_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Content(object sender)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
//case "IsVisible":
// Control.Hidden = Element.IsVisible;
// break;
case "IsEnabled":
Control.IsEnabled = Element.IsEnabled;
break;
case "Checked":
Control.IsChecked = Element.Checked;
break;
case "Content":
Control.Content = Element.Content;
break;
}
}
There are two ways to do this,
Use nuget package to show check box or
A simple way to change the style is to use the image control for checkbox.
Take two images as per your colour, one checked and other one is unchecked and do hide & show on click.
I have a xamarin forms image, and when the user taps on the image I want to get the x,y coordinates of where the user tapped. Not the x,y coordinates of the view per se, but the coordinates within the image. I have this working on Android below. What would the iOS custom renderer be like?
Create a interface for touch event:
public interface IFloorplanImageController
{
void SendTouched();
}
Create a custom control for image:
public class FloorplanImage : Image, IFloorplanImageController
{
public event EventHandler Touched;
public void SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public Tuple<float, float> TouchedCoordinate
{
get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
set { SetValue(TouchedCoordinateProperty, value); }
}
public static readonly BindableProperty TouchedCoordinateProperty =
BindableProperty.Create(
propertyName: "TouchedCoordinate",
returnType: typeof(Tuple<float, float>),
declaringType: typeof(FloorplanImage),
defaultValue: new Tuple<float, float>(0, 0),
propertyChanged: OnPropertyChanged);
public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
Implement the custom renderer:
[assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.Clickable = true;
Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
}
}
base.Dispose(disposing);
}
private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
() => new ImageTouchListener());
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
var element = obj.Obj;
var controller = element as IFloorplanImageController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
var x = e.GetX();
var y = e.GetY();
element.TouchedCoordinate = new Tuple<float, float>(x, y);
controller?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
}
return false;
}
}
}
public class JavaObjectWrapper<T> : Java.Lang.Object
{
public T Obj { get; set; }
}
}
Use this control like this:
<local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
Aspect="AspectFit" Touched="image_Touched" />
code behind:
private void image_Touched(object sender, EventArgs e)
{
var cor = image.TouchedCoordinate;
}
I found this Customer success example on github that does exactly this.
[assembly: ExportRenderer(typeof(CustomImage), typeof(CustomImageRenderer))]
namespace FormsImageTapGesture.iOS
{
public class CustomImageRenderer : ImageRenderer
{
#region properties & fields
// ---------------------------------------------------------------------------
//
// PROPERTIES & FIELDS
//
// ---------------------------------------------------------------------------
private UIImageView nativeElement;
private CustomImage formsElement;
#endregion
#region methods
// ---------------------------------------------------------------------------
//
// METHODS
//
// ---------------------------------------------------------------------------
//
// Set up the custom renderer. In this case, that means set up the gesture
// recognizer.
//
protected override void OnElementChanged(ElementChangedEventArgs<Image> e) {
base.OnElementChanged (e);
if (e.NewElement != null) {
// Grab the Xamarin.Forms control (not native)
formsElement = e.NewElement as CustomImage;
// Grab the native representation of the Xamarin.Forms control
nativeElement = Control as UIImageView;
// Set up a tap gesture recognizer on the native control
nativeElement.UserInteractionEnabled = true;
UITapGestureRecognizer tgr = new UITapGestureRecognizer (TapHandler);
nativeElement.AddGestureRecognizer (tgr);
}
}
//
// Respond to taps.
//
public void TapHandler(UITapGestureRecognizer tgr) {
CGPoint touchPoint = tgr.LocationInView (nativeElement);
formsElement.OnTapEvent ((int)touchPoint.X, (int)touchPoint.Y);
}
#endregion
}
}
In my application i'm trying to load the main module trough code. Everything works up to the point of rendering and i have NO clue why it isn't rendering. The content has the right values and everything.. My guess is that something went wibbly wobbly and I seem to be missing it.
Control that holds the view
[ContentPropertyAttribute("ContentView")]
public class ContentControlExtened : ContentControl
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Type",
typeof(Type),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentTypeChanged));
public static readonly DependencyProperty ContentViewProperty =
DependencyProperty.Register(
"View",
typeof(FrameworkElement),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentViewChanged));
private static void ContentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public ContentControlExtened()
{
this.Loaded += ContentControlLoaded;
}
private void ContentControlLoaded(object sender, RoutedEventArgs e)
{
this.LoadContent();
}
private void LoadContent()
{
UserControl u = null;
if (Type != null)
{
u = (UserControl)ServiceLocator.Current.GetInstance(this.Type);
}
u.Background = new SolidColorBrush(Color.FromRgb(0, 0, 255));
this.View = u;
}
public Type Type
{
get { return (Type)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Method in shell to load the main view of the given moduleInfo
private void OpenMainView(ModuleInfo module)
{
Type moduleType = Type.GetType(module.ModuleType);
var moduleMainViewType = moduleType.Assembly.GetType(moduleType.Namespace + ".Views.MainView");
this.ContentHolder.MainContent.Type = moduleMainViewType;
}
The contructor of the mainview is straight forward. Just standard control InitializeComponent() and Datacontext is being set trough the servicelocator.
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
Here you are getting and setting the ContentProperty. It should be ContentViewProperty.