xamarin.forms entry binding on blur - xamarin.forms

It appears the default binding trigger for an entry text is the TextChanged event. I want to defer updating the source until the blur event. In WPF there was the UpdateSourceTrigger parameter that could be set to modify the binding trigger, but there isn’t any documentation I’ve found on this in Xamarin.Form.
How can that be achieved in Xaramin.Forms with binding in XAML. For obvious reasons, I don’t want to manually handle it in the code behind.

This question is old but still actual. Especially for fields with decimal point...
First option to use Xamarin Community Toolkit EventToCommandBehavior (or similar implementations, like Prism). Then possible to bind "Unfocused" event to some command:
<Entry.Behaviors>
<xct:EventToCommandBehavior
EventName="Unfocused"
Command="{Binding MyCustomCommand}" />
</Entry.Behaviors>
Other option to add custom Entry component that handle this event, this option fits me better with numeric input with decimal point (float, double, decimal). This a little modified solution from MSDN forum Entry binding Decimal:
public class NumericEntry : Entry
{
#region Bindables
public static readonly BindableProperty NumericValueProperty = BindableProperty.Create(
"NumericValue",
typeof(decimal?),
typeof(NumericEntry),
null,
BindingMode.TwoWay,
coerceValue: (_, value) => (decimal?)value,
propertyChanged: (bindable, _, __) => SetDisplayFormat((NumericEntry)bindable)
);
public static readonly BindableProperty NumericValueFormatProperty = BindableProperty.Create(
"NumericValueFormat",
typeof(string),
typeof(NumericEntry),
"N0",
BindingMode.TwoWay,
propertyChanged: (bindable, _, __) => SetDisplayFormat((NumericEntry)bindable)
);
#endregion Bindables
#region Constructor
public NumericEntry()
{
Keyboard = Keyboard.Numeric;
Focused += OnFocused;
Unfocused += OnUnfocused;
}
#endregion Constructor
#region Events
private void OnFocused(object sender, FocusEventArgs e)
{
SetEditFormat(this);
}
private void OnUnfocused(object sender, FocusEventArgs e)
{
var numberFormant = CultureInfo.CurrentCulture.NumberFormat;
var _text = Text.Replace(".", numberFormant.NumberDecimalSeparator);
if (decimal.TryParse(_text, NumberStyles.Number, CultureInfo.CurrentCulture, out var numericValue))
{
var round = Convert.ToInt32(NumericValueFormat.Substring(1));
NumericValue = Math.Round(numericValue, round);
}
else
{
NumericValue = null;
}
SetDisplayFormat(this);
}
#endregion Events
#region Properties
public decimal? NumericValue
{
get => (decimal?)GetValue(NumericValueProperty);
set => SetValue(NumericValueProperty, value);
}
public string NumericValueFormat
{
get => (string)GetValue(NumericValueFormatProperty) ?? "N0";
set
{
var _value = string.IsNullOrWhiteSpace(value) ? "N0" : value;
SetValue(NumericValueFormatProperty, _value);
}
}
#endregion Properties
#region Methods
private static void SetDisplayFormat(NumericEntry textBox)
{
if (textBox.NumericValue.HasValue)
{
textBox.Text = textBox.NumericValue.Value.ToString(textBox.NumericValueFormat, CultureInfo.DefaultThreadCurrentCulture);
}
else
{
textBox.Text = string.Empty;
}
}
private static void SetEditFormat(NumericEntry textBox)
{
if (textBox.NumericValue.HasValue)
{
var numberFormant = CultureInfo.CurrentCulture.NumberFormat;
textBox.Text = textBox.NumericValue.Value.ToString(textBox.NumericValueFormat, CultureInfo.CurrentCulture).Replace(numberFormant.NumberGroupSeparator, string.Empty);
}
else
{
textBox.Text = string.Empty;
}
}
#endregion Methods
}
And use it like this:
// import our component
xmlns:ex="clr-namespace:BoganPos.Extensions"
//...
<ex:NumericEntry NumericValue="{Binding DecimalValue}" NumericValueFormat="F2" Placeholder="Placeholder"/>

