Show/Hide Password MVVM Xamarin Forms Checkbox - c#

I have been trolling this site, youtube and google to find an answer but I only come up with EventTriggers or ImageSource.
I have a LoginPageView and LoginPageViewModel there is an Entry for where the user must put in their password and a CheckBox which allows the option of viewing the password entered.
I have tried numerous ways to bind and code the Checkbox to show the password but I only end up either not succeeding or just permanently showing the password.
I have the code for the .XAML
<Entry Text="{Binding Password}"
TextColor="White"
FontSize="18"
FontAttributes="Bold"
Placeholder="Password"
PlaceholderColor="White"
IsPassword="{Binding IsPass}"
x:Name="PasswordBox"
VerticalTextAlignment="Center"/>
<CheckBox Color="White"
x:Name="ChkShowPass"
Margin="25,0,0,0"
IsChecked="{Binding IsCheckPass}"/>
<Label Text="Show Password"
TextColor="White"
FontAttributes="Bold"
FontSize="17"
RelativeLayout.XConstraint="60"
RelativeLayout.YConstraint="4"/>
Now the options I have tried for LoginPageViewModel are as follows
Option 1
private bool isCheckPass;
public bool IsCheckPass
{
get { return isCheckPass; }
set
{
if (isCheckPass != value)
{
isCheckPass = value;
if (PropertyChanged != null)
{
}
}
}
}
Then tried .XAML
<ImageButton Source="ShowPass.png"
x:Name="BtnShowPass"
Margin="25,0,0,0"
BackgroundColor="Transparent"
Command="{Binding ToggleIsPassword}">
<ImageButton.Triggers>
<DataTrigger TargetType="ImageButton"
Binding="{Binding IsPassword}" Value="True">
<Setter Property="Source" Value="HidePass.png" />
</DataTrigger>
</ImageButton.Triggers>
</ImageButton>
.cs in LoginPageViewModel
private bool _IsPass = true;
public bool IsPass
{
get
{
return _IsPass;
}
set
{
_IsPass = value;
OnPropertyChanged();
OnPropertyChanged("IsPass");
}
}
public ICommand ToggleIsPassword => new Command(() => IsPass = !IsPass);
Please can someone assist here, any and all help welcome.

UPDATE
You need a converter to convert this for that you can use the InverseBoolConverter as shown below:
public class InverseBooleanConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Define it in your XAML:
<ContentPage.Resources>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</ContentPage.Resources>
If I were you I would do something like below:
In your ViewModel add a property:
public bool IsPasswordVisible
{
get => isPassword;
set
{
isPassword = value;
OnPropertyChanged();
}
}
And then In your View, you would Bind them as such:
<Entry Text="{Binding IsPasswordVisible, Converter={StaticResoource InverseBooleanConverter}}"
TextColor="White"
FontSize="18"
FontAttributes="Bold"
Placeholder="Password"
PlaceholderColor="White"
IsPassword="{Binding IsPass}"
x:Name="PasswordBox"
VerticalTextAlignment="Center"/>
<CheckBox Color="White"
x:Name="ChkShowPass"
Margin="25,0,0,0"
IsChecked="{Binding IsPasswordVisible, Mode=TwoWay}"/>
Hope this helps!

Related

Want to activate a button only when two inputs of the form are not empty using MAUI

