MvvmCross Custom Event Binding Event Args - c#

I have created a custom binding for the FocusChange event on an EditText using MvvmCross. I can get the event bound and firing, but I can't figure out how to pass the event args. My Custom Binding is this
using Android.Views;
using Android.Widget;
using Cirrious.MvvmCross.Binding;
using Cirrious.MvvmCross.Binding.Droid.Target;
using Cirrious.MvvmCross.Binding.Droid.Views;
using Cirrious.MvvmCross.ViewModels;
using System;
namespace MPS_Mobile_Driver.Droid.Bindings
{
public class MvxEditTextFocusChangeBinding
: MvxAndroidTargetBinding
{
private readonly EditText _editText;
private IMvxCommand _command;
public MvxEditTextFocusChangeBinding(EditText editText) : base(editText)
{
_editText = editText;
_editText.FocusChange += editTextOnFocusChange;
}
private void editTextOnFocusChange(object sender, EditText.FocusChangeEventArgs eventArgs)
{
if (_command != null)
{
_command.Execute( eventArgs );
}
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_editText.FocusChange -= editTextOnFocusChange;
}
base.Dispose(isDisposing);
}
public override Type TargetType
{
get { return typeof(IMvxCommand); }
}
protected override void SetValueImpl(object target, object value)
{
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
}
I wire it up in my ViewModel as this:
public IMvxCommand FocusChange
{
get
{
return new MvxCommand(() =>
OnFocusChange()
);
}
}
private void OnFocusChange()
{
//Do Something
}
Is there a way to do something like
public IMvxCommand FocusChange
{
get
{
return new MvxCommand((e) =>
OnFocusChange(e)
);
}
}
private void OnFocusChange(EditText.FocusChangeEventArgs e)
{
//Do Something
}
What I tried to do there doesn't work, but I was hoping that there was something similar that might work. I am able to pass the eventargs when the command fires in the custom binding with this line
_command.Execute( eventArgs );
I just can't figure a way to catch them in the ViewModel. Can anyone help me with this?
Jim

After trying many different arrangements, I found that the correct syntax to wire up your MvxCommand is
public IMvxCommand FocusChange
{
get
{
return new MvxCommand<EditText.FocusChangeEventArgs>(e => OnFocusChange(e));
}
}
private void OnFocusChange(EditText.FocusChangeEventArgs e)
{
if (!e.HasFocus)
{
//Do Something
}
}
Hope this helps!

Related

How to fire TextChanged event on a Xamarin Forms entry field programmatically?

We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
However, we'd also like to reuse this behaviors when a submit button is tapped.
So I need to fire the TextChanged event manually, any ideas how I can do this, now sure if it's possible ?
public class NotEmptyEntryBehaviour : Behavior<Entry>
{
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(bindable);
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(bindable);
}
void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (args == null)
return;
var oldString = args.OldTextValue;
var newString = args.NewTextValue;
}
}
If you want an alternative you can use one of the pre-built validation behaviors that comes with Xamarin.CommunityToolkit package, like TextValidationBehavior (by specifying a Regexp) or any more specific derived ones (example NumericValidationBehavior) that may fit your needs or even create a custom one by sub-classing ValidationBehavior.
It let you define custom styles for Valid and InValid states, but more important for the question has an async method called ForceValidate().
Also the Flags property could be interesting.
NotEmptyEntryBehaviour seems closer to TextValidationBehavior with MinimumLenght=1
xaml
<Entry Placeholder="Type something..." x:Name="entry">
<Entry.Behaviors>
<xct:TextValidationBehavior Flags="ValidateOnValueChanging"
InvalidStyle="{StaticResource InvalidEntryStyle}"
ValidStyle="{StaticResource ValidEntryStyle}"/>
</Entry.Behaviors>
</Entry>
Code
await (entry.Behaviors[0] as TextValidationBehavior)?.ForceValidate();
Docs
https://learn.microsoft.com/en-us/xamarin/community-toolkit/behaviors/charactersvalidationbehavior
Repo Samples
https://github.com/xamarin/XamarinCommunityToolkit/tree/main/samples/XCT.Sample/Pages/Behaviors
EDIT
If you want to run the validation from the ViewModel you need to bind ForceValidateCommand as explained in this GitHub discussion/question.
We have setup some Xamarin behavior for not null entry fields etc, this fires when the user makes a change to a field and we then changed the entry border color, red for invalid.
You can create custom Entry with behavior to get.
The first I’m going to do is to create a new control that inherits from Entry and will add three properties: IsBorderErrorVisible, BorderErrorColor, ErrorText.
public class ExtendedEntry : Entry
{
public static readonly BindableProperty IsBorderErrorVisibleProperty =
BindableProperty.Create(nameof(IsBorderErrorVisible), typeof(bool), typeof(ExtendedEntry), false, BindingMode.TwoWay);
public bool IsBorderErrorVisible
{
get { return (bool)GetValue(IsBorderErrorVisibleProperty); }
set
{
SetValue(IsBorderErrorVisibleProperty, value);
}
}
public static readonly BindableProperty BorderErrorColorProperty =
BindableProperty.Create(nameof(BorderErrorColor), typeof(Xamarin.Forms.Color), typeof(ExtendedEntry), Xamarin.Forms.Color.Transparent, BindingMode.TwoWay);
public Xamarin.Forms.Color BorderErrorColor
{
get { return (Xamarin.Forms.Color)GetValue(BorderErrorColorProperty); }
set
{
SetValue(BorderErrorColorProperty, value);
}
}
public static readonly BindableProperty ErrorTextProperty =
BindableProperty.Create(nameof(ErrorText), typeof(string), typeof(ExtendedEntry), string.Empty);
public string ErrorText
{
get { return (string)GetValue(ErrorTextProperty); }
set
{
SetValue(ErrorTextProperty, value);
}
}
}
Then creating custom render to android platform.
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
namespace FormsSample.Droid
{
public class ExtendedEntryRenderer : EntryRenderer
{
public ExtendedEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null || e.NewElement == null) return;
UpdateBorders();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null) return;
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName)
UpdateBorders();
}
void UpdateBorders()
{
GradientDrawable shape = new GradientDrawable();
shape.SetShape(ShapeType.Rectangle);
shape.SetCornerRadius(0);
if (((ExtendedEntry)this.Element).IsBorderErrorVisible)
{
shape.SetStroke(3, ((ExtendedEntry)this.Element).BorderErrorColor.ToAndroid());
}
else
{
shape.SetStroke(3, Android.Graphics.Color.LightGray);
this.Control.SetBackground(shape);
}
this.Control.SetBackground(shape);
}
}
}
Finally, Creating an Entry Behavior, handle the error to provide ui feedback to the user when validation occurs
public class EmptyEntryValidatorBehavior : Behavior<ExtendedEntry>
{
ExtendedEntry control;
string _placeHolder;
Xamarin.Forms.Color _placeHolderColor;
protected override void OnAttachedTo(ExtendedEntry bindable)
{
bindable.TextChanged += HandleTextChanged;
bindable.PropertyChanged += OnPropertyChanged;
control = bindable;
_placeHolder = bindable.Placeholder;
_placeHolderColor = bindable.PlaceholderColor;
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
ExtendedEntry customentry = (ExtendedEntry)sender;
if (!string.IsNullOrEmpty(customentry.Text))
{
((ExtendedEntry)sender).IsBorderErrorVisible = false;
}
else
{
((ExtendedEntry)sender).IsBorderErrorVisible = true;
}
}
protected override void OnDetachingFrom(ExtendedEntry bindable)
{
bindable.TextChanged -= HandleTextChanged;
bindable.PropertyChanged -= OnPropertyChanged;
}
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == ExtendedEntry.IsBorderErrorVisibleProperty.PropertyName && control != null)
{
if (control.IsBorderErrorVisible)
{
control.Placeholder = control.ErrorText;
control.PlaceholderColor = control.BorderErrorColor;
control.Text = string.Empty;
}
else
{
control.Placeholder = _placeHolder;
control.PlaceholderColor = _placeHolderColor;
}
}
}
}
Update:
You can change custom entry's IsBorderErrorVisible in button.click, to call this from submit button.
private void btn1_Clicked(object sender, EventArgs e)
{
if(string.IsNullOrEmpty(entry1.Text))
{
entry1.IsBorderErrorVisible = true;
}
}
<customentry:ExtendedEntry
x:Name="entry1"
BorderErrorColor="Red"
ErrorText="please enter name!">
<customentry:ExtendedEntry.Behaviors>
<behaviors:EmptyEntryValidatorBehavior />
</customentry:ExtendedEntry.Behaviors>
</customentry:ExtendedEntry>

