Required to Select atleast one Item from Picker in Xamarin

Viewed 576

In my Application, there is a Picker and Button. I want two things.

  1. Required to Select atleast one Item from Picker, else won't be able to click a Button. e.g. just like required Attribute in HTML

  2. If the value is Provider 1, then Navigate to this Page, else if Provider 2, then navigate to this Page.

Code for Picker & Button in Views

<Picker x:Name="picker_provider" 
        Title="Select a Provider">
        <Picker.ItemsSource>
            <x:Array Type="{x:Type x:String}">
                <x:String>Provider 1</x:String>
                <x:String>Provider 2</x:String>
            </x:Array>
        </Picker.ItemsSource>
</Picker>

<Button
    Text="Next"
    Command="{Binding NextPageCommand}"/>

Code for Button Binding in ViewModel.cs

public async Task NextPage(RegisterViewModel nextpage) => await NavigationService.NavigateToAsync(nextpage, null, NavigationType.Modal);

#region Bindable Command
public ICommand NextPageCommand => new Command<RegisterViewModel>(async (nextpage) =>
    {
        await NextPage(nextpage);
    });
#endregion
4 Answers

You can use a multi data trigger in your button to enable or disable it depending on the status of your picker selection properties.

Because you are binding to strings, you will need add a converter to test whether they are null or empty, for example:

namespace Osma.Mobile.App.Converters
{
    public class StringNullOrEmptyValueBoolConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string str && !string.IsNullOrEmpty(str))
            {
                // String is not null or empty
                return false;
            }
            
            // String is null or empty
            return true;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Add the required properties to your ViewModel:

public class ProviderViewModel : ABaseViewModel
{
    public ProviderViewModel(
        IUserDialogs userDialogs,
        INavigationService navigationService
        ) : base(
            nameof(ProviderViewModel),
            userDialogs,
            navigationService)
    {
    }

    public override async Task InitializeAsync(object navigationData)
    {
        await base.InitializeAsync(navigationData);
    }

   private string _selectedProvider;
   public string SelectedProvider {
       get { return _selectedProvider; }
       set 
       { 
            this.RaiseAndSetIfChanged(ref _selectedProvider, value) }
       }
  }

   private string _selectedCountry;
   public string SelectedCountry {
       get { return _selectedCountry; }
       set 
       { 
           this.RaiseAndSetIfChanged(ref _selectedCountry, value) }
       }
  }

    public async Task RegistrationPage(RegisterViewModel registration) => await NavigationService.NavigateToAsync(registration, null, NavigationType.Modal);

    public ICommand RegistrationPageCommand => new Command<RegisterViewModel>(async (registration) =>
    {
        await RegistrationPage(registration);
    }, (x) => false);
}

Add the converter namespace and resource to your page and implement the multitrigger and bindings:

<?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:converters="clr-namespace:Osma.Mobile.App.Converters"
             x:Class="Osma.Mobile.App.Views.ProviderPage"
             NavigationPage.HasNavigationBar="False"
             BackgroundColor="#004B86">

   <ContentPage.Resources>
    <ResourceDictionary>
        <converters:StringNullOrEmptyValueBoolConverter x:Key="stringNullOrEmptyValueBoolConverter" />
    </ResourceDictionary>
   </ContentPage.Resources>


    <ContentPage.Content>
        <StackLayout
            Spacing="30">

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="70" />
                    <RowDefinition Height="70" />
                </Grid.RowDefinitions>
                
                <Grid Grid.Row="0">
                    <Picker 
                        x:Name="picker_country" 
                        Title="Select a Country" 
                        TextColor="Silver" 
                        TitleColor="Silver" 
                        HorizontalOptions="Start" 
                        WidthRequest = "200" 
                        VerticalOptions="Start" 
                        Margin="130,0,0,0"
                        SelectedItem="{Binding SelectedCountry}">
                        <Picker.ItemsSource>
                            <x:Array Type="{x:Type x:String}">
                                <x:String>Sweden</x:String>
                            </x:Array>
                        </Picker.ItemsSource>
                    </Picker>
                </Grid>

                <Grid Grid.Row="1">
                    <Picker 
                        x:Name="picker_provider" 
                        Title="Select a Provider" 
                        TextColor="Silver" 
                        TitleColor="Silver" 
                        HorizontalOptions="Start" 
                        WidthRequest = "200" 
                        VerticalOptions="Start" 
                        Margin="130,0,0,0"
                        SelectedItem="{Binding SelectedProvider}">
                        <Picker.ItemsSource>
                            <x:Array Type="{x:Type x:String}">
                                <x:String>Provider 1</x:String>
                            </x:Array>
                        </Picker.ItemsSource>
                    </Picker>
                </Grid>
            </Grid>

        <Button 
            BackgroundColor="#2194EF"
            TextColor="White" 
            Text="Next" 
            HeightRequest="60"
            MinimumHeightRequest="60"
            IsEnabled="False"
            Command="{Binding RegistrationPageCommand}">
            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding SelectedCountry,
                                                       Converter={StaticResource stringNullOrEmptyValueBoolConverter }}" Value="false" />
                        <BindingCondition Binding="{Binding SelectedProvider,
                                                       Converter={StaticResource stringNullOrEmptyValueBoolConverter }}" Value="false" />
                    </MultiTrigger.Conditions>
        
                    <Setter Property="IsEnabled" Value="True" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

