David Shochet
David Shochet

Reputation: 5395

How to change background color of a CollectionView item based on a ViewModel property?

I have a .Net MAUI app that uses CommunityToolkit.Mvvm. I have a CollectionView of buttons bound to a list of DateTime. On pressing any button a command is called that sets SelectedTime property of the ViewModel to a corresponding value.

Here is some ViewModel code:

    [ObservableProperty]
    [Required(ErrorMessage = "Please select the time of your appointment.")]
    private DateTime? _selectedTime;

    public List<DateTime> Times { get; set; } = new ()
        { new DateTime(2000, 1, 1, 9, 0, 0), new DateTime(2000, 1, 1, 9, 30, 0), new DateTime(2000, 1, 1, 10, 0, 0), new DateTime(2000, 1, 1, 10, 30, 0),
          new DateTime(2000, 1, 1, 11, 0, 0), new DateTime(2000, 1, 1, 11, 30, 0), new DateTime(2000, 1, 1, 12, 0, 0), new DateTime(2000, 1, 1, 12, 30, 0),
          new DateTime(2000, 1, 1, 13, 0, 0), new DateTime(2000, 1, 1, 13, 30, 0), new DateTime(2000, 1, 1, 14, 0, 0), new DateTime(2000, 1, 1, 14, 30, 0),
          new DateTime(2000, 1, 1, 15, 0, 0), new DateTime(2000, 1, 1, 15, 30, 0), new DateTime(2000, 1, 1, 16, 0, 0), new DateTime(2000, 1, 1, 16, 30, 0) };

    [RelayCommand]
    private void SelectTime(object parameter)
    {
        SelectedTime = (DateTime)parameter;
    }

Here is my Page code:

                <CollectionView ItemsSource="{Binding Times}" ItemsLayout="VerticalGrid, 3" Margin="5">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <ContentView Padding="5">
                                <Button Text="{Binding ., StringFormat='{0:hh:mm tt}'}"
                                    Command="{Binding BindingContext.SelectTimeCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
                                    CommandParameter="{Binding .}">
                                </Button>
                            </ContentView>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>

The collection of buttons is displayes on the screen, but I cannot figure out how I can make a pressed button change its color, and restore it back when another button is pressed? I guess the color should be bound to the SelectedTime property, but cannot make it work.

Upvotes: 1

Views: 1438

Answers (2)

Peter Wessberg
Peter Wessberg

Reputation: 1931

I understand you want each button to change color but is it not the complete row you want to change? I can't ask questions due to low rep. So my answer is to ditch the buttons and go with the SelectionChangedCommand, and the whole row will lit up when you click on it.

    <CollectionView
        x:Name="CollectionAppointment"
        Margin="5"
        ItemSizingStrategy="MeasureAllItems"
        ItemsSource="{Binding Times, Mode=OneTime}"
        SelectedItem="SelectedItem"
        SelectionChangedCommand="{Binding SelectTimeCommand}"
        SelectionChangedCommandParameter="{Binding SelectedItem, Source={RelativeSource Self}}"
        SelectionMode="Single">
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical" Span="3" />
        </CollectionView.ItemsLayout>
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="viewmodels:Appointment">
                <ContentView Padding="5">
                    <Label
                        BackgroundColor="Transparent"
                        Text="{Binding Time, StringFormat='{0:hh:mm tt}'}"
                        TextColor="Black" />
                </ContentView>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>

The ViewModel for clearification:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

namespace MauiTest.ViewModels
{
    public partial class TestViewModel : ObservableObject
    {
        [ObservableProperty]
        private Appointment _selectedItem;

        [ObservableProperty]
        public ObservableCollection<Appointment> _times;
        public TestViewModel()
        {
            Times = new()
            {
                new Appointment{ Time = new DateTime(2000, 1, 1, 9, 0, 0)}, new Appointment{ Time = new DateTime(2000, 1, 1, 9, 30, 0)},
                new Appointment{ Time = new DateTime(2000, 1, 1, 10, 0, 0)},
                new Appointment{ Time = new DateTime(2000, 1, 1, 10, 30, 0)}
            };
        }


        [RelayCommand]
        private void SelectTime(Appointment parameter)
        {


        }
    }

    public class Appointment
    {
        public DateTime Time { get; set; }
    }

}

enter image description here

Upvotes: 1

Jessie Zhang -MSFT
Jessie Zhang -MSFT

Reputation: 13919

You can create a model for your each bound item and add a property (e.g.BgColor) to set the background of the button, and implement interface INotifyPropertyChanged for the model. Once you click the button, try to change the value of the property BgColor, then the UI will refresh itself.

Based on your code, I created a demo and achieved this function.

You can refer to the following code:

1.create a class Item and add the properties we need(Time and BgColor).

public  class Item: INotifyPropertyChanged 
    {  
       // add a property Time
        public DateTime Time { get; set; }

       // add background color property
        private Color _bgColor;
        public Color BgColor
        {
            set { SetProperty(ref _bgColor, value); }
            get { return _bgColor; }
        }

        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

2.create a viewmodel and initialize data

public partial class MyViewModel 
{

    public List<Item> Items { get; set; }

    public ICommand SelectTimeCommand => new Command(selectedTime);

    private void selectedTime(object obj)
    {
       if (obj != null )
        {
            Item item = obj is Item ? (Item)obj : null;
            item.BgColor = Colors.Purple;

            foreach (Item model in Items)
            {
                //here we can change the value of the passed Item
                if (model != item)
                {
                    model.BgColor = Colors.Blue;
                }

            }
        }
    }

    public MyViewModel() {

        Items = new List<Item>();
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 9, 0, 0),BgColor= Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 10, 0, 0), BgColor = Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 11, 0, 0), BgColor = Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 12, 0, 0), BgColor = Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 13, 0, 0), BgColor = Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 14, 0, 0), BgColor = Colors.Blue });
        Items.Add(new Item {  Time = new DateTime(2000, 1, 1, 15, 0, 0), BgColor = Colors.Blue });
    }

}

3.bind the BgColor on the yourpage.xaml:

<?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"
             x:Class="MauiCollectionViewApp.TestPage"
             Title="TestPage"
             xmlns:viewmodels="clr-namespace:MauiCollectionViewApp.ViewModels"
             >

    <ContentPage.BindingContext>
        <viewmodels:MyViewModel></viewmodels:MyViewModel>
    </ContentPage.BindingContext>
    <VerticalStackLayout>    
        <CollectionView ItemsSource="{Binding Items}" ItemsLayout="VerticalGrid, 3" Margin="5"
                        
                        >
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <ContentView Padding="5">
                      
                        <Button Text="{Binding Time, StringFormat='{0:hh:mm tt}'}"  BackgroundColor="{Binding BgColor}"
                                    Command="{Binding BindingContext.SelectTimeCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
                                    CommandParameter="{Binding .}">
                        </Button>

     
                    </ContentView>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

Upvotes: 1

Related Questions