Custom property of a custom control not being set - xamarin forms - data-binding

I'm trying to create a custom entry control with EntryType property which I then use inside custom renderers to set platform specific values. But the EntryType is never set when used from Xaml. Here's my code:
public class ExtendedEntry : Xamarin.Forms.Entry
{
public static readonly BindableProperty EntryTypeProperty = BindableProperty.Create(
propertyName: "EntryType",
returnType: typeof(int),
declaringType: typeof(EntryTextType),
defaultValue: 1
);
public EntryTextType EntryType
{
get
{
return (EntryTextType)GetValue(EntryTypeProperty);
}
set
{
SetValue(EntryTypeProperty, value);
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == EntryTypeProperty.PropertyName)
{
}
}
}
public enum EntryTextType
{
Any,
Numeric,
Url,
Email
}
public class ExtendedEntryRenderer : EntryRenderer
{
public ExtendedEntryRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var element = (ExtendedEntry)Element;
Control.Hint = element.Placeholder;
switch(element.EntryType)
{
case EntryTextType.Numeric:
Control.SetRawInputType(Android.Text.InputTypes.ClassNumber);
break;
default:
break;
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var p = e.PropertyName;
base.OnElementPropertyChanged(sender, e);
}
}
And then in XAML, I use the control as this:
<controls:ExtendedEntry Placeholder="Password" IsPassword="True" Text="{Binding Secret}" EntryType="Any"/>
The problem is, EntryType never gets set to EntryTextType.Any, and always uses the default value of EntryTextType.Numeric. What am I missing here? Thanks.

I noticed some discrepancies in the EntryTypeProperty declaration.
Declaring type should be the owner which is ExtendedEntry
And, to instruct XAML to use the enum TypeConverter, you will have to define the datatype as EntryTextType
So your new code will look like:
public static readonly BindableProperty EntryTypeProperty = BindableProperty.Create(
propertyName: "EntryType",
returnType: typeof(EntryTextType),
declaringType: typeof(ExtendedEntry),
defaultValue: EntryTextType.Numeric
);

Related

Allow popups to work in Xamarin Forms WebView

