Month Picker In Xamarin Forms - xamarin

I am having a requirement like a Month picker which is showing only month and year and not the date .How can we achieve the same for both IOS and Android platform using Xamarin forms ?

You could implement it by using Custom Renderer
in Forms
create a custom View
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace App20
{
public class MonthYearPickerView :View
{
public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(
propertyName: nameof(FontSize),
returnType: typeof(double),
declaringType: typeof(MonthYearPickerView),
defaultValue: (double)24,
defaultBindingMode: BindingMode.TwoWay);
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
propertyName: nameof(TextColor),
returnType: typeof(Color),
declaringType: typeof(MonthYearPickerView),
defaultValue: Color.White,
defaultBindingMode: BindingMode.TwoWay);
public Color TextColor
{
get => (Color)GetValue(TextColorProperty);
set => SetValue(TextColorProperty, value);
}
public static readonly BindableProperty InfiniteScrollProperty = BindableProperty.Create(
propertyName: nameof(InfiniteScroll),
returnType: typeof(bool),
declaringType: typeof(MonthYearPickerView),
defaultValue: true,
defaultBindingMode: BindingMode.TwoWay);
public bool InfiniteScroll
{
get => (bool)GetValue(InfiniteScrollProperty);
set => SetValue(InfiniteScrollProperty, value);
}
public static readonly BindableProperty DateProperty = BindableProperty.Create(
propertyName: nameof(Date),
returnType: typeof(DateTime),
declaringType: typeof(MonthYearPickerView),
defaultValue: default,
defaultBindingMode: BindingMode.TwoWay);
public DateTime Date
{
get => (DateTime)GetValue(DateProperty);
set => SetValue(DateProperty, value);
}
public static readonly BindableProperty MaxDateProperty = BindableProperty.Create(
propertyName: nameof(MaxDate),
returnType: typeof(DateTime?),
declaringType: typeof(MonthYearPickerView),
defaultValue: default,
defaultBindingMode: BindingMode.TwoWay);
public DateTime? MaxDate
{
get => (DateTime?)GetValue(MaxDateProperty);
set => SetValue(MaxDateProperty, value);
}
public static readonly BindableProperty MinDateProperty = BindableProperty.Create(
propertyName: nameof(MinDate),
returnType: typeof(DateTime?),
declaringType: typeof(MonthYearPickerView),
defaultValue: default,
defaultBindingMode: BindingMode.TwoWay);
public DateTime? MinDate
{
get => (DateTime?)GetValue(MinDateProperty);
set => SetValue(MinDateProperty, value);
}
}
}
in iOS
using System;
using App20;
using App20.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MonthYearPickerView), typeof(MonthYearPickerRenderer))]
namespace App20.iOS
{
public class MonthYearPickerRenderer : ViewRenderer<MonthYearPickerView, UITextField>
{
private DateTime _selectedDate;
private UITextField _dateLabel;
private PickerDateModel _pickerModel;
protected override void OnElementChanged(ElementChangedEventArgs<MonthYearPickerView> e)
{
base.OnElementChanged(e);
_dateLabel = new UITextField();
_dateLabel.TextAlignment = UITextAlignment.Center;
var dateToday = DateTime.Today;
SetupPicker(new DateTime(dateToday.Year, dateToday.Month, 1));
SetNativeControl(_dateLabel);
Control.EditingChanged += ControlOnEditingChanged;
Element.PropertyChanged += Element_PropertyChanged;
}
private void ControlOnEditingChanged(object sender, EventArgs e)
{
var currentDate = $"{Element.Date.Month:D2} | {Element.Date.Year}";
if (_dateLabel.Text != currentDate)
{
_dateLabel.Text = currentDate;
}
}
protected override void Dispose(bool disposing)
{
Element.PropertyChanged -= Element_PropertyChanged;
base.Dispose(disposing);
}
private void SetupPicker(DateTime date)
{
var datePicker = new UIPickerView();
_pickerModel = new PickerDateModel(datePicker, date, Element.MaxDate, Element.MinDate);
datePicker.ShowSelectionIndicator = true;
_selectedDate = date;
_pickerModel.PickerChanged += (sender, e) =>
{
_selectedDate = e;
};
datePicker.Model = _pickerModel;
_pickerModel.MaxDate = Element.MaxDate ?? DateTime.MaxValue;
_pickerModel.MinDate = Element.MinDate ?? DateTime.MinValue;
var toolbar = new UIToolbar
{
BarStyle = UIBarStyle.Default,
Translucent = true
};
toolbar.SizeToFit();
var doneButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done,
(s, e) =>
{
Element.Date = _selectedDate;
_dateLabel.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
_dateLabel.ResignFirstResponder();
});
toolbar.SetItems(new[] { new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace), doneButton }, true);
_dateLabel.InputView = datePicker;
_dateLabel.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
_dateLabel.InputAccessoryView = toolbar;
_dateLabel.TextColor = Element.TextColor.ToUIColor();
}
private void Element_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == MonthYearPickerView.MaxDateProperty.PropertyName)
{
_pickerModel.MaxDate = Element.MaxDate ?? DateTime.MinValue;
}
else if (e.PropertyName == MonthYearPickerView.MinDateProperty.PropertyName)
{
_pickerModel.MinDate = Element.MinDate ?? DateTime.MaxValue;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UIKit;
namespace App20.iOS
{
public class PickerDateModel : UIPickerViewModel
{
public event EventHandler<DateTime> PickerChanged;
#region Fields
private readonly List<string> _mainNamesOfMonthSource;
private readonly List<int> _mainYearsSource;
private readonly UIPickerView _picker;
private readonly int _numberOfComponents;
private readonly int _minYear;
private readonly int _maxYear;
private List<int> _years;
private List<string> _namesOfMonth;
private DateTime _selectedDate;
private DateTime _maxDate;
private DateTime _minDate;
#endregion Fields
#region Constructors
public PickerDateModel(UIPickerView datePicker, DateTime selectedDate, DateTime? maxDate, DateTime? minDate)
{
_mainNamesOfMonthSource = DateTimeFormatInfo.CurrentInfo?.MonthNames
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
_maxDate = maxDate ?? DateTime.MaxValue;
_minDate = minDate ?? DateTime.MinValue;
_maxYear = _maxDate.Year;
_minYear = _minDate.Year;
_years = new List<int>();
_picker = datePicker;
_namesOfMonth = _mainNamesOfMonthSource;
_numberOfComponents = 2;
SelectedDate = selectedDate;
}
#endregion Constructors
#region Properties
public DateTime SelectedDate
{
get => _selectedDate;
set
{
_selectedDate = value;
ReloadSections();
PickerChanged?.Invoke(this, value);
}
}
public DateTime MaxDate
{
get => _maxDate;
set
{
_maxDate = value;
ReloadSections();
}
}
public DateTime MinDate
{
get => _minDate;
set
{
_minDate = value;
ReloadSections();
}
}
#endregion Properties
#region Private Methods
private void ReloadSections()
{
var selectedDate = SelectedDate == DateTime.MinValue
? DateTime.Today
: SelectedDate;
_years.Clear();
for (int i = _minYear; i <= _maxYear; i++)
{
_years.Add(i);
}
_namesOfMonth = _mainNamesOfMonthSource;
if (SelectedDate.Year == MinDate.Year)
{
_namesOfMonth = _mainNamesOfMonthSource.Skip(MinDate.Month - 1).ToList();
}
if (SelectedDate.Year == MaxDate.Year)
{
_namesOfMonth = _mainNamesOfMonthSource.Take(MaxDate.Month).ToList();
}
SetCarousels(selectedDate);
}
#endregion Private Methods
#region Public Methods
public void SetCarousels(DateTime dateTime)
{
if (_picker.NumberOfComponents != _numberOfComponents) return;
var y = DateTimeFormatInfo.CurrentInfo?.GetMonthName(dateTime.Month);
var x = _namesOfMonth.IndexOf(y);
_picker.Select(x, 0, false);
_picker.Select(_years.IndexOf(dateTime.Year), 1, false);
_picker.ReloadComponent(0);
_picker.ReloadComponent(1);
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return _numberOfComponents;
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
{
return _namesOfMonth.Count;
}
else if (component == 1)
{
return _years.Count;
}
else
{
return 0;
}
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
{
return _namesOfMonth.Count==0 ? _namesOfMonth.First() : _namesOfMonth[(int)row];
}
else if (component == 1)
{
var list = _years;
return _years.Count==0? _years.First().ToString() : _years[(int)row].ToString();
}
else
{
return row.ToString();
}
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var month = GetMonthNumberByName(_namesOfMonth[(int)pickerView.SelectedRowInComponent(0)]);
var year = _years[(int)pickerView.SelectedRowInComponent(1)];
if (year == MinDate.Year)
{
month = month >= MinDate.Month ? month : MinDate.Month;
}
if (year == MaxDate.Year)
{
month = month <= MaxDate.Month ? month : MaxDate.Month;
}
SelectedDate = new DateTime(year, month, 1);
ReloadSections();
pickerView.ReloadAllComponents();
int GetMonthNumberByName(string monthName) =>
DateTime.ParseExact(monthName, "MMMM", CultureInfo.CurrentCulture).Month;
}
#endregion Public Methods
}
}
in Android
in MainActivity
public static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
Instance = this;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
using Android.Content;
using Android.Support.V7.App;
using Android.Widget;
using App20;
using App20.Droid;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(MonthYearPickerView), typeof(MonthYearPickerRenderer))]
namespace App20.Droid
{
public class MonthYearPickerRenderer : ViewRenderer<MonthYearPickerView, EditText>
{
private readonly Context _context;
private MonthYearPickerDialog _monthYearPickerDialog;
public MonthYearPickerRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<MonthYearPickerView> e)
{
base.OnElementChanged(e);
CreateAndSetNativeControl();
Control.KeyListener = null;
Element.Focused += Element_Focused;
}
protected override void Dispose(bool disposing)
{
if (Control == null) return;
Element.Focused -= Element_Focused;
if (_monthYearPickerDialog != null)
{
_monthYearPickerDialog.OnDateTimeChanged -= OnDateTimeChanged;
_monthYearPickerDialog.OnClosed -= OnClosed;
_monthYearPickerDialog.Hide();
_monthYearPickerDialog.Dispose();
_monthYearPickerDialog = null;
}
base.Dispose(disposing);
}
#region Private Methods
private void ShowDatePicker()
{
if (_monthYearPickerDialog == null)
{
_monthYearPickerDialog = new MonthYearPickerDialog();
_monthYearPickerDialog.OnDateTimeChanged += OnDateTimeChanged;
_monthYearPickerDialog.OnClosed += OnClosed;
}
_monthYearPickerDialog.Date = Element.Date;
_monthYearPickerDialog.MinDate = FormatDateToMonthYear(Element.MinDate);
_monthYearPickerDialog.MaxDate = FormatDateToMonthYear(Element.MaxDate);
_monthYearPickerDialog.InfiniteScroll = Element.InfiniteScroll;
var appcompatActivity = MainActivity.Instance;
var mFragManager = appcompatActivity?.SupportFragmentManager;
if (mFragManager != null)
{
_monthYearPickerDialog.Show(mFragManager, nameof(MonthYearPickerDialog));
}
}
private void ClearPickerFocus()
{
((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
Control.ClearFocus();
}
private DateTime? FormatDateToMonthYear(DateTime? dateTime) =>
dateTime.HasValue ? (DateTime?) new DateTime(dateTime.Value.Year, dateTime.Value.Month, 1) : null;
private void CreateAndSetNativeControl()
{
var tv = new EditText(_context);
tv.SetTextColor(Element.TextColor.ToAndroid());
tv.TextSize = (float)Element.FontSize;
tv.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
tv.Gravity = Android.Views.GravityFlags.Center;
tv.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
SetNativeControl(tv);
}
#endregion
#region Event Handlers
private void Element_Focused(object sender, FocusEventArgs e)
{
if (e.IsFocused)
{
ShowDatePicker();
}
}
private void OnClosed(object sender, DateTime e)
{
ClearPickerFocus();
}
private void OnDateTimeChanged(object sender, DateTime e)
{
Element.Date = e;
Control.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
ClearPickerFocus();
}
#endregion
}
}
using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
using System;
using System.Linq;
namespace App20.Droid
{
public class MonthYearPickerDialog : Android.Support.V4.App.DialogFragment
{
public event EventHandler<DateTime> OnDateTimeChanged;
public event EventHandler<DateTime> OnClosed;
#region Private Fields
private const int DefaultDay = 1;
private const int MinNumberOfMonths = 1;
private const int MaxNumberOfMonths = 12;
private const int MinNumberOfYears = 1900;
private const int MaxNumberOfYears = 2100;
private NumberPicker _monthPicker;
private NumberPicker _yearPicker;
#endregion
#region Public Properties
public DateTime? MinDate { get; set; }
public DateTime? MaxDate { get; set; }
public DateTime? Date { get; set; }
public bool InfiniteScroll { get; set; }
#endregion
public void Hide() => base.Dialog?.Hide();
public override Dialog OnCreateDialog(Bundle savedInstanceState)
{
var builder = new AlertDialog.Builder(Activity);
var inflater = Activity.LayoutInflater;
var selectedDate = GetSelectedDate();
var dialog = inflater.Inflate(Resource.Layout.date_picker_dialog, null);
_monthPicker = (NumberPicker)dialog.FindViewById(Resource.Id.picker_month);
_yearPicker = (NumberPicker)dialog.FindViewById(Resource.Id.picker_year);
InitializeMonthPicker(selectedDate.Month);
InitializeYearPicker(selectedDate.Year);
SetMaxMinDate(MaxDate, MinDate);
builder.SetView(dialog)
.SetPositiveButton("Ok", (sender, e) =>
{
selectedDate = new DateTime(_yearPicker.Value, _monthPicker.Value, DefaultDay);
OnDateTimeChanged?.Invoke(dialog, selectedDate);
})
.SetNegativeButton("Cancel", (sender, e) =>
{
Dialog.Cancel();
OnClosed?.Invoke(dialog, selectedDate);
});
return builder.Create();
}
protected override void Dispose(bool disposing)
{
if (_yearPicker != null)
{
_yearPicker.ScrollChange -= YearPicker_ScrollChange;
_yearPicker.Dispose();
_yearPicker = null;
}
_monthPicker?.Dispose();
_monthPicker = null;
base.Dispose(disposing);
}
#region Private Methods
private DateTime GetSelectedDate() => Date ?? DateTime.Now;
private void InitializeYearPicker(int year)
{
_yearPicker.MinValue = MinNumberOfYears;
_yearPicker.MaxValue = MaxNumberOfYears;
_yearPicker.Value = year;
_yearPicker.ScrollChange += YearPicker_ScrollChange;
if (!InfiniteScroll)
{
_yearPicker.WrapSelectorWheel = false;
_yearPicker.DescendantFocusability = DescendantFocusability.BlockDescendants;
}
}
private void InitializeMonthPicker(int month)
{
_monthPicker.MinValue = MinNumberOfMonths;
_monthPicker.MaxValue = MaxNumberOfMonths;
_monthPicker.SetDisplayedValues(GetMonthNames());
_monthPicker.Value = month;
if (!InfiniteScroll)
{
_monthPicker.WrapSelectorWheel = false;
_monthPicker.DescendantFocusability = DescendantFocusability.BlockDescendants;
}
}
private void YearPicker_ScrollChange(object sender, View.ScrollChangeEventArgs e)
{
SetMaxMinDate(MaxDate, MinDate);
}
private void SetMaxMinDate(DateTime? maxDate, DateTime? minDate)
{
try
{
if (maxDate.HasValue)
{
var maxYear = maxDate.Value.Year;
var maxMonth = maxDate.Value.Month;
if (_yearPicker.Value == maxYear)
{
_monthPicker.MaxValue = maxMonth;
}
else if (_monthPicker.MaxValue != MaxNumberOfMonths)
{
_monthPicker.MaxValue = MaxNumberOfMonths;
}
_yearPicker.MaxValue = maxYear;
}
if (minDate.HasValue)
{
var minYear = minDate.Value.Year;
var minMonth = minDate.Value.Month;
if (_yearPicker.Value == minYear)
{
_monthPicker.MinValue = minMonth;
}
else if (_monthPicker.MinValue != MinNumberOfMonths)
{
_monthPicker.MinValue = MinNumberOfMonths;
}
_yearPicker.MinValue = minYear;
}
_monthPicker.SetDisplayedValues(GetMonthNames(_monthPicker.MinValue));
}
catch (Exception e)
{
}
}
private string[] GetMonthNames(int start = 1) =>
System.Globalization.DateTimeFormatInfo.CurrentInfo?.MonthNames.Skip(start - 1).ToArray();
#endregion
}
}
create date_picker_dialog.xml in Resource ->layout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal">
<NumberPicker
android:id="#+id/picker_month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp">
</NumberPicker>
<NumberPicker
android:id="#+id/picker_year"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</NumberPicker>
</LinearLayout>
</LinearLayout>
Now you can reference it in Xaml
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<local:MonthYearPickerView
Date="06.15.2020"
BackgroundColor="LightBlue"
WidthRequest="150"
MinDate="01.01.2020"
MaxDate="12.31.2050"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Center" />
</StackLayout>

Related

DatePicker With Placeholder using Xamarin

I created a custom DatePicker to look like it has placeholder, but the
problem is, when clicking the DatePicker then when the DatePicker
Dialog opens and I click on okay with the preselected/default date
value of today, The date is not written in the field. Below is my
code.
I think the reason is that since "Date" property has a default value
of today and there is no change in the selection when the "OK" button
was clicked
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XXX.XXX.Mobile.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DatePickerWithPlaceHolder : DatePicker
{
public static readonly BindableProperty NullableDateProperty = BindableProperty.Create(nameof(NullableDate),typeof(DateTime?),typeof(DatePickerWithPlaceHolder), null, BindingMode.TwoWay);
private string _format;
public DateTime? NullableDate
{
get => (DateTime?)GetValue(NullableDateProperty);
set
{
SetValue(NullableDateProperty, value);
UpdateDate();
}
}
private void UpdateDate()
{
DatePickerWithPlaceHolder datePicker = this;
if (NullableDate.HasValue)
{
if (null != _format) Format = _format;
Date = NullableDate.Value;
datePicker.FontAttributes = FontAttributes.None;
datePicker.TextColor = Color.Black;
}
else
{
_format = Format;
Format = "Select Date";
datePicker.FontAttributes = FontAttributes.Italic;
datePicker.TextColor = Color.Gray;
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
UpdateDate();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Date") NullableDate = Date;
if (propertyName == "NullableDate") UpdateDate();
}
}
}
I think you need set NullableDate to null in the background code.
Below is the sample code for your reference:
DatePickerWithPlaceHolder.cs
public class DatePickerWithPlaceHolder : DatePicker
{
private string _format = null;
public static readonly BindableProperty NullableDateProperty = BindableProperty.Create<DatePickerWithPlaceHolder, DateTime?>(p => p.NullableDate, null, BindingMode.TwoWay);
public DateTime? NullableDate
{
get { return (DateTime?)GetValue(NullableDateProperty); }
set { SetValue(NullableDateProperty, value); UpdateDate(); }
}
private void UpdateDate()
{
if (NullableDate.HasValue) { if (null != _format) Format = _format; Date = NullableDate.Value; }
else { _format = Format; Format = "Select Date ..."; FontAttributes = FontAttributes.Italic;
TextColor = Color.Green;
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
UpdateDate();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Date") NullableDate = Date;
}
}
Consume it in Xaml:
<appentrymask:DatePickerWithPlaceHolder x:Name="myDatePicker"/>
Initialize the NullableDate in code behind:
public MainPage()
{
InitializeComponent();
myDatePicker.NullableDate = null;
}
Last but not least,for detecting the OK or Cancel event, you could refer to this thread.

Xamarin Forms Webview Custom Renderer Zoom not working

Ok, so I've been trying to get the custom renderer working but I have a problem.
The Xamarin Forums discussion is here: https://forums.xamarin.com/discussion/comment/376813#Comment_376813
The problem is tht when I zoom out the webview stays in a part of the screen and doesn't fit the whole screen.
I add the code of the project:
MyWebView.cs
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace CofarLE_Ejemplo_5
{
public class MyWebView : WebView
{
public int ZoomInLevel
{
get { return (int)GetValue(ZoomInLevelProperty); }
set { SetValue(ZoomInLevelProperty, value); }
}
public bool EnableZoomControl
{
get { return (bool)GetValue(EnableZoomControlProperty); }
set { SetValue(EnableZoomControlProperty, value); }
}
public static readonly BindableProperty ZoomInLevelProperty = BindableProperty.Create(propertyName: "ZoomInLevel", returnType: typeof(int), declaringType: typeof(MyWebView), defaultValue: 100, propertyChanged: OnZoomInLevelPropertyChanged);
public static readonly BindableProperty EnableZoomControlProperty = BindableProperty.Create(propertyName: "EnableZoomControl", returnType: typeof(bool), declaringType: typeof(MyWebView), defaultValue: false, propertyChanged: OnEnableZoomChanged);
private static void OnZoomInLevelPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control1 = (MyWebView)bindable;
control1.ZoomInLevel = (int)newValue;
}
private static void OnEnableZoomChanged(BindableObject bindable, object oldValue, object newValue)
{
var control1 = (MyWebView)bindable;
control1.EnableZoomControl = (bool)newValue;
}
}
}
MyWebViewRendereriOS.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using CofarLE_Ejemplo_5;
using CofarLE_Ejemplo_5.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRendereriOS))]
namespace CofarLE_Ejemplo_5.iOS
{
public class MyDelegate : UIScrollViewDelegate
{
public UIView myView;
public UIView ViewForZoom;
public MyDelegate(UIView view)
{
myView = view;
ViewForZoom = view;
}
public override UIView ViewForZoomingInScrollView(UIScrollView scrollView)
{
MessagingCenter.Subscribe<object, bool>(this, "zoom", (sender, arg) => {
if (arg == true)
{
myView.ContentMode = UIViewContentMode.ScaleToFill;
ViewForZoom = myView;
}
else
{
ViewForZoom = null;
}
});
return ViewForZoom;
}
}
public class MyWebViewRendereriOS : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (NativeView != null && e.NewElement != null)
{
var control1 = NativeView as UIWebView;
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
}
if (e.NewElement != null)
{
e.NewElement.PropertyChanged += OnElementPropertyChanged;
}
control1.ScalesPageToFit = true;
control1.ScrollView.Delegate = new MyDelegate(control1);
control1.ContentMode = UIViewContentMode.ScaleToFill;
control1.ScrollView.SizeToFit();
}
}
private void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var control1 = NativeView as UIWebView;
if (control1 == null)
{
return;
}
control1.ScalesPageToFit = true;
ScrollView.MaximumZoomScale = 2;
ScrollView.MinimumZoomScale = nfloat.Parse("0.5");
control1.ScrollView.MaximumZoomScale = 2;
control1.ScrollView.SizeToFit();
control1.ScrollView.MinimumZoomScale = nfloat.Parse("0.5");
}
}
}
you could try this,is it the effect you need(here is for Android)
1.create a custom view ZoomWebView:
public class ZoomWebView : View
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(ZoomWebView),
defaultValue: default(string));
public string Uri {
get { return (string)GetValue (UriProperty); }
set { SetValue (UriProperty, value); }
}
}
2. custom renderer ZoomWebViewRenderer
public class ZoomWebViewRenderer : ViewRenderer<ZoomWebView, Android.Webkit.WebView>
{
Context _context;
public ZoomWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<ZoomWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.Settings.UseWideViewPort = true;
webView.Settings.LoadWithOverviewMode = true;
webView.Settings.SetSupportZoom(true);
webView.Settings.BuiltInZoomControls = true;
webView.Settings.DisplayZoomControls = false;
webView.SetWebViewClient(new ZoomWebViewClient());
SetNativeControl(webView);
Control.LoadUrl(Element.Uri);
}
}
class ZoomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
{
view.LoadUrl(request.Url.ToString());
return true;
}
}
}
3.call in the page xaml:
<ContentPage.Content>
<local:ZoomWebView Uri="https://www.microsoft.com" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentPage.Content>