I would like to do the following using C# MAUI.
I want to enable the Save button only if the contents of the email Entry and the phone Entry are both non-null.
The documentation has the following code
<Entry x:Name="email"
Text="" />
<Entry x:Name="phone"
Text="" />
<Button Text="Save">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference email},
Path=Text.Length}"
Value="0" />
<BindingCondition Binding="{Binding Source={x:Reference phone},
Path=Text.Length}"
Value="0" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
<!-- multiple Setter elements are allowed -->
</MultiTrigger>
</Button.Triggers>
</Button>
However, with the above code, the save button will be enabled as long as neither the email nor the phone is null.
How can I change this?
You can add a TextChanged event to the Entry to determine whether the Text is empty. I wrote a simple demo to test it. You can refer to the code:
Xaml:
<Entry x:Name="email" TextChanged="OnTextChanged"/>
<Entry x:Name="phone" TextChanged="OnTextChanged"/>
<Button x:Name="Save" Text="Click Me" Clicked="OnSaveClicked" IsEnabled="False"/>
.cs file:
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(email.Text) && !string.IsNullOrWhiteSpace(phone.Text))
{
Save.IsEnabled = true;
}
else
{
Save.IsEnabled = false;
}
}
Hope it can help you.
There are a couple of ways to achieve this apart from setting the IsEnabled property of the <Button> from the code-behind.
Option 1: Using the MVVM pattern
In your ViewModel, define some properties for E-Mail and Phone, a Command as well as an evaluation function that serves as a predicate for the CanExecute parameter for the Command like follows:
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string email;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
private string phone;
[RelayCommand(CanExecute = nameof(CanSave))]
private void Save()
{
// your logic here
}
private bool CanSave() => !string.IsNullOrWhiteSpace(Email) && !string.IsNullOrWhiteSpace(Phone);
}
Then, in the code-behind of your View (the *.xaml.cs file), you need to set the BindingContext to the ViewModel:
public partial class MyPage: ContentPage
{
public MyPage()
{
InitializeComponent();
BindingContext = new MyViewModel();
}
}
Finally, in your View, you can bind the Text property of each Entry to the appropriate property in the ViewModel and bind the Button to the Command:
<Entry Text="{Binding Email, Mode=TwoWay}" />
<Entry Text="{Binding Phone, Mode=TwoWay}" />
<Button Text="Save"
Command="{Binding SaveCommand}">
This way, the Button will only be enabled when both entries contain some text.
You can find more information on the Model-View-ViewModel (MVVM) pattern in the documentation. This example also uses the Source Generators of the MVVM Community Toolkit, which I've also written a blog series about which also covers the topic of enabling and disabling buttons based on property values.
Option 2: Using MultiBinding
Instead of a MultiTrigger, you could use a MultiBinding instead, which allows you evaluate your bindings to either true or false based on both entries.
For this, you first need a converter that implements the IMultiValueConverter interface and checks if all the inputs are of type string and not null, whitespace or empty:
public class AllNotNullOrEmptyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
{
return false;
}
foreach (var value in values)
{
if (value is not string b)
{
return false;
}
if (string.IsNullOrWhiteSpace(b))
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return default;
}
}
You can then consume the converter and use a MultiBinding in your XAML:
<ContentPage.Resources>
<converters:AllNotNullOrEmptyConverter x:Key="AllNotNullOrEmptyConverter" />
</ContentPage.Resources>
<VerticalStackLayout>
<Entry x:Name="Email"/>
<Entry x:Name="Phone"/>
<Button>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource AllNotNullOrEmptyConverter}">
<Binding Path="Text" Source="{x:Reference Email}" />
<Binding Path="Text" Source="{x:Reference Phone}" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</VerticalStackLayout>
The beauty of this approach is that you can add more entries and only need to add one extra line to the MultiBinding for each to include them in the evaluation for the IsEnabled property of the button.

How do I bind an ImageSource to a specific element's property in an ObservableCollection?

I would really like to know how I can bind an ImageSource to a specific element's property in an ObservableCollection... Right now I have this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyNameSpace"
x:Class="MyNameSpace.MainPage"
x:Name = "Main"
Padding="5,20,5,5"
BindingContext="{x:Reference Name=Main }">
<Grid
<ImageButton
Grid.Column="0"
Grid.Row="0"
Source="{Binding _hand[0].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="1"
Grid.Row="0"
Source="{Binding _hand[1].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="0"
Grid.Row="1"
Source="{Binding _hand[2].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
<ImageButton
Grid.Column="1"
Grid.Row="1"
Source="{Binding _hand[3].ResourceId , Converter={StaticResource
StringToSourceConverter}}"
>
</ImageButton>
</Grid>
</ContentPage>
I would like to bind the ImageSource to the following ObservableCollection of Cards...
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
private ObservableCollection<Card> _hand;
public MainPage()
{
Init();
InitializeComponent();
}
private void Init()
{
_hand = new ObservableCollection<Card>()
{
new Card("image1.jpg"),
new Card("image2.jpg"),
new Card("image3.jpg"),
new Card("image4.jpg")
};
}
}
My Card class looks something like this:
public Card ( string resourceId)
{
ResourceId = resourceId;
}
public string ResourceId { get; set; }
The Converter used :
public class ToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string ResourceId = value.ToString();
if (String.IsNullOrWhiteSpace(ResourceId))
return null;
return ImageSource.FromResource(ResourceId);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My question now is how do I make this code work? Also I have methods that switch elements in the collection. Where do I implement PropertyChangedEvent? Thank you guys a lot :)

Binding ContentView to ContentPage