This seems like a simple thing - I want to use a WebView to load / run some JavaScript from a 3rd party.
When I load the page I get a message saying "You must allow popups for this to work". I think I need to create a custom WebView (for iOS and Android!?) and I assume I need one in the shared project too?
I added this to my Android project:
using Android.Content;
using MyApp.Custom;
using MyApp.Droid.Custom;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace MyApp.Droid.Custom
{
public class CustomWebViewRenderer : WebViewRenderer
{
public CustomWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.Settings.JavaScriptCanOpenWindowsAutomatically = true;
Control.Settings.JavaScriptEnabled = true;
}
}
}
and this to my shared project
using Xamarin.Forms;
namespace MyKRing.Custom
{
public class CustomWebView : WebView
{
}
}
and then used "CustomWebView" in my XAML.
<custom:CustomWebView x:Name="WebView"
HeightRequest="1000"
Source="{Binding WebViewContent}"
WidthRequest="1000" />
My TestPageViewModel has
public WebViewSource WebViewContent
{
get => _webViewContent;
set => SetProperty(ref _webViewContent, value);
}
public TestPageViewModel()
{
AmountEntryFrameVisible = true;
NuapayFrameVisible = false;
ConfirmLoadCommand = new Command(ExecuteConfirmLoadCommand);
var localHtml = new HtmlWebViewSource();
localHtml.Html = #"<html>
<body>
<p>This is a test</p>
<script src='https://testurl.com/test.js\'></script>
<script>
TestJS.showUI('1234567890', 'https://testurl.com/ui/');
</script>
</body>
</html>";
WebViewContent = localHtml;
}
With the aim of making the JS run when the page is loaded by WebView.
Am I anywhere close? What is wrong with the above, as it just doesn't do anything (that ( can see).
1. Create the CustomWebView and create the ShowDialog method.
public class CustomWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(CustomWebView ),
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 ShowDialog(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
2. Consume the CustomWebView
<local:CustomWebView x:Name="customWebView " Uri="index.html" />
3. Register the ShowDiaplg action to be invoked from JavaScript
customWebView.RegisterAction(data => DisplayAlert("Alert", DateTime.Now + "/n" + data, "OK"));
4. Create the custom renderer on Android platform.
CustomWebViewRenderer .cs:
[assembly: ExportRenderer(typeof(CustomWebView ), typeof(CustomWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class CustomWebViewRenderer: WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge2.showDialog(data);}";
Context _context;
public CustomWebViewRenderer (Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((CustomWebView )Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient2(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge2(this), "jsBridge2");
Control.LoadUrl($"file:///android_asset/Content/{((CustomWebView )Element).Uri}");
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((CustomWebView )Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
JavascriptWebViewClient2.cs:
public class JavascriptWebViewClient2 : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient2(CustomWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
JSBridge2.cs:
public class JSBridge2 : Java.Lang.Object
{
readonly WeakReference<CustomWebViewRenderer > hybridWebViewRenderer;
public JSBridge2(CustomWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<CustomWebViewRenderer >(hybridRenderer);
}
[JavascriptInterface]
[Export("showDialog")]
public void ShowDialog(string data)
{
CustomWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((CustomWebView)hybridRenderer.Element).ShowDialog(data);
}
//string ret = "Alert :" + DateTime.Now;
//return ret;
}
}
I followed the code sample in the link below. https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/customrenderers-hybridwebview/

DatePicker DateSelected Command

Just wanted to confirm whether DatePicker's DateSelected is not Binding possible like:
DateSelected="{Binding TargetDateSelectedCommand}"/>
To me it looks like handler has to be created in .cs file.
It is possible, you could have a try with Turn Events into Commands with Behaviors.
Custom a Behavior<DatePicker> class as follows:
public class DatePickerSelectedItemBehavior: Behavior<DatePicker>
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create("Command", typeof(ICommand), typeof(DatePickerSelectedItemBehavior), null);
public static readonly BindableProperty InputConverterProperty =
BindableProperty.Create("Converter", typeof(IValueConverter), typeof(DatePickerSelectedItemBehavior), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
public DatePicker AssociatedObject { get; private set; }
protected override void OnAttachedTo(DatePicker bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
bindable.BindingContextChanged += OnBindingContextChanged;
bindable.DateSelected += DatePickerItemSelected;
}
protected override void OnDetachingFrom(DatePicker bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
bindable.DateSelected -= DatePickerItemSelected;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
void DatePickerItemSelected(object sender, DateChangedEventArgs e)
{
if (Command == null)
{
return;
}
object parameter = Converter.Convert(e, typeof(object), null, null);
if (Command.CanExecute(parameter))
{
Command.Execute(parameter);
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
Then used in Xaml:
<ContentPage.Resources>
<ResourceDictionary>
<local:SelectedItemEventArgsToSelectedItemConverter x:Key="SelectedItemConverter" />
</ResourceDictionary>
</ContentPage.Resources>
...
<DatePicker >
<DatePicker.Behaviors>
<local:DatePickerSelectedItemBehavior Command="{Binding OutputAgeCommand}"
Converter="{StaticResource SelectedItemConverter}" />
</DatePicker.Behaviors>
</DatePicker>
Here is the SelectedItemEventArgsToSelectedItemConverter.cs:
public class SelectedItemEventArgsToSelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as DateChangedEventArgs;
return eventArgs.NewDate;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
according to the docs DatePicker does not have any command properties. DateSelected is a DateTime, not a command

Xamarin Forms WebView CanGoForward property is inaccurate on sites that utilize the History API

I am having an issue that relates to a post on GitHub. User nirbil summarizes it best:
If a website utilizes the historyApi then the WebView's CanGoBack CanGoForward are wrong.
Steps to reproduce -
Set the webview's source to a single page web application
Navigate within the application to a different url (should utilize
the pushstate)
Navigate back -> CanGoForward changes to true
Navigate within the application again by following some link ->
CanGoForward erroneously does not change to false
On windows this behavior can be traced to the webview's renderer - https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.UAP/WebViewRenderer.cs
Here is the original post on GitHub https://github.com/xamarin/Xamarin.Forms/issues/7691
A lot of ideas are suggested on how to solve the problem however, my grasp of Xamarin Forms is somewhat lacking. Any ideas on how to solve the issue?
You could add the source of webview to use renderer. I use the code sample on GitHub. https://github.com/xamarin/Xamarin.Forms/issues/7691
The original:
Set the source of webview.
<WebView x:Name="webView" Source="https://www.google.com" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></WebView>
Now:
This question on GitHub has been collected. You could follow it on GitHub. It would be fixed soon.
I solved it using a custom Xamarin Forms WebView with custom CanGoBack/Forward properties.
You can find the it here:
https://github.com/nirbil/XF.CanGoWebView
Following are the main bits of the solution.
CustomWebView control with new bindable properties:
public class CustomWebView : WebView
{
public CustomWebView() {}
public static BindableProperty CustomCanGoForwardProperty =
BindableProperty.Create(
nameof(CustomCanGoForward),
typeof(bool),
typeof(CustomWebView),
false,
BindingMode.OneWayToSource);
public static BindableProperty CustomCanGoBackProperty =
BindableProperty.Create(
nameof(CustomCanGoBack),
typeof(bool),
typeof(CustomWebView),
defaultValue: false,
BindingMode.OneWayToSource);
public bool CustomCanGoForward
{
get => (bool)GetValue(CustomCanGoForwardProperty);
set => SetValue(CustomCanGoForwardProperty, value);
}
public bool CustomCanGoBack
{
get => (bool)GetValue(CustomCanGoBackProperty);
set => SetValue(CustomCanGoBackProperty, value);
}
}
Custom WebViewRenderer on Android:
public class CustomWebViewRenderer : WebViewRenderer
{
protected override WebViewClient GetWebViewClient()
{
CustomWebViewClient webViewClient = new CustomWebViewClient(this);
webViewClient.AddressChanged += AddressChanged;
return webViewClient;
}
private void AddressChanged(string url)
{
if (Element is CustomWebView customWebView && Control != null)
{
customWebView.CustomCanGoBack = Control.CanGoBack();
customWebView.CustomCanGoForward = Control.CanGoForward();
}
}
}
Android CustomWebViewClient that intercepts changes and alerts the renderer:
public class CustomWebViewClient: FormsWebViewClient
{
public delegate void AddressChangedEventHandler(string url);
public event AddressChangedEventHandler AddressChanged;
public CustomWebViewClient(WebViewRenderer renderer) : base(renderer) {}
public override void DoUpdateVisitedHistory(WebView view, string url, bool isReload)
{
base.DoUpdateVisitedHistory(view, url, isReload);
AddressChanged?.Invoke(view.Url);
}
}
Custom WebViewRenderer on UWP:
public class CustomWebViewRenderer : WebViewRenderer
{
bool _registeredControl = false;
long _goBackRegistrationToken = 0;
long _goForwardRegistrationToken = 0;
protected override void Dispose(bool disposing)
{
UnregisterControl();
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
RegisterControl();
}
private void UnregisterControl()
{
if (Control != null && _registeredControl)
{
_registeredControl = false;
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, _goBackRegistrationToken);
Control.UnregisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, _goForwardRegistrationToken);
}
}
private void RegisterControl()
{
if (Control != null && !_registeredControl)
{
_registeredControl = true;
_goBackRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoBackProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoBackChanged));
_goForwardRegistrationToken = Control.RegisterPropertyChangedCallback(Windows.UI.Xaml.Controls.WebView.CanGoForwardProperty, new Windows.UI.Xaml.DependencyPropertyChangedCallback(OnWebViewCanGoForwardChanged));
}
}
private void OnWebViewCanGoBackChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoBack = Control.CanGoBack;
}
private void OnWebViewCanGoForwardChanged(Windows.UI.Xaml.DependencyObject sender, Windows.UI.Xaml.DependencyProperty dp)
{
((CustomWebView)Element).CustomCanGoForward = Control.CanGoForward;
}
}

Switch Toggling Event Using MVVM

I realized recently that a switch doesn't have a command. And i need to bind the toggling event to my view model. How do i go about it?
I tried to bind the command to Toggled event but the code is running into error
You can use EventToCommandBehavior to convert the event to command
create the EventToCommandBehavior class
using System;
using Xamarin.Forms;
namespace xxx
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null)
{
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom(T bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
}
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace xxx
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent(EventName);
}
protected override void OnDetachingFrom(View bindable)
{
DeregisterEvent(EventName);
base.OnDetachingFrom(bindable);
}
void RegisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
}
void DeregisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (eventHandler == null)
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent(object sender, object eventArgs)
{
if (Command == null)
{
return;
}
object resolvedParameter;
if (CommandParameter != null)
{
resolvedParameter = CommandParameter;
}
else if (Converter != null)
{
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
}
else
{
resolvedParameter = eventArgs;
}
if (Command.CanExecute(resolvedParameter))
{
Command.Execute(resolvedParameter);
}
}
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null)
{
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent(oldEventName);
behavior.RegisterEvent(newEventName);
}
}
}
in your xaml
<Switch >
<Switch.Behaviors>
<local:EventToCommandBehavior EventName="Toggled" Command="{Binding ToggledCommand}"/>
</Switch.Behaviors>
</Switch>
And in your ViewModel
public class MyViewModel
{
public ICommand ToggledCommand { get; private set; }
public MyViewModel()
{
ToggledCommand = new Command(() => {
// do some thing you want
});
}
}
I bind a bool property on my viewmodel to the IsToggled property on the switch, then handle when this changes in the viewmodel.

