かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

DataGridで特定の列の値が重複してる行だけ色を変えたい

という問題が出されました。 うんうんと頭をひねった結果こうなりました…ちょっと全ループしてるのがダサい。ReactiveProperty 2.x前提です。

まず、表示するデータ。

using Reactive.Bindings;

namespace DupItemColorApp
{
    public class PersonViewModel
    {
        public ReactiveProperty<string> Name { get; private set; }
        public ReactiveProperty<int> Age { get; private set; }

        public ReactiveProperty<string> BackgroundColor { get; private set; }

        public PersonViewModel()
        {
            this.Name = new ReactiveProperty<string>();
            this.Age = new ReactiveProperty<int>();
            this.BackgroundColor = new ReactiveProperty<string>("White");
        }

        public static PersonViewModel Create(string name, int age)
        {
            var result = new PersonViewModel();
            result.Name.Value = name;
            result.Age.Value = age;
            return result;
        }

    }
}

こいつのBackgroundColorをいじってDataGridのRowの色を変えるつもりです。

プロパティの変更と要素の変更時に全走査!ダサい…もっといい方法募集中。一応連続での値の変更なんかに対応するためにThrottle入れて緩和してる。

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;

namespace DupItemColorApp
{
    public class MainWindowViewModel
    {
        private static readonly string[] Names = new[]
        {
            "hoge",
            "fuga",
            "piyo",
            "foo",
            "bar",
            "bazz",
            "fizz",
        };
        
        private readonly ObservableCollection<PersonViewModel> people = new ObservableCollection<PersonViewModel>();
        public ReadOnlyReactiveCollection<PersonViewModel> People { get; private set; }

        public ReactiveCommand AddCommand { get; private set; }

        public MainWindowViewModel()
        {
            this.People = this.people.ToReadOnlyReactiveCollection();
            this.People.ObserveElementObservableProperty(x => x.Name)
                .Throttle(TimeSpan.FromMilliseconds(500))
                .Subscribe(x =>
                {
                    Console.WriteLine("Name changed.");
                    foreach (var item in this.People.ToArray())
                    {
                        item.BackgroundColor.Value = "White";
                    }
                    this.ChangeBackgroundColor();
                });
            this.People.CollectionChangedAsObservable()
                .Throttle(TimeSpan.FromMilliseconds(500))
                .Subscribe(_ => this.ChangeBackgroundColor());

            this.AddCommand = new ReactiveCommand();
            this.AddCommand.Subscribe(_ =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    var name = Names.OrderBy(x => Guid.NewGuid()).First();
                    this.people.Add(PersonViewModel.Create(name, 0));
                }
            });
        }

        private void ChangeBackgroundColor()
        {
            Debug.WriteLine("ChangeBackgroundColor called.");
            foreach (var item in this.People.ToArray().GroupBy(x => x.Name.Value).Where(x => x.Count() > 1).SelectMany(x => x))
            {
                item.BackgroundColor.Value = "Red";
            }
        }
    }
}

んで、XAML。これはバインドしてるだけ。

<Window x:Class="DupItemColorApp.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:DupItemColorApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="Add" Command="{Binding AddCommand}" />
        </Menu>
        
        <DataGrid Grid.Row="1"
                  ItemsSource="{Binding People}"
                  AutoGenerateColumns="False"
                  UseLayoutRounding="True">
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Setter Property="Background" Value="{Binding BackgroundColor.Value}" />
                </Style>
            </DataGrid.RowStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" 
                                    Binding="{Binding Name.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <DataGridTextColumn Header="Age" 
                                    Binding="{Binding Age.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>