Search code examples
c#xamlmaui

.NET MAUI XAML: How to make a CollectionView/ListView correctly convert and display null values?


I have a ViewModel with the following property:

    [ObservableProperty]
    ObservableCollection<Agent> agentList;

This collection is databound to the the ItemSource of a CollectionView and I'm using AgentToStringConverter to display Agent objects. This converter returns the string "None" if the value is null, however "None" is not showing up on my UI when AgentList contains a null value.

                    <CollectionView ItemsSource="{Binding AgentList}"
                                    SelectionMode="Multiple"
                                    SelectedItems="{Binding QueryAgents}"
                                    IsVisible="{Binding EnableFilters}"
                                    Grid.Row="2"
                                    Grid.Column="3">
                        <CollectionView.ItemTemplate>
                            <DataTemplate>
                                <Label Text="{Binding ., Converter={StaticResource AgentToString}}" />
                            </DataTemplate>
                        </CollectionView.ItemTemplate>
                    </CollectionView>

This is the converter:

internal class AgentToStringConverter : IValueConverter
{
    // Property to get Agents collection from, set in Resource declaration of ProjectDetailsPage.xaml
    public SettingsViewModel SettingsViewModel { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Agent agent)
        {
            var percentage = agent.FeeDecimal * 100;
            return $"{agent.Name}: {percentage:F1}%";
        }

        else if (value == null)
        {
            Debug.WriteLine("NULL VALUE CONVERTED");
            return "None";
        }

        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is string stringValue)
        {
            // Split the string into parts assuming it's in the format: "Name: Percentage%"
            var parts = stringValue.Split(':');

            if (parts.Length == 2 && decimal.TryParse(parts[1].Trim().TrimEnd('%'), out decimal percentage))
            {
                var name = parts[0].Trim();
                var feeDecimal = percentage / 100.0m;
                var agent = SettingsViewModel.Agents.First(a => a.Name == name && a.FeeDecimal == feeDecimal);
                return agent;
            }
        }
        return null;
    }
}

For testing purposes I have also used the same AgentList and AgentToStringConverter for a Picker and a ListView.

                    <Picker ItemsSource="{Binding AgentList}"
                            ItemDisplayBinding="{Binding ., Converter={StaticResource AgentToString}}"
                            Grid.Row="0"
                            Grid.Column="3" />

                    <ListView ItemsSource="{Binding AgentList}"
                              IsVisible="{Binding EnableFilters}"
                              Grid.Row="1"
                              Grid.Column="3">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <Label Text="{Binding ., Converter={StaticResource AgentToString}}" />
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>

                    <CollectionView ItemsSource="{Binding AgentList}"
                                    SelectionMode="Multiple"
                                    SelectedItems="{Binding QueryAgents}"
                                    IsVisible="{Binding EnableFilters}"
                                    Grid.Row="2"
                                    Grid.Column="3">
                        <CollectionView.ItemTemplate>
                            <DataTemplate>
                                <Label Text="{Binding ., Converter={StaticResource AgentToString}}" />
                            </DataTemplate>
                        </CollectionView.ItemTemplate>
                    </CollectionView>

The UI correctly displays "None" as a picker option, the ListView displays an empty space, and the CollectionView completely skips the null value and only shows the not-null Agents. Is there any way I can make the CollectionView or the ListView display the "None" for null values?

This is my collection i.imgur.com/MB0ix7F.png

And this is how it's displayed as Picker options on the left, and as a CollectionView on the right: i.imgur.com/Qg2aK7j.png

Both using the same AgentList as ItemSource and AgentToStringConverter as converter.


Solution

  • There are no nulls here:

    [ObservableProperty]
    ObservableCollection<Agent> agentList;
    

    ObservableCollection is observable enough. No need to create field, then property.

    You can do something like:

     class AgentWrapper {
           Agent agent; //this gets set null every now and then
     }
    

    And then make ObservableCollection< AgentWrapper >. Again - no nulls here, just inside.

    Edit: Another alternative:

    public String Property;
    
    public String DisplayProperty
    {
          get { return Property == null?"None":Property; }
    }
    

    And we bind to:

    <Label Text="{Binding DisplayProperty}" />