Search code examples
c#.netxamldata-bindingmaui

ObservableCollection conditional color binding in .NET MAUI


Using Binding with an ObservableCollection in .NET MAUI, how do I conditionally set the BackgroundColor property of a GUI element and evaluate the conditions for each item individually?

XAML <Label BackgroundColor="{Binding name.color}"/>
C# if (x) { name.color = Colors.Red; } else { name.color = Colors.Blue; }
or using string data like this:
C# name.color = "#FF0000"; does not work.

C# LabelCommonName.BackgroundColor = Colors.Red;
impossible using a CollectionView.

My actual XAML
I've left out the margins, paddings, grid definitions and other irrelevant styling.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:projectName"
             x:Class="projectName.CalendarPage"
             Title="CalendarPage">
    <Grid>
        <ScrollView>
            <RefreshView x:Name="CalendarRefresh"
                         Refreshing="RefreshCalendar">
                <CollectionView x:Name="CalendarView">
                    <CollectionView.ItemTemplate>
                        <DataTemplate x:DataType="local:Calendar">
                            <Frame>
                                <Grid>
                                    <!-- Trying to evaluate this BackgroundColor for each item in the CollectionView individually, so not the same value for all items: -->
                                    <Label BackgroundColor="{Binding 
                                               if (name.color=1) {
                                                   Colors.Red
                                               } else if (name.color=2) {
                                                   Colors.Blue
                                               } else {
                                                   Colors.Green
                                               }
                                           }" 
                                           x:Name="LabelCommonName" 
                                           Text="{Binding name.common}"/>
                                    <Label Text="{Binding name.scientific}"/>
                                </Grid>
                            </Frame>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </RefreshView>
        </ScrollView>
    </Grid>
</ContentPage>

My code behind
Using an ObservableCollection to add more items when scrolling down.

public partial class CalendarPage : ContentPage
{
    private ActivityIndicator activityIndicator = new ActivityIndicator() { IsRunning = false };
    private ObservableCollection<Calendar> calendars = new ObservableCollection<Calendar>();
    private string url = "[censored]";
    private int start = 0;
    private int limit = 10;

    public CalendarPage()
    {
        InitializeComponent();
        LoadCalendar();
    }

    private void RefreshCalendar(object sender, EventArgs e)
    {
        LoadCalendar();
        CalendarRefresh.IsRefreshing = false;
    }

    private void ScrollCalendar(object sender, ItemsViewScrolledEventArgs e)
    {
        if (e.LastVisibleItemIndex > (start - limit))
        {
            LoadCalendar();
        }
    }

    private async void LoadCalendar()
    {
        if (activityIndicator.IsRunning == true)
        {
            return;
        }
        else
        {
            activityIndicator.IsRunning = true;

            var http = new HttpClient();
            var items = await http.GetFromJsonAsync<List<Calendar>>(url + start + limit);

            foreach (var item in items)
            {
                calendars.Add(item);
            }

            CalendarView.ItemsSource = calendars;

            activityIndicator.IsRunning = false;

            start += limit;
        }
    }
}

Calendar class
Items are collected through an API externally so there's an item template.

namespace projectName;

public class Calendar
{
    public int item_id { get; set; }
    public int status { get; set; }
    public Name name { get; set; }
}

public class Name
{
    public string common { get; set; }
    public string scientific { get; set; }
    public string color { get; set; }
}

Solution

  • You may use a Binding value converters.

    You could define a ColorChangedConverter which inherit IValueConverter:

    public class ColorChangedConverter : IValueConverter
    {
        public ColorChangedConverter()
        {
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
    
            if ((int)value == 1)
            {
                return Colors.Red;
            }
            else if ((int)value == 2)
            {
                return Colors.Blue;
            }
            else
            {
                return Colors.Green;
            }                  
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    And in xaml consume Converters:

    <ContentPage.Resources>
        <local:ColorChangedConverter x:Key="colorChangedConverter" />
    </ContentPage.Resources>
    
    
    <Grid>
        <ScrollView>
            <RefreshView x:Name="CalendarRefresh"                       >
                <CollectionView x:Name="CalendarView">
                    <CollectionView.ItemTemplate>
                        <DataTemplate x:DataType="local:Calendar">
                            <Frame x:Name="myframe">
                                <StackLayout>
                                    <Label x:Name="LabelCommonName"
                                           Text="{Binding name.common}" BackgroundColor="{Binding name.color,Converter={StaticResource colorChangedConverter}}">
                                    </Label>
                                    <Label Text="{Binding name.scientific}"
                                           TextColor="{Binding name.color}"/>
                                    <Label x:Name="mylabel" Text="{Binding item_id}"/>
                                </StackLayout>
                            </Frame>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </RefreshView>
        </ScrollView>
    </Grid>
    

    In code behind, I add some test code:

    private ObservableCollection<Calendar> calendars = new ObservableCollection<Calendar>();
    public MainPage()
    {
        InitializeComponent();
        LoadCalendar();
    }
    
    private async void LoadCalendar()
    {
    
        calendars.Add(new Calendar
        {
            item_id = 1,
            status = 1,
            name = new Name
            {
                common = "common1",
                scientific = "scientific1",
                color = 1
            }
        });
        calendars.Add(new Calendar
        {
            item_id = 2,
            status = 2,
            name = new Name
            {
                common = "common2",
                scientific = "scientific2",
                color = 2
            }
        });
    
        calendars.Add(new Calendar
        {
            item_id = 3,
            status = 3,
            name = new Name
            {
                common = "common3",
                scientific = "scientific3",
                color = 3
            }
        });
    
        CalendarView.ItemsSource = calendars;
    }
    

    The following image shows the result:

    enter image description here

    Hope it works.