Related

Is there a way to update child elements in custom renderer

I was creating a custom control it is more like TabPage, where it's derive from View, containing a list of CSMenuItems and foreach menuItem is derived from BaseMenuItem and has menuContent that is derived from ContentView, like this :
• CSView
• CSMenuItem
• MenuContent
• Content
• CSMenuItem
• MenuContent
• Content
My problem is whenever I Change the properties value of MenuContent in the xaml file, the propertyChanged won't fire and MenuContent won't update. I am pretty sure the problem is in my renderers. Is there any way to update the child element in custom renderer?
Here are my codes for controls:
class CSView : View
{
public CSView ()
{
var items = new ObservableCollection <CSMenuItem>();
items.CollectionChanged += OnItemsChanged;
Items = items;
}
public static readonly BindableProperty ItemsProperty = BindableProperty.Create ("Items", typeof (IList <CSMenuItem>), typeof (CSView));
void OnItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
{
foreach (CSMenuItem item in e.NewItems)
item.Parent = this;
// Maybe setting the item parent to this would be good?
// cause whenever new item is add I want its parent to be this.
// Correct me if im wrong.
}
public IList <CSMenuItem> Items
{
get => (IList <CSMenuItem>)GetValue (ItemsProperty);
set => SetValue (ItemsProperty, value);
}
class CSMenuItem : BaseMenuItem
{
public CSMenuItem()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(CSMenuItem), default);
public static readonly BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(MenuContent), typeof(CSMenuItem));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
public MenuContent Content
{
get => (MenuContent)GetValue (ContentProperty);
set => SetValue (ContentProperty, value);
}
}
class MenuContent : ContentView
{
public MenuContent ()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create ("Title", typeof (string), typeof (MenuContent));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
}
and for renderers :
class CSViewRenderer : ViewRenderer<CSView, FrameLayout>, NavigationBarView.IOnItemSelectedListener
{
...
protected override void OnElementChanged (ElementChangedEventArgs <CSView> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (_control);
HandleEvents (e.NewElement)
}
}
void HandleEvents (CSView element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSView element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
/// this is where the issue came from
class CSMenuItemRender : AbsItemRenderer
{
...
protected override View CreateNativeControl ()
{
_base = new LinearLayout (Context);
_toolBar = new ToolBar (Context)
_content = Platform.CreateRendererWithContext (Element.Content, Context); // where Element is type of MenuItem
_toolBar.Title = Element.Content.Title;
_base.Orientation = Orientation.Vertical;
_base.AddView (_toolBar, new LinearLayout.LayoutParams (-1, -2);
_base.AddView (_content.View, new LinearLayout.LayoutParams (-1, -2);
ElementRendererUtil.FitElement (Context, _base, _content);
Platform.SetRenderer (Element.Content, _content);
_content.ElementChanged += OnRendererElementChanged;
return _base;
}
void OnElementChanged (ElementChangedEventArgs <CSMenuItem> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (this)
HandleEvents (e.NewElement);
}
}
void OnRendererElementChanged (object sender, VisualElementChangedEventArgs e)
{
if (e.OldElement != null)
e.OldElement.PropertyChanged -= OnRendererElementPropertyChanged;
if (e.NewElement != null)
e.NewElement.PropertyChanged += OnRendererElementPropertyChanged;
}
void OnRendererElementPropertyChanged (object sender, PropertyChangedEventArgs e)
{
var menuContent = (MenuContent)sender;
if (e.PropertyName == MenuContent.TitleProperty.PropertyName)
_toolBar.Title = menuContent.Title;
else if (e.PropertyName == MenuContent.ContentProperty.PropertyName)
_content.Tracker.UpdateLayout ();
}
void HandleEvents (CSMenuItem element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSMenuItem element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
I was able to change the MenuContent properties like Title when changing the derive type to Element, like this:
class MenuContent : Element
And setting its parent to CSMenuItem, like this :
public MenuContent Content
{
...
set {
if (value.Parent != this)
value.Parent = this;
SetValue (ContentProperty, value);
}
}
but still the Content property of MenuContent won't update when the value is changed. What I want is to update the MenuContent what ever the value changed in xaml file and to know why the propertyChanged won't fire.
Sorry for my bad English, hope you understand. May the Almighty Bless You😇 Stay safe.

Notify Activity of changes in viewModel

I try to exit 'lock task mode' in Xamarin Android app. Here is what I am trying to achieve:
User taps on label (view in Xamarin.Forms) -> it cause change in ViewModel's boolean property to true
MainActivity (Xamarin.Android) observe that property has changed to true -> it makes application exit 'lock task mode'
My viewModel is placed in Xamarin.Forms 'App.xaml' class so it is accessible in Forms and Android part.
How Can I notify my Activity that property has changed so it can exit locked mode? I know this is propably very poor workaround, I would love to hear any advices and tips to make it more professional.
Thank you in advance!
EDIT
So the point is that I have got ViewModel with boolean property exitLockMode which indicates if app should be in lock mode or not:
public class AdminViewModel : BaseViewModel
{
//Number of taps to touch at main banner in 'MainPage' to open Admin Window
private int _tapsRequiredToAdmin = 5;
//Number of tolerance in miliseconds between next taps
private int _toleranceInMs = 1000;
private bool _exitLockMode = false;
public int ToleranceInMs { get => _toleranceInMs; }
public int TapsRequiredToAdmin { get => _tapsRequiredToAdmin; }
public bool ExitLockMode
{
get => _exitLockMode;
set => _exitLockMode=value;
}
}
AdminViewModel is created in 'App.xaml' class:
public partial class App : Application
{
private static AdminViewModel _adminViewModel;
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
public static AdminViewModel AdminViewModel
{
get
{
if(_adminViewModel == null )
_adminViewModel = new AdminViewModel();
return _adminViewModel;
}
}
protected override void OnStart() { }
protected override void OnSleep() { }
protected override void OnResume() { }
}
In my main view (Xamarin.Forms) I have got label where admin want to tap few times in order to exit lock mode:
private DateTime? LastTap = null;
private byte NumberOfTaps = 0;
AdminViewModel adminViewModel = App.AdminViewModel;
**********************************************
//This is method binded to Label in <TapGestureRecognizer Tapped="OnLabelTapped">
private async void OnLabelTapped(object sender, EventArgs e)
{
if (LastTap == null || (DateTime.Now - LastTap.Value).TotalMilliseconds < adminViewModel.ToleranceInMs)
{
if (NumberOfTaps == (adminViewModel.TapsRequiredToAdmin - 1))
{
NumberOfTaps = 0;
LastTap = null;
adminViewModel.ExitLockMode = true;
return;
}
else
{
NumberOfTaps++;
LastTap = DateTime.Now;
}
}
else
{
NumberOfTaps = 1;
LastTap = DateTime.Now;
}
}
Now I want to achieve that when I turn 'ExitLockMode' bool to true, it notify my 'MainActivity' (Xamarin.Android) to fire 'StopLockTask()' method. I know that in native Android it could be handled by observing bool property, but I don't know how to do it here.
I am newbie so it could be very messy, every help appreciated.
As Jason said, you can use messagecenter.The Xamarin.Forms MessagingCenter class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references.
This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between them.
You can follow this document and the sample in it https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center

C#, Xamarin Forms: No Custom TextChangedEvent Raised on initialization

I'm creating an Xamarin.Forms MVVM App (only using Android) which needs certain buttons to be outlined red, whenever their text property holds a specific value. (Purpose: alert the user to press the button and select a value, which will change the Button Text Property and therefore remove the red outline)
To achieve this I've create the following documents:
A custom button CButton that extents the default Button:
public class CButton : Button
{
// this Hides the Default .Text-Property
public string Text
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
// The Raised Event
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
A custom behavior makes use of the raised TextChangedEvent
public class ButtonValBehavior : Behavior<CButton>
{
protected override void OnAttachedTo(CButton bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, EventArgs e)
{
string forbidden = "hh:mm|dd.mm.yyyy";
if (forbidden.Contains((sender as CButton).Text.ToLower()))
{
//Do when Button Text = "hh:mm" || "dd.mm.yyyy"
(sender as CButton).BorderColor = Color.Gray;
}
else
{
//Do whenever Button.Text is any other value
(sender as CButton).BorderColor = Color.FromHex("#d10f32");
}
}
protected override void OnDetachingFrom(CButton bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
The relevant parts of the ViewModel look the following:
public class VM_DIVI : VM_Base
{
public VM_DIVI(O_BasisProtokoll base)
{
Base = base;
}
private O_BasisProtokoll _base = null;
public O_BasisProtokoll Base
{
get => _base;
set
{
_base = value;
OnPropertyChanged();
}
}
Command _datePopCommand;
public Command DatePopCommand
{
get
{
return _datePopCommand ?? (_datePopCommand = new Command(param => ExecuteDatePopCommand(param)));
}
}
void ExecuteDatePopCommand(object param)
{
//launch popup
var p = new PP_DatePicker(param);
PopupNavigation.Instance.PushAsync(p);
}
}
The .xmal looks the following (b is the xmlns of the Namespace):
<b:CButton x:Name="BTN_ED_Datum"
Text="{Binding Base.ED_datum, Mode=TwoWay}"
Grid.Column="1"
Command="{Binding DatePopCommand}"
CommandParameter="{x:Reference BTN_ED_Datum}">
<b:CButton.Behaviors>
<b:ButtonValBehavior/>
</b:CButton.Behaviors>
</b:CButton>
This solution works fine whenever the input is caused by user interaction. However, when a Value is assigned during the initialization of the Page no red outline is created, in fact the TextChangedEvent isn't raised. By using breakpoints I noticed that during initialization the Text Property of CButton is never set, eventhough it actually will be in the view.
Despite fiddling around with my solution I cannot make this work on initialization. I tried to work around this issue by outlining every button by default in their constructor, however this will outline every button red, even when their text value doesn't require them to be.
How can I achieve my initial goal?
Many thanks in advance!
It's been a while but if I recall correctly what I ended up doing was:
Changing the new Text-Property of my custom Button to CText and
Making sure that I have Mode=TwoWay activated for any Element, that doesn't have it enabled by default. (Look up Binding modes on msdn for more)
making CText a bindable property of CButton
My custom button now looks the following:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace EORG_Anton.Model
{
public class CButton : Button
{
public static readonly BindableProperty CTextProperty =
BindableProperty.Create(nameof(CText),
typeof(string),
typeof(CButton),
default(string),
BindingMode.TwoWay,
propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CButton)bindable;
var value = (string)newValue;
control.CText = value;
}
public string CText
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
}

Unable To Retrieve Custom Control Value from View

Xamarin Forms Android Autosize Label TextCompat pre android 8 doesn't autosize text
I unfortunately do not have a high enough rep to comment on anyones post.
I was trying some things out and came across the post linked which got me very close to the solution after experimenting with other posts. I am also trying to autosize text within an app, but inside of an MVVM Master Detail project. If I enter values directly in the Droid renderer it works as expected, but that defeats the purpose when I have fonts of all sizes needed.
I have already made sure my return type is correct.
The code behind is initialized prior to the get value.
The fields are public.
There are no other issues by plugging in numeric values instead of bindable properties.
I am not receiving any values from the view. I would assume the view has not been created yet but the code behind has initialized. I am pretty sure I have done everything mostly right but I mostly deal with stock Xamarin so expanding functionality is still pretty new to me. All help is appreciated.
Custom Control (edit: changed default value from default(int) to an integer value to get rid of exception)
/// <summary>Auto scale label font size class.</summary>
public class AutoSizeLabel : Label
{
/// <summary>Minimum font size property.</summary>
public static readonly BindableProperty MinimumFontSizeProperty = BindableProperty.Create(
propertyName: nameof(MinimumFontSize),
returnType: typeof(int),
declaringType: typeof(AutoSizeLabel),
defaultValue: 17);
/// <summary>Maximum font size property.</summary>
public static readonly BindableProperty MaximumFontSizeProperty = BindableProperty.Create(
propertyName: nameof(MaximumFontSize),
returnType: typeof(int),
declaringType: typeof(AutoSizeLabel),
defaultValue: 24);
/// <summary>Gets or sets minimum font size.</summary>
public int MinimumFontSize
{
get
{
return (int)this.GetValue(MinimumFontSizeProperty);
}
set
{
this.SetValue(MinimumFontSizeProperty, value);
}
}
/// <summary>Gets or sets maximum font size.</summary>
public int MaximumFontSize
{
get
{
return (int)this.GetValue(MaximumFontSizeProperty);
}
set
{
this.SetValue(MaximumFontSizeProperty, value);
}
}
}
Droid Renderer
public class AutoSizeLabelRenderer : LabelRenderer
{
protected override bool ManageNativeControlLifetime => false;
protected override void Dispose(bool disposing)
{
Control.RemoveFromParent();
base.Dispose(disposing);
}
private AutoSizeLabel bindingValue = new AutoSizeLabel();
private AppCompatTextView appCompatTextView;
public AutoSizeLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
//v8 and above supported natively, no need for the extra stuff below.
if (DeviceInfo.Version.Major >= 8)
{
Control?.SetAutoSizeTextTypeUniformWithConfiguration(bindingValue.MinimumFontSize, bindingValue.MaximumFontSize, 2, (int)ComplexUnitType.Sp);
return;
}
appCompatTextView = new AppCompatTextView(Context);
appCompatTextView.SetTextColor(Element.TextColor.ToAndroid());
appCompatTextView.SetMaxLines(1);
appCompatTextView.SetBindingContext(autoLabel.BindingContext);SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, bindingValue.MinimumFontSize, bindingValue.MaximumFontSize, 2, (int)ComplexUnitType.Sp);
}
}
XAML Call
<renderer:AutoSizeLabel MinimumFontSize="17"
MaximumFontSize="24"
Style="{StaticResource SomeStyle}"
Text="{Binding SomeText}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SomeCommand}"></TapGestureRecognizer>
</Label.GestureRecognizers>
</renderer:AutoSizeLabel>
This line is unnecessary.
private AutoSizeLabel bindingValue = new AutoSizeLabel();
Instead reference autoLabel. Alternatively I changed the check to
if (e.NewElement == null || Control == null) { return; }
and cast in the following line using
var autoSizeLabel = e.NewElement as AutoSizeLabel;

Why does setting BindableProperty.DefaultValue not call OnPropertyChanged?

I'm using a bindable property like this in a class the inherits from Xamarin.Forms.ContentView:
public static readonly BindableProperty OverlayColorProperty = BindableProperty.Create(nameof(OverlayColor), typeof(Color), typeof(MyControl), Color.FromHex("#55000000"));
public Color OverlayColor
{
get => (Color)GetValue(OverlayColorProperty);
set => SetValue(OverlayColorProperty, value);
}
Furthermore I'm listening for changes to update an inner elements background color:
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(OverlayColor):
GridBackground.BackgroundColor = OverlayColor;
break;
}
}
I just noticed that OnPropertyChanged does not get called with the default value. Just when I update it from another place, XAML or through code.
Is this exspected behavior?
If yes, why? What should I do instead? Define it also in XAML code?

Resources