How to simplify bindings that depend on each other? - c#

I have an Entry that I want to give a red outline when the entry is empty. I'm using SyncFusion's SFTextInputLayout for my Entry and it has a property "HasError" that once it's set to true, it'll automatically highlight my entire in Red.
Here is the following XAML code for SFTextInputLayout
<inputLayout:SfTextInputLayout Grid.Column="0" Hint="Phone Number" ContainerType="{StaticResource RepairOrderContainerType}" HasError="{Binding IsPhoneNumberError}" FocusedColor="{StaticResource AccentColor}" VerticalOptions="Center" HorizontalOptions="Start">
<Entry Keyboard="Telephone" Style="{StaticResource TextInputStyle}" Text="{Binding PhoneNumber}"/>
</inputLayout:SfTextInputLayout>
As you can see, I have two bindings that handles the text of the entry and another one to check if it has an error or not. While this solution works, it will get redundant pretty soon as the number of my entry fields grow. For every entry field I have, I need another boolean to cover its Error property as shown below.
private string _phoneNumber;
public string PhoneNumber
{
get => _phoneNumber;
set
{
IsPhoneNumberError = string.IsNullOrWhiteSpace(value) ? true : false;
this.RaiseAndSetIfChanged(ref _phoneNumber, _phoneNumber);
}
}
private bool _isPhoneNumberError = false;
public bool IsPhoneNumberError
{
get => _isPhoneNumberError;
set
{
this.RaiseAndSetIfChanged(ref _isPhoneNumberError, value);
}
}
I'm wondering if there's any way to simplify this code. Thanks in advance!

One way of many to accomplish this is by creating a custom control with a behavior.
create a custom control:
public class MySfTextInputLayout : SfTextInputLayout
{
public MySfTextInputLayout ()
{
Behaviors.Add(new ShowErrorBehavior());
}
public bool HasErrors
{
get { return (bool)GetValue(HasErrorsProperty); }
set { SetValue(HasErrorsProperty, value); }
}
public static readonly BindableProperty HasErrorsProperty =
BindableProperty.Create(nameof(HasErrors), typeof(bool), typeof(MySfTextInputLayout ), false);
}
and the behavior:
public class ShowErrorBehavior : Behavior<MySfTextInputLayout>
{
protected override void OnAttachedTo(MySfTextInputLayout entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}
protected override void OnDetachingFrom(MySfTextInputLayout entry)
{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
}
void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
((MySfTextInputLayout)sender).HasErrors = string.IsNullOrWhiteSpace(args.NewTextValue);
}
}
The behavior will decide the validity of text for you, so you don't have to bind to another property just for that.
Also take a look at the Validation API, you may want to add multiple rules for an entry to be valid:
https://devblogs.microsoft.com/xamarin/validation-xamarin-forms-enterprise-apps/

