かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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>