Change data binding value in ViewModel xamarin forms android

I have a binding set up:
using System;
using System.Collections.Generic;
using System.Text;
using LoanApp2.Model;
using System.Windows.Input;
using System.Threading.Tasks;
using System.ComponentModel;
using LoanApp2.Views;
using Newtonsoft.Json;
using System.Net.Http;
namespace LoanApp2.ViewModel
{
public class LoginViewModel : INotifyPropertyChanged
{
// For data binding of activity indicator
string actIndVal = "False";
public string ActIndVal {
get => actIndVal;
set {
if(actIndVal == value)
{
return;
} else
{
actIndVal = value;
OnPropertyChanged(nameof(ActIndVal));
}
}
}
public static List<LoginBasicData> listLoginBasicData = new List<LoginBasicData>();
public LoginViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(value));
}
public static async void VerifyClientID(string clientID)
{
// Start of HTTP Requests
using (HttpClient client = new HttpClient())
{
...
}
}
}
The default value would be false, in in the class VerifyClientID(), I want to call the ActIndVal and change it to true so that the Activity Indicator will be visible. And call it again in the bottom part of the VerifyClientID() class so that I can change the value to false again.
Change the property in the ViewModel won't work, you should change the value of ActIndVal of the ViewModel which is your BindContext.
For example:
public partial class MainPage : ContentPage
{
LoginViewModel myViewModel;
public MainPage()
{
InitializeComponent();
myViewModel = new LoginViewModel();
this.BindingContext = myViewModel;
}
private async void Button_Clicked(object sender, EventArgs e)
{
myViewModel.ActIndVal = true;
await myViewModel.VerifyClientID("clientID");
myViewModel.ActIndVal = false;
}
}
public class LoginViewModel : INotifyPropertyChanged
{
// For data binding of activity indicator
bool actIndVal = false;
public bool ActIndVal
{
get => actIndVal;
set
{
if (actIndVal == value)
{
return;
}
else
{
actIndVal = value;
OnPropertyChanged(nameof(ActIndVal));
}
}
}
public LoginViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(value));
}
public async Task VerifyClientID(string clientID)
{
// Start of HTTP Requests
await Task.Delay(3000);
}
}
myViewModel.ActIndVal is the value you need to change because Frame.IsVisible is binding to this value. Changing the value of ActIndVal in your viewModel makes no sense.
I uploaded my sample project here and feel free to ask me any question.
first of all this should be a boolean to avoid casting issues
private bool actIndVal;
public bool ActIndVal {
get => actIndVal;
set {
if(actIndVal == value)
{
return;
} else
{
actIndVal = value;
OnPropertyChanged(nameof(ActIndVal));
}
}
}
Then in your method change the values:
public async Task VerifyClientID(string clientID)
{
ActIndVal= true;
//API CALL CODE
ActIndVal= false;
}
Then just bind this to your Indicator
<ActivityIndicator IsRunning="{Binding ActIndVal}" IsVisible="{Binding ActIndVal}" ....