You can achieve requirement to handle error when adding multiple entry fields by binding entry text to HasError property and using Converter as shown in below code snippets.
Code snippets [Xaml]:
<StackLayout >
<inputLayout:SfTextInputLayout
Grid.Column="0"
HasError="{Binding Text,Source={x:Reference entry1}, Converter={StaticResource Converter}}"
Hint="Phone Number"
HorizontalOptions="Start"
VerticalOptions="Center">
<Entry x:Name="entry1" Keyboard="Telephone" Text="{Binding PhoneNumber}" />
</inputLayout:SfTextInputLayout>
<inputLayout:SfTextInputLayout
Grid.Column="0"
HasError="{Binding Text,Source={x:Reference entry2}, Converter={StaticResource Converter}}"
Hint="Address"
HorizontalOptions="Start"
VerticalOptions="Center">
<Entry x:Name="entry2" Text="{Binding Address}" />
</inputLayout:SfTextInputLayout>
</StackLayout>
Code snippets [C#]:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
return string.IsNullOrEmpty(value.ToString()) ? true : false;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Sample: https://www.syncfusion.com/downloads/support/directtrac/general/ze/TextInputLayout-1703941642.zip

Related

Button Color Binding is effecting the size

I have three buttons. The save button's color is Green if the element can be changed and Gray if it cannot be changed. I bind the color of the button with property on ViewModel which is a string and Convert the value of the string using Converter. When I remove the binding of the color from the button, the size is fine, but with the binding, the size is smaller than other buttons' size.
<StackLayout Grid.Row="3"
Orientation="Horizontal"
Padding="5,5,5,5"
BackgroundColor="CadetBlue">
<Button x:Name="wczytajWzorzecButton"
WidthRequest="120" HeightRequest="20"
Text="Last" FontSize="12"
HorizontalOptions="CenterAndExpand"
Command="{Binding GetTemplateCommand}"/>
<Button x:Name="wczytajOstatniButton"
WidthRequest="120" HeightRequest="20"
Text="First" FontSize="12"
HorizontalOptions="EndAndExpand"
Command="{Binding GetLastDocumentCommand}"/>
<Button x:Name="saveButton"
WidthRequest="120" HeightRequest="20"
Text="Save" FontSize="12"
BackgroundColor="{Binding PropertyButtonColor, Converter={StaticResource StringToColorConverter}}"
HorizontalOptions="StartAndExpand"
Command="{Binding SaveDocumentCommand}"/>
</StackLayout>
My converter:
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string valueAsString = value.ToString();
switch (valueAsString)
{
case ("White"):
{
return Color.White;
}
case ("Gray"):
{
return Color.DarkGray;
}
case ("LighGreen"):
{
return Color.LightGreen;
}
default:
{
return Color.FromHex(value.ToString());
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
My ViewModel:
public string PropertyButtonColor
{
get
{
if (CanSave())
return "Green";
else
return "Gray";
}
}
Any ideas on why it might be happening?
This issue is not caused by binding, If you set the backgroundColor for Button, this Buttons size will be changed in android like following screenshot.
Actually that is a different drawable is being used
because you've requested a solid color, note that the wczytajOstatniButton and wczytajWzorzecButton have a shadow
But saveButton does not have it, it is blue.
if you enable Show Layout Bounds in Developer Options you will see the actual boundaries of the controls and it might be clear whether the button is getting larger, or just that the different drawable makes it appear to
Here is an workaround to fix this issue, you can create an custon Button.
public class TintableButton : Button
{
public static readonly BindableProperty TintColorProperty = BindableProperty.Create("TintColor", typeof(Color), typeof(Button), (object)Color.Default, BindingMode.OneWay, (BindableProperty.ValidateValueDelegate)null, (BindableProperty.BindingPropertyChangedDelegate)null, (BindableProperty.BindingPropertyChangingDelegate)null, (BindableProperty.CoerceValueDelegate)null, (BindableProperty.CreateDefaultValueDelegate)null);
public Color TintColor
{
get
{
return (Color)GetValue(TintColorProperty);
}
set
{
SetValue(TintColorProperty, value);
}
}
}
Then create an custom renderer in android. set SetColorFilter on the Control Background
[assembly: ExportRenderer(typeof(TintableButton), typeof(TintableButtonRenderer))]
namespace ImageViewModel.Droid
{
public class TintableButtonRenderer : ButtonRenderer
{
public TintableButtonRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
var control = e.NewElement as TintableButton;
if (control != null)
{
if (control.TintColor != Xamarin.Forms.Color.Default)
{
var androidColor = control.TintColor.ToAndroid();
Control.Background.SetColorFilter(androidColor, PorterDuff.Mode.Src);
}
}
}
}
}
Here is running screenshot.

Required to Enter a Value in Entry in Xamarin

In my Application there is an Multiple Entry and Button.
What I want is that the Entry shouldn't be empty and if they are, then Button won't work. e.g. just like required Attribute in HTML.
I asked some what similar question like this for Picker, but this solution didn't work on Entry.
Code for Entry & Button in Views
<Entry
Text="{Binding Email}"
Keyboard="Email"
ReturnType="Done"
Placeholder="Enter Email">
</Entry>
<Entry
Text="{Binding Password}"
Keyboard="Numeric"
IsPassword="true"
ReturnType="Done"
Placeholder="Enter Password">
</Entry>
<Button
Text="Submit"
Command="{Binding FormDataButtonCommand}"/>
Code for Entry and Button Binding in ViewModel.cs
#region Bindable Command
public ICommand FormDataButtonCommand => new Command(async () => await FormDataButton(default, default));
#endregion
#region Bindable Properties
private string _Email;
public string Email
{
get => _Email;
set => this.RaiseAndSetIfChanged(ref _Email, value);
}
private string _Password;
public string Password
{
get => _Password;
set => this.RaiseAndSetIfChanged(ref _Password, value);
}
I really like https://www.reactiveui.net and use a lot WhenAny... Observable and all the cool reactive stuff
But as #Miamy said the command will be disable that way, but If you want to change the color or style, I'll do it this way
<Entry
Text="{Binding Email}"
Keyboard="Email"
ReturnType="Done"
Placeholder="Enter Email">
</Entry>
<Entry
Text="{Binding Password}"
Keyboard="Numeric"
IsPassword="true"
ReturnType="Done"
Placeholder="Enter Password">
</Entry>
<Button
Text="Submit"
IsEnabled="{Binding IsButtonEnabled}"
Command="{Binding FormDataButtonCommand}"/>
private string _Email;
public string Email
{
get => _Email;
set {
IsButtonEnabled = !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(Password);
this.RaiseAndSetIfChanged(ref _Email, value);
}
}
private string _Password;
public string Password
{
get => _Password;
set {
IsButtonEnabled = !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(Email);
this.RaiseAndSetIfChanged(ref _Password, value);
}
}
private bool _IsButtonEnabled;
public bool IsButtonEnabled
{
get => _IsButtonEnabled;
set => this.RaiseAndSetIfChanged(ref _IsButtonEnabled, value);
}
Not a fan of doing it this way, but It's the simpler way... IMO
ICommand constructor cat take second parameter CanExecute. You should use it for enabling/disabling a command:
public ICommand FormDataButtonCommand { get; set; }
...
FormDataButtonCommand = new Command(FormDataButton, CanFormData); // in ctor
public bool CanFormData(object obj)
{
return !string.IsNullOrWhiteSpace(Email) && !string.IsNullOrWhiteSpace(Password);
}
private async void FormDataButton(object obj)
{
// your logic here
}
Use Multi-Bindings
<ContentPage.Resources>
<local:AnyNullValueCheckConverter x:Key="AnyNullValueCheckConverter" />
</ContentPage.Resources>
...
<Button>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource AnyNullValueCheckConverter}">
<Binding Path="Email" />
<Binding Path="Password" />
</MultiBinding>
</Button.IsEnabled>
</Button>
Converter
public class AnyNullValueCheckConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null)
{
return false;
}
foreach (var value in values)
{
if (value is string str && string.IsNullOrEmpty(str))
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can do it for other values e.g. Button.TextColor instead of false return Color.Gray and instead of true return Color.Black . Something like this.

Entry's are showing default text instead of showing placeholder Xamarin

When I run the app, on first up page instead Entrys are showing default text 0 instead of placeholder.
I have AddContactPage.xaml
<StackLayout>
<Entry Placeholder="Enter Class" Text="{Binding Class}"></Entry>
<Entry Placeholder="Enter Id" Text="{Binding StudentId}"></Entry>
</StackLayout>
Attributes binded to Entries
public int StudentId { get; set; }
public int Class { get; set; }
How can I solve this issue. See o/p-
Actually you can use StringFormat. Try this
StringFormat='{0:#.##;;}'
One possible solution is a string property for binding:
private int _studentId;
public int StudentId
{
get { return _studentId; }
set
{
SetProperty(ref _studentId, value);
RaisePropertyChanged("StudentIdString"); // If you're using Prism. You can use any other way to raise the PropertyChanged event
}
}
public string StudentIdString
{
get { return StudentId.ToString(); }
}
That's it! Now you can bind StudentIdString to your Entry. Do the same with Class and you're good to go.
Another solution to your problem is a converter as Woj suggested:
public class IntToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = (int)value;
return i.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return int.Parse((string)value);
}
}
Then use it in your xaml like this:
<ContentPage.Resources>
<ResourceDictionary>
<local:IntToStringConverter x:Key="intToString" />
</ResourceDictionary>
</ContentPage.Resources>
<Entry Placeholder="Enter Id" Text="{Binding StudentId, Converter={StaticResource intToString}}"></Entry>
Since the Entry control Text is binded to a model, it will not show a place holder for a null property which is associated. You may need to remove the Text binding in that case.

