Bob
Bob

Reputation: 315

.NET Maui CollectionView, how to display value only if value exists

I would like to display the properties in a list of objects, but if the properties don't have any values I'd like to not only leave it blank but have the entire row not there at all (that's key).

Here's what I mean: let's say I have an object with four string values:

object person:
string firstname
string lastname
string favoriteMovie
string favoriteBook

Actually, I have a list of them: ObservableList<Person>. I want to display Person and the properties in each Person using CollectionView, only if the property doesn't have a value I want to skip it.

It might looks something like this:
enter image description here

The XAML code would look something like this:

<CollectionView ItemsSource={Binding People}>
   <CollectionView.ItemTemplate>
      <DataTemplate x:DataType="model:Person">
           <Frame>
               <Grid>
                   <Label Text="{Binding Name}"/>
                   <Label Text="{Binding FavoriteMovie}"/>
                   <Label Text="{Binding FavoriteBook}"/>
               ...
            ...
        ...
      ...
  </CollectionView>

How do I do that?

NOTE: I'm using MVVM pattern, and I'd like to mostly do this in XAML if possible.

Upvotes: 1

Views: 1057

Answers (2)

Peter Wessberg
Peter Wessberg

Reputation: 1921

Here is one way do this. First we do the Model that is responsive using the INotifyPropertyChanged and a PropertyChangedEventHandler, here we also put the boolean to use if we are to present the row or not.

public class Person : INotifyPropertyChanged
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FavoriteMovie { get; set; }
    public string FavoriteBook { get; set; }

    public string DisplayName => FirstName + " " + LastName;

    public bool HasFavoriteMovie => !string.IsNullOrEmpty(FavoriteMovie);
    public bool HasFavoriteBook => !string.IsNullOrEmpty(FavoriteBook);

    public event PropertyChangedEventHandler PropertyChanged;
}

The xaml code for this to mimic the layou from your image. On the label/grid we have IsVisible binded to the model boolean value.

<ContentPage.Resources>
    <Style x:Key="LabelStyle" TargetType="Label">
        <Setter Property="TextColor" Value="{StaticResource Blue100Accent}" />
        <Setter Property="FontSize" Value="18" />
        <Setter Property="LineHeight" Value="1.2" />
    </Style>
    <Style x:Key="TitleLabelStyle" TargetType="Label">
        <Setter Property="TextColor" Value="{StaticResource Primary}" />
        <Setter Property="FontSize" Value="28" />
        <Setter Property="TextTransform" Value="Uppercase" />
        <Setter Property="FontAttributes" Value="Bold" />
        <Setter Property="HorizontalOptions" Value="Center" />
        <Setter Property="LineHeight" Value="1.2" />
    </Style>
    <DataTemplate x:Key="PeopleTemplate">
        <Border
            Margin="20,10,10,10"
            BackgroundColor="{StaticResource LightBackgroundColor}"
            Stroke="Gray"
            StrokeThickness="2">
            <Border.StrokeShape>
                <RoundRectangle CornerRadius="10" />
            </Border.StrokeShape>
            <Border.GestureRecognizers>
                <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:PeopleViewModel}}, Path=PersonClickedCommand}" CommandParameter="{Binding .}" />
            </Border.GestureRecognizers>
            <Grid Padding="50,10,10,10">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Label
                    Grid.Row="0"
                    Style="{StaticResource LabelStyle}"
                    Text="{Binding DisplayName}" />
                <Label
                    Grid.Row="1"
                    IsVisible="{Binding HasFavoriteMovie}"
                    Style="{StaticResource LabelStyle}"
                    Text="{Binding FavoriteMovie}" />
                <Label
                    Grid.Row="2"
                    IsVisible="{Binding HasFavoriteBook}"
                    Style="{StaticResource LabelStyle}"
                    Text="{Binding FavoriteBook}" />
            </Grid>
        </Border>
    </DataTemplate>
</ContentPage.Resources>
<VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Style="{StaticResource TitleLabelStyle}" Text="People" />
    <CollectionView
        x:Name="PeopleCollection"
        ItemSizingStrategy="MeasureFirstItem"
        ItemTemplate="{StaticResource PeopleTemplate}"
        ItemsSource="{Binding People}">
        <CollectionView.ItemsLayout>
            <LinearItemsLayout Orientation="Vertical" />
        </CollectionView.ItemsLayout>
    </CollectionView>
    <Button
        Command="{Binding AddPersonCommand}"
        Text="Add Person"
        WidthRequest="200" />
</VerticalStackLayout>

And finally in your ViewModel you have

public partial class PeopleViewModel : ObservableObject
{
    public ObservableCollection<Person> People { get; set; }

    public PeopleViewModel()
    {
        People = new ObservableCollection<Person>
        {
            new Person { FirstName = "John", LastName = "Doe", FavoriteMovie = "Inception", FavoriteBook = "1984" },
            new Person { FirstName = "Jane", LastName = "Doe", FavoriteMovie = "Avatar" },
            new Person { FirstName = "Michael", LastName = "Brown" }
        };
    }

    [RelayCommand]
    private async Task PersonClicked(Person person)
    {
        var navigationParameter = new Dictionary<string, object>
            {
                { "Person", person }
            };
        await Shell.Current.GoToAsync($"{nameof(PersonPage)}", navigationParameter);
    }

    [RelayCommand]
    private void AddPerson()
    {
        People.Add(new Person { FirstName = "Emily", LastName = "Smith", FavoriteBook = "To Kill a Mockingbird" });
    }
}

This is what it looks like. I hope this is what you want.

enter image description here

Upvotes: 0

FreakyAli
FreakyAli

Reputation: 16562

You will need a Converter that converts empty or null strings to booleans and then you need to assign it to the visibility of your item so something like the below would work,

/// <summary>
/// Inherit this for one-way converters to avoid unnecessary extra methods in each converter
/// </summary>
public abstract class BaseOneWayValueConverter : IValueConverter
{
    public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException($"{GetType().Name} is a one-way converter");
    }
}

public class StringIsNullToBoolConverter : BaseOneWayValueConverter
{
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is not string text || !string.IsNullOrWhiteSpace(text);
    }
}

And then use it in your Label as shown below:

<ContentPage
.
.
.
.
xmlns:converters=*converter-namepsace-here* 
>
<ContentPage.Resources>
   <StringIsNullToBoolConverter x:Key="StringIsNullToBoolConverter"/>
</ContentPage.Resources>

Then use it in your respective labels:

<Label Text="{Binding Name}" IsVisible="{Binding Name,Converter={StaticResource StringIsNullToBoolConverter}}"/>

Upvotes: 1

Related Questions