user14139029
user14139029

Reputation:

Required to Select atleast one Item from Picker in Xamarin

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

Upvotes: 3

Views: 898

Answers (4)

Isma
Isma

Reputation: 15180

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.

Upvotes: 1

ColeX
ColeX

Reputation: 14475

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

Upvotes: 0

Isma
Isma

Reputation: 15180

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.

Upvotes: 0

Neil
Neil

Reputation: 11889

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();
});

Upvotes: 0

Related Questions