I am trying to implement a xamarin app that will have a MainPage like a container that will host the rest of my pages(as content view?).
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:TalosLib"
mc:Ignorable="d"
x:Class="TalosLib.MainPage">
<ContentPage.Content>
<StackLayout >
<StackLayout.Resources>
<DataTemplate x:Key="login">
<views:LoginPage />
</DataTemplate>
</StackLayout.Resources>
<ContentView Content="{Binding CurrentView}" ControlTemplate="{StaticResource login}"/>
<!--<CollectionView ItemsSource="{Binding CurrentView}" ItemTemplate="{StaticResource login}"/>-->
</StackLayout>
</ContentPage.Content>
MainPageModel.cs
public class MainPageModel : FreshBasePageModel
{
//private ObservableCollection<LoginPageModel> _currentView;
//public ObservableCollection<LoginPageModel> CurrentView
//{
// get { return _currentView; }
// set { _currentView = value; RaisePropertyChanged("CurrentView"); }
//}
private LoginPageModel _currentView;
public LoginPageModel CurrentView
{
get { return _currentView; }
set { _currentView = value; RaisePropertyChanged("CurrentView"); }
}
public override void Init(object initData)
{
base.Init(initData);
//CurrentView = new ObservableCollection<LoginPageModel>();
//CurrentView.Add(new LoginPageModel());
CurrentView = new LoginPageModel();
RaisePropertyChanged(nameof(CurrentView));
}
}
Right now i am trying just to show the LoginPage but it doesn't appear. I managed to make it work if i used the commented parts of the code. i am using FreshMVVM. Any thoughts?
Control templates help you define the root view like navbar or headers in all pages. I am not sure why you want to bind content property if you want to use a static resource. If you are going to change the content then we can use data templates and use a converter to convert the ViewModel to view.
If you are interested to change the content of the ContentView, then you can use data templates as follows:
<ResourceDictionary>
<views:DataTemplateToViewConverter x:Key="dataTemplateToViewConverter" />
<DataTemplate x:Key="Login">
<views:LoginView />
</DataTemplate>
<DataTemplate x:Key="Demo">
<views:DemoView />
</DataTemplate>
</ResourceDictionary>
<ContentView x:Name="contentview" Content="{Binding MyTemplate, Converter={StaticResource dataTemplateToViewConverter}}" />
<Button
Command="{Binding Clicked1}"
Text="1" />
<Button
Command="{Binding Clicked2}"
Text="2" />
In your ViewModel, you can use the command interface and set the templates
on clicked commands.. don't forget to create your MyTemplate bindable property.
private void Clicked2Called(object obj)
{
MyTemplate = "DemoView";
}
private void Clicked1Called(object obj)
{
MyTemplate = "Login";
}
In your converter you can do as follows:
public class DataTemplateToViewConverter : IValueConverter
{
public DataTemplateToViewConverter()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString() == "Login")
return new LoginView();
else
return new DemoView();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There are lots of ways to do this still better...I have used buttons to change the content, I am not sure how you wish to change the views when the menu items are selected. Hope it helps you solve your problem.

How to simplify bindings that depend on each other?

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

Bold label for selected item in ListView?

I have the following XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:StyleLabelInViewCell" x:Class="StyleLabelInViewCell.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:MainVM x:Key="MainVm" />
<local:IsSelectedToStyle x:Key="IsSelectedToStyle" />
<Style x:Key="SelectedStyle" TargetType="Label">
<Setter Property="FontAttributes" Value="Bold" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BindingContext="{StaticResource MainVm}">
<ListView x:Name="ChildListView" VerticalOptions="Center" ItemsSource="{Binding Chidren}" SelectedItem="{Binding SelectedVm}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="ChildViewCell">
<ViewCell.View>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Text="<" />
<Label
Grid.Column="1"
Text="{Binding Name}"
FontAttributes="{Binding Source={x:Reference ChildListView}, Path=SelectedItem.Id, Converter={StaticResource IsSelectedToStyle}, ConverterParameter={Binding Path=Id}}"
/>
<Button Grid.Column="2" Text=">" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
And the following code:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using Xamarin.Forms;
namespace StyleLabelInViewCell {
public class MainVM : INotifyPropertyChanged {
public MainVM() {
Chidren = new ObservableCollection<ChildVM> {
new ChildVM(1, "Item 1"),
new ChildVM(2, "Item 2"),
new ChildVM(3, "Item 3"),
new ChildVM(4, "Item 4"),
new ChildVM(5, "Item 5"),
new ChildVM(6, "Item 6")
};
}
public ObservableCollection<ChildVM> Chidren { get; }
private ChildVM selectedVm;
public ChildVM SelectedVm {
get { return selectedVm; }
set {
if (!ReferenceEquals(selectedVm, value)) {
selectedVm = value;
OnPropertyChanged(nameof(SelectedVm));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ChildVM {
public ChildVM() { }
public ChildVM(int id, string name) {
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
}
public sealed class IsSelectedToStyle : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var selectedId = (int)value;
var currentItemId = (int)parameter; // This ends up being Xamarin.Forms.Binding instance
return selectedId == currentItemId ? Application.Current.MainPage.Resources["SelectedStyle"] : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotSupportedException();
}
}
}
I'm trying to bold the label when the ItemTemplate is rending the currently selected row. The issue is that that ConverterParameter is sent in as a Xamarin.Forms.Binding instance, when I would have expected another integer.
Is there some way to get the value instead of the binding, or if not, from the Binding? Or is there another way to accomplish what I'm trying to do?
I think you can take a look to this post.
I have updated the repo. Now there is a Binding also for FontAttributes property
<ListView SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}" FontAttributes="{Binding Selected, Converter={StaticResource cnvInvertFontAttribute}}}" TextColor="{Binding Selected, Converter={StaticResource cnvInvert}}}" FontSize="18"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
when you select a row, Selected property is set in VM
public MyModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != null)
_selectedItem.Selected = false;
_selectedItem = value;
if (_selectedItem != null)
_selectedItem.Selected = true;
}
}
and a IValueConverter convert the boolean "Selected" property to a FontAttribute
public class SelectedToFontAttributeConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((Boolean)value)
return FontAttributes.Bold;
else
return FontAttributes.None;
}
return FontAttributes.None;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

Categories