As you can see in the code the multrigger conditions are that both properties are not null or empty and when this is the case the button will be enabled.

In your ViewModel have a property called IsNextButtonEnabled that watches if an item has been selected. Also change your XAML, so that the button is enabled when IsNextButtonEnabled is true:

ViewModel:

public bool IsNextButtonEnabled => SelectedItem!=null;
public string SelectedItem 
{
    get=>_selectedItem;
    set { _selectedItem = value; OnPropertyChanged("SelectedItem");}
}
private string _selectedItem;

Bear in mind the OnPropertyChanged function may be different depending on what (if any) libraries you use for your view models.

Xaml:

<Picker x:Name="picker_provider" 
    Title="Select a Provider" 
    SelectedItem="{Binding SelectedItem}>
    <Picker.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>Provider 1</x:String>
            <x:String>Provider 2</x:String>
        </x:Array>
    </Picker.ItemsSource>
public ICommand NextPageCommand => new Command<RegisterViewModel>(async (nextpage) =>
{
    if(_selectedItem == "Provider 1")  await ThisPage();
    else                               await OtherPage();
});

Required to Select atleast one Item from Picker, else won't be able to click a Button. e.g. just like required Attribute in HTML

You could use DataTrigger to determine when to set Button.Enable.

<ContentPage.Resources>
    <ResourceDictionary>
        <local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
    </ResourceDictionary>

</ContentPage.Resources>

<StackLayout>
    <Picker x:Name="picker_provider" 
            Title="Select a Provider" SelectedItem="{Binding xxx}">
        <Picker.ItemsSource>
            <x:Array Type="{x:Type x:String}">
                <x:String>Provider 1</x:String>
                <x:String>Provider 2</x:String>
            </x:Array>
        </Picker.ItemsSource>
    </Picker>

    <Button Text="Next" IsEnabled="False" Command="{}">
        <Button.Triggers>
            <DataTrigger 
                TargetType="Button"
                Binding="{Binding Source={x:Reference picker_provider}, Path = SelectedIndex ,
                Converter={StaticResource dataHasBeenEntered}}" Value="true">
                <Setter Property="IsEnabled" Value="True" />
            </DataTrigger>
        </Button.Triggers>
    </Button>
</StackLayout>


public class MultiTriggerConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        if ((int)value >= 0) // length > 0 ?
            return true;            // some data has been entered
        else
            return false;            // input is empty
    }

    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

If the value is Provider 1, then Navigate to this Page, else if Provider 2, then navigate to this Page

Create binding on Picker.SelectedItem , and navigate to corresponding page according to the value .

public ICommand NextPageCommand => new Command<RegisterViewModel>(async (nextpage) =>
{

     // here you could get the value of `Picker.SelectedItem`. 
     
});

enter image description here

enter image description here

enter image description here

Add a flag in your view model such as:

private bool _canNavigate;
public bool CanNavigate
{
    get { return _canNavigate; }
    set { this.RaiseAndSetIfChanged(ref _canNavigate, value) }
}

Bind this property to the property "IsEnabled" of your button:

<Button
Text="Next"
IsEnabled="{Binding CanNavigate}"
Command="{Binding NextPageCommand}"/>

Then add a binding for the selected item as well and enabled the button when :

private string _selectedItem;
public string SelectedItem {
  get { return _selectedItem; }
  set 
  { 
      if (!string.IsNullOrWhiteSpace(value)) 
      {
          this.RaiseAndSetIfChanged(ref _selectedItem, value) }
      }
  }
}

And add the binding in your view:

<Picker x:Name="picker_provider" 
        Title="Select a Provider" SelectedItem="{Binding SelectedItem}">
    <Picker.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>Provider 1</x:String>
            <x:String>Provider 2</x:String>
        </x:Array>
    </Picker.ItemsSource>
</Picker>

There are other (perhaps) more elegant ways of doing this, for example using a MultiTrigger (see here) or a behavior to intercept the SelectedIndexChanged event.

Related