Remove Placeholder from TextInputLayout in Xamarin Forms for Android & IOS

I am trying to create Suggestion Searchbar using Entry
i was able to create the suggestion searchbar but i am not able to change the look of this Entry, i want to remove the tooltip above (Search by Speciality..) and just want it to look like other entry in the app
could anyone help me what and where i had to make change for getting that look
here is my code
Xamarin Android :
[assembly: ExportRenderer(typeof(AutoCompleteViewv2),
typeof(AutoCompleteViewRendererv2))]
namespace App.Droid.Renderers.RevisedRenderer
{
public class AutoCompleteViewRendererv2 :
FormsAppCompat.ViewRenderer<AutoCompleteViewv2, TextInputLayout>
{
public AutoCompleteViewRendererv2(Context context) : base(context)
{
}
private AppCompatAutoCompleteTextView NativeControl => Control?.EditText
as AppCompatAutoCompleteTextView;
protected override TextInputLayout CreateNativeControl()
{
var textInputLayout = new TextInputLayout(Context);
var autoComplete = new AppCompatAutoCompleteTextView(Context)
{
BackgroundTintList = ColorStateList.ValueOf(GetPlaceholderColor()),
Text = Element?.Text,
Hint = Element?.Placeholder,
};
GradientDrawable gd = new GradientDrawable();
gd.SetColor(global::Android.Graphics.Color.Transparent);
autoComplete.SetBackground(gd);
if (Element != null)
{
autoComplete.SetHintTextColor(Element.PlaceholderColor.ToAndroid());
autoComplete.SetTextColor(Element.TextColor.ToAndroid());
}
textInputLayout.AddView(autoComplete);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<AutoCompleteViewv2> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// unsubscribe
NativeControl.ItemClick -= AutoCompleteOnItemSelected;
var elm = e.OldElement;
elm.CollectionChanged -= ItemsSourceCollectionChanged;
}
if (e.NewElement != null)
{
SetNativeControl(CreateNativeControl());
// subscribe
SetItemsSource();
SetThreshold();
KillPassword();
NativeControl.TextChanged += (s, args) => Element.RaiseTextChanged(NativeControl.Text);
NativeControl.ItemClick += AutoCompleteOnItemSelected;
var elm = e.NewElement;
elm.CollectionChanged += ItemsSourceCollectionChanged;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
KillPassword();
if (e.PropertyName == AutoCompleteViewv2.ItemsSourceProperty.PropertyName)
SetItemsSource();
else if (e.PropertyName == AutoCompleteViewv2.ThresholdProperty.PropertyName)
SetThreshold();
}
private void AutoCompleteOnItemSelected(object sender, AdapterView.ItemClickEventArgs args)
{
var view = (AutoCompleteTextView)sender;
var selectedItemArgs = new SelectedItemChangedEventArgs(view.Text);
var element = (AutoCompleteViewv2)Element;
element.OnItemSelectedInternal(Element, selectedItemArgs);
}
private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
var element = (AutoCompleteViewv2)Element;
ResetAdapter(element);
}
private void KillPassword()
{
if (Element.IsPassword)
throw new NotImplementedException("Cannot set IsPassword on a AutoComplete");
}
private void ResetAdapter(AutoCompleteViewv2 element)
{
var adapter = new BoxArrayAdapter(Context,
Android.Resource.Layout.SimpleDropDownItem1Line,
element.ItemsSource.ToList(),
element.SortingAlgorithm);
NativeControl.Adapter = adapter;
adapter.NotifyDataSetChanged();
}
private void SetItemsSource()
{
var element = (AutoCompleteViewv2)Element;
if (element.ItemsSource == null) return;
ResetAdapter(element);
}
private void SetThreshold()
{
var element = (AutoCompleteViewv2)Element;
NativeControl.Threshold = element.Threshold;
}
#region Section 2
protected AColor GetPlaceholderColor() => Element.PlaceholderColor.ToAndroid(Color.FromHex("#80000000"));
#endregion
}
internal class BoxArrayAdapter : ArrayAdapter
{
private readonly IList<string> _objects;
private readonly Func<string, ICollection<string>, ICollection<string>> _sortingAlgorithm;
public BoxArrayAdapter(
Context context,
int textViewResourceId,
List<string> objects,
Func<string, ICollection<string>, ICollection<string>> sortingAlgorithm) : base(context, textViewResourceId, objects)
{
_objects = objects;
_sortingAlgorithm = sortingAlgorithm;
}
public override Filter Filter
{
get
{
return new CustomFilter(_sortingAlgorithm) { Adapter = this, Originals = _objects };
}
}
}
internal class CustomFilter : Filter
{
private readonly Func<string, ICollection<string>, ICollection<string>> _sortingAlgorithm;
public CustomFilter(Func<string, ICollection<string>, ICollection<string>> sortingAlgorithm)
{
_sortingAlgorithm = sortingAlgorithm;
}
public BoxArrayAdapter Adapter { private get; set; }
public IList<string> Originals { get; set; }
protected override FilterResults PerformFiltering(ICharSequence constraint)
{
var results = new FilterResults();
if (constraint == null || constraint.Length() == 0)
{
results.Values = new Java.Util.ArrayList(Originals.ToList());
results.Count = Originals.Count;
}
else
{
var values = new Java.Util.ArrayList();
var sorted = _sortingAlgorithm(constraint.ToString(), Originals).ToList();
for (var index = 0; index < sorted.Count; index++)
{
var item = sorted[index];
values.Add(item);
}
results.Values = values;
results.Count = sorted.Count;
}
return results;
}
protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
if (results.Count == 0)
Adapter.NotifyDataSetInvalidated();
else
{
Adapter.Clear();
var vals = (Java.Util.ArrayList)results.Values;
foreach (var val in vals.ToArray())
{
Adapter.Add(val);
}
Adapter.NotifyDataSetChanged();
}
}
}
}
how can i get rid of that tooltip or placeholder and make the text in middle