Moving Strings between UserControls not Working

I am trying to pass strings between forms. Why does it not? Am I missing something or is it an error in the program or what?
On UserControl3
UserControl1 u1;
public UserControl3()
{
u1 = new UserControl1();
InitializeComponent();
}
On UserControl3
public void materialCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if (materialCheckBox1.Checked)
{
u1.toUserControl3 = "GOINTHEBOX!";
}
else
{
u1.toUserControl3 = string.Empty;
}
}
On UserControl1
public string toUserControl3
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
On UserControl1
public void textBox1_TextChanged(object sender, EventArgs e)
{
}
Changing the Text property on a control through a piece of code doesn't necessarily mean the value control will update. Typically you need some sort of binding between your property, in this case toUserControl3, and your control. You need a way to tell your control that value changed so it knows to update.
You could accomplish databinding in the following way:
Create a new class to handle state and binding: This eliminated any need to pass controls into constructors of other controls.
public class ViewModel : INotifyPropertyChanged
{
public string TextBoxText => CheckBoxChecked ? "GOINTOTHEBOX!" : string.Empty;
public bool CheckBoxChecked
{
get { return _checkBoxChecked; }
set
{
_checkBoxChecked = value;
OnPropertyChanged("CheckBoxChecked");
}
}
private bool _checkBoxChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is your main form
public void Form1
{
public Form1(ViewModel viewModel)
{
UserControl1.DataBindings.Add("TextBoxTextProperty", viewModel, "TextBoxText");
UserControl3.DataBindings.Add("MaterialCheckBoxCheckedProperty", viewModel, "CheckBoxChecked");
}
}
UserControl1
public void UserControl1()
{
public string TextBoxTextProperty
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
}
UserControl3
public void UserControl3()
{
public bool MaterialCheckBoxCheckedProperty
{
get { return materialCheckBox1.Checked; }
set { materialCheckBox1.Checked = value; }
}
}

CanExecute of ApplicationCommand does not update

I would like to call an ApplicationCommand with a MenuItem-Click:
<MenuItem Header="{StaticResource MenuItemSave}" Command="ApplicationCommands.Save"/>
In my ViewModel-Constructor I inizialize my bindings with:
CommandBinding saveBinding = new CommandBinding(ApplicationCommands.Save, SaveCommand_Execute, SaveCommand_CanExecute);
CommandManager.RegisterClassCommandBinding(typeof(ViewModel_Main), saveBinding);
RegisterCommandBindings.Add(saveBinding);
Now I would like to handle the command, but it simply is not executeable. Even if it should be ALWAYS true.
private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void SaveCommand_Execute(object sender, ExecutedRoutedEventArgs e)
{
//Stuff
}
I have also tried to update all bindings after my init function:
CommandManager.InvalidateRequerySuggested();
But my MenuItem stays disabled.
Thank you!
I would suggest using this implementation of ICommand
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace Common
{
public class Command<TArgs> : ICommand
{
public Command(Action<TArgs> exDelegate)
{
_exDelegate = exDelegate;
}
public Command(Action<TArgs> exDelegate, Func<TArgs, bool> canDelegate)
{
_exDelegate = exDelegate;
_canDelegate = canDelegate;
}
protected Action<TArgs> _exDelegate;
protected Func<TArgs, bool> _canDelegate;
#region ICommand Members
public bool CanExecute(TArgs parameter)
{
if (_canDelegate == null)
return true;
return _canDelegate(parameter);
}
public void Execute(TArgs parameter)
{
if (_exDelegate != null)
{
_exDelegate(parameter);
}
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
bool ICommand.CanExecute(object parameter)
{
if (parameter != null)
{
var parameterType = parameter.GetType();
if (parameterType.FullName.Equals("MS.Internal.NamedObject"))
return false;
}
return CanExecute((TArgs)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((TArgs)parameter);
}
#endregion
}
}

ICollectionView's CurrentChanged event not working if we create full property

when we create auto-property of ICollectionView then CurrentChanged event is working properly after refreshing Employee collection.
public ICollectionView EmployeeCollectionView{get; set; }
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
And when we create full-property then CurrentChanged event is not working.
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set { _employeeCollectionView = value; OnPropertyChanged("EmployeeCollectionView");}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}
please suggest if i missing something.
Have you bind EmployeeCollectionView_CurrentChanged event after refreshing employee collection? because if you refreshing employee collection then EmployeeCollectionView_CurrentChanged connection has been lost.
like-
private void Refresh()
{
GetEmployee();
EmployeeCollectionView.CurrentChanged += new EventHandler(EmployeeCollectionView_CurrentChanged);
}
If you expect EmployeeCollectionView to change (which seems so, otherwise you would not need an OnPropertyChanged, I'd recommend to add the Events in the setter of your property like following:
private ICollectionView _employeeCollectionView;
public ICollectionView EmployeeCollectionView
{
get { return _employeeCollectionView; }
set
{
if (_employeeCollectionView != value)
{
if (_employeeCollectionView != null)
{
_employeeCollectionView.CollectionChanged -= EmployeeCollectionView_CurrentChanged;
}
_employeeCollectionView = value;
_employeeCollectionView.CollectionChanged += EmployeeCollectionView_CurrentChanged;
OnPropertyChanged("EmployeeCollectionView");
}
}
}
public EmployeeMasterViewModel(IEmployeeMasterView view, IUnityContainer container)
{
GetEmployee();
}
private void EmployeeCollectionView_CurrentChanged(object sender, EventArgs e)
{
var currentEmployee = EmployeeCollectionView.CurrentItem as EmployeeMaster;
}

Categories