Xamarin.Forms. XAML Label IsVisible condition is not getting evaluated as expected

Viewed 1084

In my project I have to select several options on a page.

Every option has a type MyCustomType and has public string Name property declared.

Every option is displayed via label. When I click on the label I display list of options and select it.

As one option is selected, an empty label with placeholder text (like select an item) for another option should appear below the label with the just selected option.

I use separate labels for every option, not a ListView element (customer's requirement for the particular look & feel).

Number of options is limited, let say it equals to four.

In my viewmodel I have declared the list property (it has been initialized in viewmodel constructor):

public List<MyCustomType> AllOptions { get; }

In my XAML page labels are declared as:

<Label Text="{Binding AllOptions[0].Name}" >

<Label Text="{Binding AllOptions[1].Name}" IsVisible="{Binding AllOptions[0], Converter={StaticResource NullToFalseBoolConverter}}">

<Label Text="{Binding AllOptions[2].Name}" IsVisible="{Binding AllOptions[1], Converter={StaticResource NullToFalseBoolConverter}}">

<Label Text="{Binding AllOptions[3].Name}" IsVisible="{Binding AllOptions[2], Converter={StaticResource NullToFalseBoolConverter}}">

Converter NullToFalseBoolConverter looks like that:

public class NullToFalseBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value != null;
    }

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

The problem is that IsVisible condition, specified into label declarations does not work.

All labels are displayed.

And breakpoint, set to the first line of Convert method of NullToFalseBoolConverter is not getting reached.

I don't understand why does it happen.

Any ideas?

2 Answers

That's funny, but the following approach fixed my problem.

I've had AllOptions declared as List (did not work), and as ObservableCollection (did not work too).

I should declare my list as array:

public MyCustomType[] AllOptions { get; }

And my labels start displaying properly, one after another is set.

And upon processing its values, if I get null value, that means, that we're reached the end of populated options.

Instead of trying binding in that matter, consider using either a layout type that supports both an ItemSource and also a data template. So this would be something like a ListView/CollectionView/Stacklayout

So, for example, if you decide to use a StackLayout for example:

<StackLayout
...
BindableLayout.ItemsSource="{Binding AllOptions}">
<BindableLayout.ItemTemplate>
        <DataTemplate>
            <Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}">

        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

The beauty of this approach is that since your labels all follow the same approach you can now not only A) write cleaner code, and B) leverage MVVM patterns. Now, of course this does mean that each control gets the converters applied to them as well; however, if still want to not include it for the first element then all we have to do is change your type to include an index property. If you're wondering what the syntax Binding . means, it means that we are just binding the whole object of that collection for that element.

public int Index {get; set;}

Set it where you build that array, and then in the converter all you have to do is:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var element = (MyCustomType) value;
if(element.Index != 0)
        return value != null;
else
        return true;
}

EDIT

So as an addition to what you want to do in terms of passing the selected item to your command you can do the following. I am assuming that your overall page is a , but the same concept applies to any type of page really.

  1. Set an x:Name property on the , give it any name you want. ex:

    <ContentPage ... x:Name="root">

  2. Define a <GestureRecognizer> on your Label

<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding BindingContext.YourCommandName, Source={x:Reference root}}" CommandParameter="{Binding .}"/> </Label.GestureRecognizers>

  1. In your view model that is bond to the page, create the following command as follows:

    public ICommand YourCommandName => new Command(x => YourCustomMethodHere(x));

  2. Finally create the method that handles the object you selected

    public void YourCustomMethodHere(MyCustomType type) {}

So what the above XAML code does is that we are binding the command of the Label to the overall parent view model, when an item is inside of a DataTemplate that has been defined by its ItemSource, its view model is actually the model that is being used a data template; that is why we are setting its source VM to be the one for the overall parent. The CommandParameter="{Binding .}" is the same logic as before, we are binding that whole data template item, in this case the MyCustomType that has been rendered for that element. This way each time that label is tapped, we are passing that label and its data to the command that we now defined in the VM.

Related