How to change custom entry floating label color on button click in Xamarin.Forms

i have created one custom entry field with floating label using custom renderer like from this uri https://github.com/AlejandroRuiz/FloatingTextEntry
i need to change entry underline color as well placeholder color on button click .
Custom Entry Code :
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Xamarin.Forms;
namespace BloodTrace.CustomControls
{
public class XfxEntry : Entry
{
public static readonly BindableProperty ErrorTextProperty = BindableProperty.Create(nameof(ErrorText),
typeof(string),
typeof(XfxEntry),
default(string), propertyChanged: OnErrorTextChangedInternal);
public static readonly BindableProperty FloatingHintEnabledProperty = BindableProperty.Create(nameof(FloatingHintEnabled),
typeof(bool),
typeof(XfxEntry),
true);
public static readonly BindableProperty ActivePlaceholderColorProperty = BindableProperty.Create(nameof(ActivePlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Accent);
/// <summary>
/// ActivePlaceholderColor summary. This is a bindable property.
/// </summary>
public Color ActivePlaceholderColor
{
get { return (Color)GetValue(ActivePlaceholderColorProperty); }
set { SetValue(ActivePlaceholderColorProperty, value); }
}
public Color ErrorPlaceholderColor
{
get { return (Color)GetValue(ErrorPlaceholderColorProperty); }
set { SetValue(ErrorPlaceholderColorProperty, value); }
}
public static readonly BindableProperty ErrorPlaceholderColorProperty = BindableProperty.Create(nameof(ErrorPlaceholderColor),
typeof(Color),
typeof(XfxEntry),
Color.Green);
/// <summary>
/// <c>true</c> to float the hint into a label, otherwise <c>false</c>. This is a bindable property.
/// </summary>
public bool FloatingHintEnabled
{
get { return (bool)GetValue(FloatingHintEnabledProperty); }
set { SetValue(FloatingHintEnabledProperty, value); }
}
/// <summary>
/// Gets or Sets whether or not the Error Style is 'Underline' or 'None'
/// </summary>
public ErrorDisplay ErrorDisplay { get; set; } = ErrorDisplay.None;
public bool IsErrorPlaceholderColorChange { get; set; } = false;
/// <summary>
/// Error text for the entry. An empty string removes the error. This is a bindable property.
/// </summary>
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set { SetValue(ErrorTextProperty, value); }
}
/// <summary>
/// Raised when the value of the error text changes
/// </summary>
public event EventHandler<TextChangedEventArgs> ErrorTextChanged;
private static void OnErrorTextChangedInternal(BindableObject bindable, object oldvalue, object newvalue)
{
var materialEntry = (XfxEntry)bindable;
materialEntry.OnErrorTextChanged(bindable, oldvalue, newvalue);
materialEntry.ErrorTextChanged?.Invoke(materialEntry, new TextChangedEventArgs((string)oldvalue, (string)newvalue));
}
protected virtual void OnErrorTextChanged(BindableObject bindable, object oldvalue, object newvalue) { }
}
public enum ErrorDisplay
{
Underline,
None
}
}
Android Renderer : -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BloodTrace;
using BloodTrace.Droid.Renderer;
using Application = Android.App.Application;
using Color = Xamarin.Forms.Color;
using AColor = Android.Graphics.Color;
using FormsAppCompat = Xamarin.Forms.Platform.Android.AppCompat;
using BloodTrace.CustomControls;
using Android.Support.Design.Widget;
using Android.Text;
using Xamarin.Forms;
using Android.Content.Res;
using Android.Views.InputMethods;
using Java.Lang;
using Android.Support.V7.Widget;
using Android.Util;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Text.Method;
using Android.Support.V4.View;
using BloodTrace.Droid.Extensions;
using BloodTraceSharedProject.Extensions;
[assembly: ExportRenderer(typeof(XfxEntry), typeof(XfxEntryRendererDroid))]
namespace BloodTrace.Droid.Renderer
{
public class XfxEntryRendererDroid : FormsAppCompat.ViewRenderer<XfxEntry, TextInputLayout>,
ITextWatcher,
TextView.IOnEditorActionListener
{
private bool _hasFocus;
private ColorStateList _defaultTextColor;
public XfxEntryRendererDroid(Context context) : base(context)
{
AutoPackage = false;
}
protected EditText EditText => Control.EditText;
public bool OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
if ((actionId == ImeAction.Done) || ((actionId == ImeAction.ImeNull) && (e.KeyCode == Keycode.Enter)))
{
Control.ClearFocus();
HideKeyboard();
((IEntryController)Element).SendCompleted();
}
return true;
}
public virtual void AfterTextChanged(IEditable s)
{
}
public virtual void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public virtual void OnTextChanged(ICharSequence s, int start, int before, int count)
{
if (string.IsNullOrWhiteSpace(Element.Text) && (s.Length() == 0)) return;
((IElementController)Element).SetValueFromRenderer(Entry.TextProperty, s.ToString());
}
protected override TextInputLayout CreateNativeControl()
{
var textInputLayout = new TextInputLayout(Context);
var editText = new AppCompatEditText(Context)
{
SupportBackgroundTintList = ColorStateList.ValueOf(GetPlaceholderColor())
};
editText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
textInputLayout.AddView(editText);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<XfxEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
if (Control != null)
EditText.FocusChange -= ControlOnFocusChange;
if (e.NewElement != null)
{
var ctrl = CreateNativeControl();
SetNativeControl(ctrl);
if (!string.IsNullOrWhiteSpace(Element.AutomationId))
EditText.ContentDescription = Element.AutomationId;
_defaultTextColor = EditText.TextColors;
Focusable = true;
EditText.ShowSoftInputOnFocus = true;
// Subscribe
EditText.FocusChange += ControlOnFocusChange;
EditText.AddTextChangedListener(this);
EditText.SetOnEditorActionListener(this);
EditText.ImeOptions = ImeAction.Done;
SetText();
SetHintText();
SetErrorText();
SetFontAttributesSizeAndFamily();
SetInputType();
SetTextColor();
SetHorizontalTextAlignment();
SetFloatingHintEnabled();
SetIsEnabled();
SetErrorDisplay();
SetLabelAndUnderlineColor();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.PlaceholderProperty.PropertyName)
SetHintText();
else if (e.PropertyName == XfxEntry.ErrorTextProperty.PropertyName)
SetErrorText();
else if (e.PropertyName == Entry.IsPasswordProperty.PropertyName ||
e.PropertyName == InputView.KeyboardProperty.PropertyName)
SetInputType();
else if (e.PropertyName == Entry.TextProperty.PropertyName)
SetText();
else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
SetHorizontalTextAlignment();
else if (e.PropertyName == XfxEntry.FloatingHintEnabledProperty.PropertyName)
SetFloatingHintEnabled();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
SetIsEnabled();
else if ((e.PropertyName == Entry.FontAttributesProperty.PropertyName) ||
(e.PropertyName == Entry.FontFamilyProperty.PropertyName) ||
(e.PropertyName == Entry.FontSizeProperty.PropertyName))
SetFontAttributesSizeAndFamily();
else if (e.PropertyName == XfxEntry.ActivePlaceholderColorProperty.PropertyName ||
e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
SetLabelAndUnderlineColor();
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
SetTextColor();
}
private void ControlOnFocusChange(object sender, FocusChangeEventArgs args)
{
_hasFocus = args.HasFocus;
if (_hasFocus)
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
EditText.PostDelayed(() =>
{
EditText.RequestFocus();
manager.ShowSoftInput(EditText, 0);
},
100);
}
var isFocusedPropertyKey = Element.GetInternalField<BindablePropertyKey>("IsFocusedPropertyKey");
((IElementController)Element).SetValueFromRenderer(isFocusedPropertyKey, _hasFocus);
//This will change floating label entry SetUnderlineColor >>>> SetUnderlineColor color basedon focus or non focus
SetUnderlineColor(_hasFocus ? GetActivePlaceholderColor() : GetPlaceholderColor());
}
protected AColor GetPlaceholderColor() => Element.PlaceholderColor.ToAndroid(Color.FromHex("#80000000"));
private AColor GetActivePlaceholderColor() => Element.ActivePlaceholderColor.ToAndroid(global::Android.Resource.Attribute.ColorAccent, Context);
protected virtual void SetLabelAndUnderlineColor()
{
var defaultColor = GetPlaceholderColor();
var activeColor = GetActivePlaceholderColor();
SetHintLabelDefaultColor(defaultColor);
SetHintLabelActiveColor(activeColor);
SetUnderlineColor(_hasFocus ? activeColor : defaultColor);
}
//This will change entry under line color
private void SetUnderlineColor(AColor color)
{
var element = (ITintableBackgroundView)EditText;
element.SupportBackgroundTintList = ColorStateList.ValueOf(color);
}
private void SetHintLabelActiveColor(AColor color)
{
var hintText = Control.Class.GetDeclaredField("mFocusedTextColor");
hintText.Accessible = true;
hintText.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetHintLabelDefaultColor(AColor color)
{
var hint = Control.Class.GetDeclaredField("mDefaultTextColor");
hint.Accessible = true;
hint.Set(Control, new ColorStateList(new int[][] { new[] { 0 } }, new int[] { color }));
}
private void SetText()
{
if (EditText.Text != Element.Text)
{
EditText.Text = Element.Text;
if (EditText.IsFocused)
EditText.SetSelection(EditText.Text.Length);
}
}
private void SetHintText()
{
Control.Hint = Element.Placeholder;
}
private void SetTextColor()
{
if (Element.TextColor == Color.Default)
{
EditText.SetTextColor(_defaultTextColor);
}
else
{
EditText.SetTextColor(Element.TextColor.ToAndroid());
}
}
private void SetHorizontalTextAlignment()
{
switch (Element.HorizontalTextAlignment)
{
case Xamarin.Forms.TextAlignment.Center:
EditText.Gravity = GravityFlags.CenterHorizontal;
break;
case Xamarin.Forms.TextAlignment.End:
EditText.Gravity = GravityFlags.Right;
break;
default:
EditText.Gravity = GravityFlags.Left;
break;
}
}
public void SetFloatingHintEnabled()
{
Control.HintEnabled = Element.FloatingHintEnabled;
Control.HintAnimationEnabled = Element.FloatingHintEnabled;
SetFontAttributesSizeAndFamily();
}
public void SetErrorDisplay()
{
switch (Element.ErrorDisplay)
{
case ErrorDisplay.None:
Control.ErrorEnabled = false;
break;
case ErrorDisplay.Underline:
Control.ErrorEnabled = true;
break;
}
}
protected void HideKeyboard()
{
var manager = (InputMethodManager)Application.Context.GetSystemService(Context.InputMethodService);
manager.HideSoftInputFromWindow(EditText.WindowToken, 0);
}
//here we can set entry floating label >>> input text size
private void SetFontAttributesSizeAndFamily()
{
EditText.Typeface = Control.Typeface = Element.ToTypeface();
EditText.SetTextSize(ComplexUnitType.Sp, (float)Element.FontSize);
//var a = (float)Element.FontSize; //18
}
private void SetErrorText()
{
Control.Error = !string.IsNullOrEmpty(Element.ErrorText) ? Element.ErrorText : null;
}
private void SetIsEnabled()
{
EditText.Enabled = Element.IsEnabled;
}
private void SetInputType()
{
EditText.InputType = Element.Keyboard.ToInputType();
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.TextVariationPassword;
}
if (Element.IsPassword && (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber)
{
EditText.TransformationMethod = new PasswordTransformationMethod();
EditText.InputType = EditText.InputType | InputTypes.NumberVariationPassword;
}
}
}
}
XML : -
<xfx:XfxEntry x:Name="EntPassword" Placeholder="Password" IsPassword="True" Focused="EntPassword_Focused"></xfx:XfxEntry>
<Button Text="Sign Up" BackgroundColor="#C62729" TextColor="White" x:Name="BtnSignUp" Clicked="BtnSignUp_Clicked" ></Button>
Please help.
Thanks in advance

