Bruno
Bruno

Reputation: 4665

WPF Datagrid - Highlight row on data change

I have this working stub:

XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="False" CanUserAddRows="False" GridLinesVisibility="None" HeadersVisibility="None" BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Visible">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Label>
                            <TextBlock Text="{Binding Id}" />
                        </Label>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Label>
                            <TextBlock Text="{Binding TheData}" />
                        </Label>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Window>

Code-behind

using System.Windows;
using WpfApp1.ViewModels;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataContext = new MainWindowVM();
            InitializeComponent();
        }
    }
}

ViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WpfApp1.ViewModels
{
    public class MainWindowVM
    {
        Random random = new Random();
        public List<ListItemVM> MyList { get; set; } = new List<ListItemVM>
        {
            new ListItemVM(1, "Data1"),
            new ListItemVM(2, "Data2"),
            new ListItemVM(3, "Data3"),
        };

        public MainWindowVM()
        {
            // Start an infinite task that updates the data every 2 second
            // This emulates an external process that sends new data that must be displayed
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Task.Delay(2000).Wait();
                    var nextData = new string(Enumerable.Repeat("ABCDEFG", 10).Select(s => s[random.Next(s.Length)]).ToArray());
                    MyList[1].SetNewData(nextData);
                }
            });
        }
    }
}

Item ViewModel

using System.ComponentModel;

namespace WpfApp1.ViewModels
{
    public class ListItemVM : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public int Id { get; set; }
        public string TheData { get; set; }

        public ListItemVM(int id, string theData)
        {
            Id = id;
            TheData = theData;
        }

        internal void SetNewData(string nextData)
        {
            // Change data
            TheData = nextData;
            // Notify the UI of the change
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TheData)));
        }
    }
}

Every 2 seconds, I see the data update in the UI for the second item in the DataGrid.

The question

I would like the DataGridRow to get highlighted and fade out in 1 second on every update. Can someone help me achieve that please?

Upvotes: 0

Views: 596

Answers (2)

mm8
mm8

Reputation: 169400

You could create an attached behaviour that performs the animation:

public static class Animator { private static readonly HashSet _rows = new HashSet();

    public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(object),
        typeof(Animator), new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));

    public static object GetValue(DataGridRow d) => d.GetValue(ValueProperty);

    public static void SetValue(DataGridRow d, object value) => d.SetValue(ValueProperty, value);

    private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DataGridRow row = (DataGridRow)d;
        if (!_rows.Contains(row))
        {
            _rows.Add(row);
            row.Unloaded += Row_Unloaded;
        }
        else
        {
            ColorAnimation animation = new ColorAnimation();
            animation.From = Colors.Gray;
            animation.To = Colors.White;
            animation.Duration = new Duration(TimeSpan.FromSeconds(1));
            Storyboard.SetTarget(animation, row);
            Storyboard.SetTargetProperty(animation, new PropertyPath("Background.Color"));
            Storyboard sb = new Storyboard();
            sb.Children.Add(animation);
            sb.Begin();
        }
    }

    private static void Row_Unloaded(object sender, RoutedEventArgs e)
    {
        DataGridRow row = (DataGridRow)sender;
        _rows.Remove(row);
        row.Unloaded -= Row_Unloaded;
    }
}

Usage:

<DataGrid.ItemContainerStyle>
    <Style TargetType="DataGridRow">
        <Setter Property="local:Animator.Value" Value="{Binding TheData}" />
    </Style>
</DataGrid.ItemContainerStyle>

Upvotes: 1

Jeremy Crick
Jeremy Crick

Reputation: 108

You'll need to do something like:

  • Set DataGrid.RowStyle to a new Style - probably override the default ControlTemplate (see this answer) to add a background element you can "highlight"
  • Define an appropriate Trigger in Style.Triggers - maybe a DataTrigger based on TheData not being empty
  • Use the trigger to start an animation - see msdn documentation

You might need to do something to defer the application of this style - presumably you don't want all the rows to highlight on initial load. To do this you could look at using a Xaml behavior to do set the RowStyle after Loaded has fired

Upvotes: 0

Related Questions