Custom Control parameters binding order

pardon the cross-posting in the xamarin forum, but no one answers me there.
Some time ago I was looking for a Repeater-like control in XF, and I finally get this http://www.qimata.com/?p=7671, very simple indeed. I then started the usual "why don't add this, why don't add that" and so I added other properties and templates. Now, the control works very well for now, but I have a problem (apart from this, I don't think that is the best way to handle this scenario, if you have advice please share your thoughts).
All the logic is in the ItemsChanged event, that fires when ItemSource property is bound. Now, if I don't write the property for last, when event fires the other are yet to be evaluated. For example, this
<local:RepeaterView ShowSeparator="false" ItemsSource="{Binding itemsource}">
is not the same of
<local:RepeaterView ItemsSource="{Binding itemsource}" ShowSeparator="false">
Only in the first case property ShowSeparator has the expected value, because ItemsChanged fires before parameter initialization. Now, caring about parameters' order is not acceptable, then how can I handle this in a more decently manner?
public class RepeaterView : StackLayout
{
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(RepeaterView), default(DataTemplate));
public static readonly BindableProperty HeaderTemplateProperty = BindableProperty.Create(nameof(HeaderTemplate), typeof(DataTemplate), typeof(RepeaterView), default(DataTemplate));
public static readonly BindableProperty EmptyTextTemplateProperty = BindableProperty.Create(nameof(EmptyTextTemplate), typeof(DataTemplate), typeof(RepeaterView), default(DataTemplate));
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(ICollection), typeof(RepeaterView), new List<object>(), BindingMode.OneWay, null, propertyChanged: (bindable, oldValue, newValue) => { ItemsChanged(bindable, (ICollection)oldValue, (ICollection)newValue); });
public static readonly BindableProperty EmptyTextProperty = BindableProperty.Create(nameof(EmptyText), typeof(string), typeof(RepeaterView), string.Empty);
public static readonly BindableProperty SelectedItemCommandProperty = BindableProperty.Create(nameof(SelectedItemCommand), typeof(ICommand), typeof(RepeaterView), default(ICommand));
public ICollection ItemsSource
{
get { return (ICollection)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
public DataTemplate EmptyTextTemplate
{
get { return (DataTemplate)GetValue(EmptyTextTemplateProperty); }
set { SetValue(EmptyTextTemplateProperty, value); }
}
public string EmptyText
{
get { return (string)GetValue(EmptyTextProperty); }
set { SetValue(EmptyTextProperty, value); }
}
public ICommand SelectedItemCommand
{
get { return (ICommand)GetValue(SelectedItemCommandProperty); }
set { SetValue(SelectedItemCommandProperty, value); }
}
public bool ShowSeparator { get; set; } = true;
private static void ItemsChanged(BindableObject bindable, ICollection oldValue, ICollection newValue)
{
var repeater = (RepeaterView)bindable;
repeater.Children.Clear();
var headerTemplate = repeater.HeaderTemplate;
if (headerTemplate != null)
{
var content = headerTemplate.CreateContent();
if (!(content is View) && !(content is ViewCell))
{
//throws exception
}
var view = (content is View) ? content as View : ((ViewCell)content).View;
repeater.Children.Add(view);
repeater.Children.Add(new Divider());
}
if (newValue.Count == 0 && (repeater.EmptyTextTemplate != null || !string.IsNullOrEmpty(repeater.EmptyText)))
{
if (repeater.EmptyTextTemplate == null)
repeater.Children.Add(new Label { Text = repeater.EmptyText });
else
{
var content = repeater.EmptyTextTemplate.CreateContent();
if (!(content is View) && !(content is ViewCell))
{
//throws exception
}
var view = (content is View) ? content as View : ((ViewCell)content).View;
repeater.Children.Add(view);
}
return;
}
var dataTemplate = repeater.ItemTemplate;
foreach (object item in newValue)
{
var content = dataTemplate.CreateContent();
if (!(content is View) && !(content is ViewCell))
{
//throws exception
}
var view = (content is View) ? content as View : ((ViewCell)content).View;
if (repeater.SelectedItemCommand != null)
{
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (sender, e) => { repeater.SelectedItemCommand?.Execute(item); };
view.GestureRecognizers.Add(tapGestureRecognizer);
}
view.BindingContext = item;
repeater.Children.Add(view);
if (repeater.ShowSeparator)
repeater.Children.Add(new Divider { Margin = new Thickness(5, 0) });
}
}
}
}
The best strategy here would be to make sure that the items are only calculated first, once they are really requested (like in LayoutChildren).
So in OnItemSourceChanged, you only set the ItemSource, but don't do anything further unless inizialization was already done.
Should look somewhat like this (pseude-code):
private static void ItemsChanged(...)
{
var repeater = (Repeaterview)bindable;
repeater.ItemsSource = value;
if(repeater.IsInitialized) UpdateItems();
}
private override void LayoutChildren()
{
IsInitialized = true;
UpdateItems();
}
This is the basic strategy. I'll update to the correct methods/overrides once I find the time to do so. Feel free to update this answer, if you happen to find out before me.

Resources