Create an ICommand Bindable property on Xamarin Forms

I have a custom checkbox control that I created with an ICommand property and the corresponding bindable property (my checkbox is a Xamarin.Forms XAML Page), the code is:
CheckBox.xaml
<Image x:Name="imgCheckBox"
WidthRequest="20"
HeightRequest="20"/>
<Label x:Name="lblCheckBox"
TextColor="Black"
VerticalOptions="CenterAndExpand"/>
<TapGestureRecognizer Tapped="OnCheckBoxTapped"/>
CheckBox.xaml.cs
public partial class CheckBox : ContentView
{
private static ImageSource uncheckedImage;
private static ImageSource checkedImage;
public CheckBox()
{
InitializeComponent();
uncheckedImage = ImageSource.FromResource("cbUnchecked.png");
checkedImage = ImageSource.FromResource("cbChecked.png");
imgCheckBox.Source = uncheckedImage;
}
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create<CheckBox, bool>(
checkbox =>
checkbox.IsChecked,
false,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, newValue);
}
});
public bool IsChecked
{
set { SetValue(IsCheckedProperty, value); }
get { return (bool)GetValue(IsCheckedProperty); }
}
void OnCheckBoxTapped(object sender, EventArgs args)
{
IsChecked = !IsChecked;
if (IsChecked)
{
imgCheckBox.Source = checkedImage;
}
else
{
imgCheckBox.Source = uncheckedImage;
}
}
public static readonly BindableProperty CheckBoxCommandProperty =
BindableProperty.Create<CheckBox, ICommand>(
checkbox =>
checkbox.CheckBoxCommand,
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, checkbox.IsChecked);
}
});
public event EventHandler<bool> CheckedChanged;
public ICommand CheckBoxCommand
{
get { return (ICommand)GetValue(CheckBoxCommandProperty); }
set { SetValue(CheckBoxCommandProperty, value); }
}
}
This checkbox implementation is on another Page called TermsAndConditionsPage, that is also a a Xamarin.Forms XAML Page, the code of the implementation is:
<toolkit:CheckBox Text="{Binding txtCheckBox}"
FontSize="Small"
CheckBoxCommand="{Binding OnCheckBoxTapChanged}"
IsChecked="{Binding IsCheckedChanged, Mode=TwoWay}"/>
<Button Text="Next"
Command="{Binding Next_OnClick}"
IsEnabled="{Binding Next_IsEnabled}"
HorizontalOptions="CenterAndExpand"
Clicked="OnNextClicked"/>
The Code Behind of this page is empty (Constructur with InitializeComponent()).
I also have the ViewModel of this page with this code:
TermsAndConditionsViewModel.cs
private string _txtCheckBox;
public string txtCheckBox
{ get { return _txtCheckBox; }
set
{
_txtCheckBox = value;
OnPropertyChanged("txtCheckBox");
}
}
private bool _Next_IsEnabled;
public bool Next_IsEnabled
{
get { return _Next_IsEnabled; }
set
{
_Next_IsEnabled = value;
OnPropertyChanged("Next_IsEnabled");
}
}
private bool _IsCheckedChanged;
public bool IsCheckedChanged
{
get { return _IsCheckedChanged; }
set
{
_IsCheckedChanged = value;
OnPropertyChanged("IsCheckedChanged");
}
}
public ICommand Next_OnClick { get; set; }
public ICommand OnCheckBoxTapChanged { get; set; }
public TermsAndConditionsViewModel()
{
txtCheckBox = "I agree with the terms and conditions";
Next_OnClick = new Command(NextClicked);
OnCheckBoxTapChanged = new Command(CheckBoxTapped);
}
private void CheckBoxTapped()
{
if (IsCheckedChanged)
{ Next_IsEnabled = true; }
else
{ Next_IsEnabled = false; }
}
private void NextClicked()
{ App.Current.MainPage = new Views.HelloWorld(); }
#region INPC
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
Now, the question time: the problem I'm having is the CheckBoxTapped Command is not working, I mean, it doesn't do anything, although the checkbox image changes every time I touch it, it does not change the Next_IsEnabled property of my button. I'd like to know what I am missing here to make this command work properly.
EDIT
What I'm looking for is a Command that behaves similarly to the one that Buttons have.
Thanks all for your time!
Since the original answer is now obsolete, here is the new method:
using System.Windows.Input;
public partial class MyControlExample : ContentView
{
// BindableProperty implementation
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(MyControlExample), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Helper method for invoking commands safely
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
public MyControlExample()
{
InitializeComponent();
}
// this is the command that gets bound by the control in the view
// (ie. a Button, TapRecognizer, or MR.Gestures)
public Command OnTap => new Command(() => Execute(Command));
}
Something like that (pseudocode):
public class YourClassName : View
{
public YourClassName()
{
var gestureRecognizer = new TapGestureRecognizer();
gestureRecognizer.Tapped += (s, e) => {
if (Command != null && Command.CanExecute(null)) {
Command.Execute(null);
}
};
var label = new Label();
label.GestureRecognizers.Add(gestureRecognizer);
}
public static readonly BindableProperty CommandProperty =
BindableProperty.Create<YourClassName, ICommand>(x => x.Command, null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}

Resources