0
votes

I have a Xamarin Forms app. I am using MVVM. ContentPage has ListView as below. ItemSource is refreshing automatically per 10 seconds from remote rest service. I would like to change Label's TextColor property when old value is different from new value. What is the best XAML approach for that? Thank you in advance

<ListView HasUnevenRows="True" ItemsSource="{Binding Items}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <Grid>
                        <Label
                            Grid.Column="0"
                            Text="{Binding ValueA}"
                            TextColor="Black" />
                        <Label
                            Grid.Column="1"
                            Text="{Binding ValueB}"
                            TextColor="Black" />
                        <Label
                            Grid.Column="2"
                            Text="{Binding ValueC}"
                            TextColor="Black" />
                    </Grid>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
2
you would really need to modify your VM to support this. Very little work would be done in the XAML.Jason

2 Answers

2
votes

EDIT: Oops, I misread, thought you wanted text color change for new items. Updated the below so it will change the item background color if the item is newly added, and will change the text color for a value if it has changed since the last refresh.

You would want to bind the TextColor property for the Label to a boolean value indicating whether the value is changed or not, and you would need to use a ValueConverter to convert the boolean to a color.

Here's a simple example (much of the below code is to simulate adding and updating items in the list, points of interest are the value converters and how to use them to turn a bound bool value into a color):

MainPage.xaml.cs: (includes converters and item view model)

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Timers;
using Xamarin.Forms;

namespace ChangeTextColorForNewItemsInListView
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public ObservableCollection<ItemViewModel> Items { get; set; } = new ObservableCollection<ItemViewModel>();
        Timer _timer; 
        int itemNumber = 0;
        Random randomNumber = new Random(DateTime.Now.Millisecond);


        public MainPage()
        {
            InitializeComponent();
            BindingContext = this;
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();

            for (int i = 0; i < 10; i++)
            {
                Items.Add(new ItemViewModel
                {
                    ValueA = $"ValueA {++itemNumber}",
                    ValueB = $"ValueB {itemNumber}",
                    ValueC = $"ValueC {itemNumber}",
                    IsNew = true
                });
            }

            _timer = new Timer(2000);
            _timer.AutoReset = true;
            _timer.Elapsed += Timer_Elapsed;
            _timer.Start();
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            foreach (var item in Items)
            {
                item.IsNew = false;
                item.ValueA = item.ValueA.Replace(" new", "");
                item.ValueB = item.ValueB.Replace(" new", "");
                item.ValueC = item.ValueC.Replace(" new", "");
                item.ValueAChanged = false;
                item.ValueBChanged = false;
                item.ValueCChanged = false;

                int changeValue = randomNumber.Next(0, 15);
                switch (changeValue)
                {
                    case 0:
                        item.ValueA = item.ValueA.Replace(" new","") + " new";
                        break;
                    case 1:
                        item.ValueB = item.ValueB.Replace(" new", "") + " new";
                        break;
                    case 2:
                        item.ValueC = item.ValueC.Replace(" new", "") + " new";
                        break;
                    default:
                        break;

                }
            }

            for (int i = 0; i < randomNumber.Next(1,5); i++)
            {
                Items.Insert(randomNumber.Next(1, Items.Count -1)  ,new ItemViewModel
                {
                    ValueA = $"ValueA {++itemNumber}",
                    ValueB = $"ValueB {itemNumber}",
                    ValueC = $"ValueC {itemNumber}",
                    IsNew = true
                });
            }       
        }
    }

    public class ItemViewModel : INotifyPropertyChanged
    {
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        bool _isNew;
        public bool IsNew
        {
            get => _isNew;
            set
            {
                if (_isNew != value)
                {
                    _isNew = value;
                    OnPropertyChanged();
                }
            }
        }

        bool _valueAChanged;
        public bool ValueAChanged
        {
            get => _valueAChanged;
            set
            {
                if (_valueAChanged != value)
                {
                    _valueAChanged = value;
                    OnPropertyChanged();
                }
            }
        }
        string _valueA;
        public string ValueA
        {
            get => _valueA;
            set
            {
                if (_valueA != value)
                {
                    _valueA = value;
                    ValueAChanged = true;
                    OnPropertyChanged();
                }
            }
        }

        bool _valueBChanged;
        public bool ValueBChanged
        {
            get => _valueBChanged;
            set
            {
                if (_valueBChanged != value)
                {
                    _valueBChanged = value;
                    OnPropertyChanged();
                }
            }
        }
        string _valueB;
        public string ValueB
        {
            get => _valueB;
            set
            {
                if (_valueB != value)
                {
                    _valueB = value;
                    ValueBChanged = true;
                    OnPropertyChanged();
                }
            }
        }

        bool _valueCChanged;
        public bool ValueCChanged
        {
            get => _valueCChanged;
            set
            {
                if (_valueCChanged != value)
                {
                    _valueCChanged = value;
                    OnPropertyChanged();
                }
            }
        }
        string _valueC;
        public string ValueC
        {
            get => _valueC;
            set
            {
                if (_valueC != value)
                {
                    _valueC = value;
                    ValueCChanged = true;
                    OnPropertyChanged();
                }
            }
        }
    }

    public class BoolToColorConverterNewItem : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? Color.Yellow : Color.White;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException("No need to convert from color to bool");
        }
    }

    public class BoolToColorConverterChangedValue : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? Color.Red : Color.Black;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException("No need to convert from color to bool");
        }
    }
}

MainPage.xaml:

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:local="using:ChangeTextColorForNewItemsInListView"
                mc:Ignorable="d"
                x:Class="ChangeTextColorForNewItemsInListView.MainPage"

                >
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:BoolToColorConverterChangedValue x:Key="boolToColorCV" />
            <local:BoolToColorConverterNewItem x:Key="boolToColorNI" />
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout Margin="0,50,0,0">
        <!-- Place new controls here -->
        <ListView HasUnevenRows="True" ItemsSource="{Binding Items}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid BackgroundColor="{Binding IsNew, Converter={StaticResource boolToColorNI}}">
                            <Label
                                Grid.Column="0"
                                Text="{Binding ValueA}"
                                TextColor="{Binding ValueAChanged, Converter={StaticResource boolToColorCV}}" />
                            <Label
                                Grid.Column="1"
                                Text="{Binding ValueB}"
                                TextColor="{Binding ValueBChanged, Converter={StaticResource boolToColorCV}}" />
                            <Label
                                Grid.Column="2"
                                Text="{Binding ValueC}"
                                TextColor="{Binding ValueCChanged, Converter={StaticResource boolToColorCV}}" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

enter image description here

1
votes

You could bind the value of TextColor

in xaml

<Label
     Grid.Column="1"
     Text="{Binding ValueB}"
     TextColor="{Binding TitleColor}" />

in Model

public class MyModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    Color color;
    public Color TitleColor
    {
        get { return color; }

        set
        {

            if (color != value&& color != null)
            {
                color = value;
                NotifyPropertyChanged("TitleColor");
            }

        }
    }       

    private string valueB;
    public string ValueB
    {
        get { return valueB; }

        set
        {
            if (valueB != value)
            {
                valueB = value;

                TitleColor= Color.Red;  // change color here 

                NotifyPropertyChanged("ValueB");
            }
        }
    }

    //...other property
    
}