Change the Background Color of an ListView Item on Xamarin Forms

I have a ListView that binds its items from an ObservableCollection, and a Button that changes an "Amount" property of a specific object of that ObservableCollection. And I want to change the BackgroundColor of these Items whose "Amount" has already been changed.
I've searched for a solution for that, but I couldn't find any.
Does anybody know a way for solving that?
One way to do it would be to add a new property, something like HasAmountChanged, bind the background color of the viewcell to that property, and use a ValueConverter to set the color. This would look something like the following:
The object class with the properties:
public class MyObject : INotifyPropertyChanged
{
double amount;
bool hasAmountChanged = false;
public event PropertyChangedEventHandler PropertyChanged;
public MyObject(double amount)
{
this.amount = amount;
}
public double Amount
{
get => amount;
set
{
if (amount != value)
{
amount = value;
OnPropertyChanged(nameof(Amount));
HasAmountChanged = true;
}
}
}
public bool HasAmountChanged
{
get => hasAmountChanged;
set
{
if (hasAmountChanged != value)
{
hasAmountChanged = value;
OnPropertyChanged(nameof(HasAmountChanged));
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The view. Notice the stacklayout inside the ViewCell, that's where the background color is set:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Delete"
x:Class="Delete.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:ListViewBackgroundColorConverter x:Key="ListViewColorConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Click Me" Clicked="ButtonClicked" />
<ListView ItemsSource="{Binding MyItemsSource}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="15"
BackgroundColor="{Binding HasAmountChanged, Converter={StaticResource ListViewColorConverter}}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<Label Text="FOO 1"/>
<Label Text="{Binding Amount}"/>
<Label Text="{Binding HasAmountChanged}" />
<Label Text="FOO 4"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The code behind of the view, included for completeness:
public partial class MainPage : ContentPage
{
public ObservableCollection<MyObject> MyItemsSource { get; set; }
public MainPage()
{
InitializeComponent();
MyItemsSource = new ObservableCollection<MyObject>
{
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
};
BindingContext = this;
}
void ButtonClicked(object sender, EventArgs e)
{
var rnd = new Random();
var myObject = MyItemsSource[rnd.Next(0, MyItemsSource.Count)];
myObject.Amount = 5.09;
}
}
And finally the most important part, the converter:
public class ListViewBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Color.LawnGreen : Color.DarkRed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that you would actually want to check it's a bool coming in and handle that as well.
You could implement an array of booleans and change them to true when Amount gets changed. Then you might want to create a custom renderer for the color of each ListView.

How to set a String ViewModel property on ReadioButton Check usin WPF?

I am new to WPF and here I am trying to set a simple string property of my viewModel when a particular radio button is checked on my window.
class ViewModel
{
string LanguageSettings {get;set;}
}
XAML looks like following:
<RadioButton Name="OptionEnglish" GroupName="LanguageOptions" IsChecked="{Binding LanguageSettings, Converter={StaticResource Converter}, ConverterParameter=English}" Content="English" HorizontalAlignment="Right" Width="760" />
<RadioButton Name="OptionChinese" GroupName="LanguageOptions" IsChecked="{Binding LanguageSettings, Converter={StaticResource Converter}, ConverterParameter=Chinese}" Content="Chinese" />
I have implemented the IValueConverted which looks like below:
public class BoolInverterConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
return !(bool)value;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
return !(bool)value;
}
return value;
}
#endregion
}
Probably I am not properly understanding the utility of IValueConverter. I think if I change it appropriately it might work.
All I want here is that when English is selected I want Language Settings to be set as English and same for Chinese. Is there any simple way to do it? is there any straightforward way to set that property?
So i do have two options
1.Change my BoolConverterImplementation
2. Find another easier way to do it.
Any ideas?
An approach that does not require a converter:
<RadioButton Content="Chinese" IsChecked="{Binding IsChinese}"/>
<RadioButton Content="English" IsChecked="{Binding IsEnglish}"/>
ViewModel:
public class LanguageSelectorViewModel
{
private bool _isChinese;
private bool _isEnglish;
public bool IsChinese
{
get { return _isChinese; }
set
{
_isChinese = value;
if (value)
SelectedLanguage = "Chinese";
}
}
public bool IsEnglish
{
get { return _isEnglish; }
set
{
_isEnglish = value;
if (value)
SelectedLanguage = "English";
}
}
public string SelectedLanguage { get; set; }
}
You should look into implementing IValueConverter. Below is a starting example for what I believe you are looking for:
public class StringMatchConverter : IValueConverter
{
public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
{
return value == parameter;
}
public object ConvertBack(object value,Type targetType,object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example usage:
<!-- Somewhere in resources -->
<conv:StringMatchConverter x:Key="Conv" />
<!-- Somewhere in application -->
<RadioButton Name="OptionEnglish" GroupName="LanguageOptions" IsChecked="{Binding LanguageSettings,Converter={StaticResource Conv}, ConverterParameter=English}" Content="English" HorizontalAlignment="Right" Width="760" />
<RadioButton Name="OptionChinese" GroupName="LanguageOptions" IsChecked="{Binding LanguageSettings,Converter={StaticResource Conv}, ConverterParameter=Chinese}" Content="Chinese" />
I'm not sure but if I understand you correct you maybe could use a IValueConverter.
One way to do it would be to create an attached property:
public class MyAttachedProperty
{
public static readonly DependencyProperty IsCheckedToStrProperty =
DependencyProperty.RegisterAttached("IsCheckedToStr", typeof (string), typeof (MyAttachedProperty), new PropertyMetadata(default(string,IsCheckedToStr)))
private static void IsCheckedToStr(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RadioButton radio = d as RadioButton;
radio.Checked+=radio_Checked;
}
private static void radio_Checked(object sender, RoutedEventArgs e)
{
RadioButton radio = sender as RadioButton;
if (radio.IsChecked == true)
{
SetIsCheckedToStr(radio, radio.Content.ToString());
}
}
public static void SetIsCheckedToStr(UIElement element, string value)
{
element.SetValue(IsCheckedToStrProperty, value);
}
public static string GetIsCheckedToStr(UIElement element)
{
return (string) element.GetValue(IsCheckedToStrProperty);
}
}
And then you can use it in your xaml like this:
<RadioButton Name="OptionEnglish" GroupName="LanguageOptions" local:MyAttachedProperty.IsCheckedStr="{Binding LanguageSettings}" Content="English" HorizontalAlignment="Right" Width="760" />
<RadioButton Name="OptionChinese" GroupName="LanguageOptions" local:MyAttachedProperty.IsCheckedStr="{Binding LanguageSettings}" Content="Chinese" />

Categories