ぽにょろん

思いついたこととメモ

WPF / DataGridでデータがNullの場合の表示について

WPFのDataGridでCellにバインドされているデータがNullの場合に、あれこれ表示を切り替えたかったのですが、手間取ったのでメモしておきます。

曲者はこいつでした。

AutoGenerateColumns="True"

目次

実現したいこと

WPF + DataGrid でバインドしているデータがNullの場合に、適当な文字列を表示し、フォント色を変えたい。

何も考えずにDataGridにデータバインドした場合、CellにNullが入ると該当セルは空白表示になります。
それでも問題ない場合は良いのですが、特に空文字とNullを判別したい場合に困ります。

解決策

AutoGenerateColumns="False" の場合

こちらはちょっと検索すれば似たような事例がたくさん出てくると思います。
データがNullの場合に特定の文字を出すには、「TargetNullValue」プロパティに値を入れておきましょう。
文字色を変えるには、ElementStyleにDataTrrigerをセットしておけば解決です。

f:id:kowill:20170606011930p:plain

<DataGrid Width="250" ItemsSource="{Binding View1}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
        <DataGridTextColumn Header="Name" Binding="{Binding Name, TargetNullValue='Nullだよーー'}">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Name}" Value="{x:Null}">
                            <Setter Property="Foreground" Value="Red"></Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

AutoGenerateColumns="True" の場合

最初から表示するデータが固定で決まっている場合は上記の方法で何の問題もないです。
しかし、表示するデータが動的に変わる場合は多いですし、適当なDataTableをポンと渡せばそれなりに表示してくれるのがDataGridの良いところです。*1

AutoGenerateColumnsをTrueにする場合、XAML側で指定する方法はなさそうなので、
動的に解決してあげる必要があります。

今回は、AutoGeneratingColumnイベントを実装したBehaviorで対処する方法をとりました。

public class NullValueBehavior: Behavior<DataGrid>
{
    public NullValueBehavior() { }
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.AutoGeneratingColumn += AssociatedObject_AutoGeneratingColumn;
    }
    private const string NullString = "Nullだよーー";
    private void AssociatedObject_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    {
        var propertyName = e.Column.Header.ToString();
        ((DataGridBoundColumn)e.Column).Binding.TargetNullValue = NullString;

        if (e.Column is DataGridTextColumn column)
        {
            var style = new Style(typeof(TextBlock));
            var trigger = new DataTrigger { Binding = new Binding(propertyName), Value = null };
            trigger.Setters.Add(new Setter(TextBlock.ForegroundProperty, new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Blue)));
            style.Triggers.Add(trigger);
            column.ElementStyle = style;
        }
    }
    protected override void OnDetaching()
    {
        AssociatedObject.AutoGeneratingColumn -= AssociatedObject_AutoGeneratingColumn;
        base.OnDetaching();
    }
}

みたいなのを作っておけばあとは、XAMLにBehavior関連付けるだけ。
f:id:kowill:20170606011934p:plain

<DataGrid Width="250" ItemsSource="{Binding View}" AutoGenerateColumns="True" CanUserAddRows="False" CanUserDeleteRows="False">
    <i:Interaction.Behaviors>
        <local:NullValueBehavior />
    </i:Interaction.Behaviors>
</DataGrid>

最後に

良いやり方はどうかはいまだに自信がありませんが、うまいこと動いてくれています。
WPF難しいですね。ハイ。。

*1:暴論