OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

WPF DataGrid.RowHeight指定のいろいろ

WPF DataGridのレイアウト設定はとかく複雑でわやよ。

今回は行の高さ指定に関する話。
DataGridには行の高さを指定できるプロパティがいくつもあるので
その適用の優先順位とか知っておかないと、
設定してるのに変わってくれないぞ??ってハマることになる。

<DataGrid RowHeight="20"><!-- (A) -->  
    <DataGrid.RowStyle>  
        <Style TargetType="DataGridRow">  
            <!-- (B) -->  
            <Setter Property="Height" Value="30" />  
        </Style>  
    </DataGrid.RowStyle>  
    <DataGrid.CellStyle>  
        <Style TargetType="DataGridCell">  
            <!-- (C1) -->  
            <Setter Property="Height" Value="40" />  
        </Style>  
    </DataGrid.CellStyle>  

    <DataGrid.Columns>  
        <DataGridTextColumn Header="列1">  
            <DataGridTextColumn.CellStyle>  
                <Style TargetType="DataGridCell">  
                    <!-- (C2) -->  
                    <Setter Property="Height" Value="50" />  
                </Style>  
            </DataGridTextColumn.CellStyle>  
        </DataGridTextColumn>  
    </DataGrid.Columns>  
</DataGrid>  

(A) DataGrid.RowHeightプロパティ
(B) DataGridRow.Heightプロパティ
(C1) DataGridCell.Heightプロパティ(DataGrid.CellStyleで指定)
(C2) DataGridCell.Heightプロパティ(DataGridColumn.CellStyleで指定)

以下、検証環境は.NET Framework 4.5.2 & Windows 8.1。

まずAとBの優先順について、MSDN「DataGrid.RowHeightプロパティ」には

RowHeight プロパティは、Height プロパティが設定されていない各 DataGridRow に適用されます

とあって、単純にAよりBを優先して採用しますって話に読めるんだけど、
そういう動きにはなっていない。

実際上記コードのようにAに20、Bに30を指定して(C1,C2は削除して)表示してみると
各行のスペースは30(Bの分)あるが、コンテンツ表示領域は20(Aの分)しか無いような
見え方(↓)になる。

Aに20,Bに30を指定したDataGrid

逆にBを20、Aを30にしてやると、行幅は20しかないが、コンテンツ自体は
30の高さを持っているらしく、行間の罫線が見えなくなる。

この見え方から推測するに、DataGridRow.Height(B)で行の外枠を作ってから、
中に表示するコンテンツ(DataGridTextColumnならTextBlockだ)の配置領域を
DataGrid.RowHeight(A)で決めてるんじゃないかなと。

なので、DataGrid.RowHeightをいくら拡張しても
DataGridRow.Heightの値を大きくしないと表示領域は広くならない。

表示内容にあわせて自動で行高が拡張されるようにしたい場合は
DataGridRow.HeightとDataGrid.RowHeightの両方をAuto(既定値)に設定する必要がある。

ついでにいうと、RowHeightプロパティについて、MSDNには

コンテンツに合わせて行のサイズを自動調整するには、このプロパティを NaN (XAML では "Auto") に設定します

とあるが、これも嘘だ。

Aのとこに「RowHeight="Auto"」と書いてみると、ビルドエラーになる(B,Cは可能)。
RowHeightに明示的にAutoを指定したいときは、XAMLから書く時もDouble.NaNで書く必要がある。

<Window x:Class="DataGridRowHeightSample.MainWindow"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:sys="clr-namespace:System;assembly=mscorlib">  

    <DataGrid RowHeight="{x:Static sys:Double.NaN}">  
        ...  
    </DataGrid>  
</Window>  

System名前空間の定数は↑こんなふうに指定しますよ。
なかなかこういうのが書けそうで書けない、実は書けるのがXAML。

C1とC2の優先順については、C2が設定されている列についてはC2が優先される。
これはまあ違和感ないと思う。

A,BとCの関係については、まず、A,Bによって行自体の高さが決められてから、
その中で各列に配置されるセルの高さがCによって規定されるイメージっぽい。
Cにいくら大きな値を設定しても、A,Bで決められた値以上に
表示領域が拡張されることはない。

あんまり使い道ないと思うが、C2を使えば列ごとにセルの高さを変えることもできる。
先の例のようにAに20、Bに30を指定した状態で、
列2にだけDataGridCell.Height="10"を指定してやると↓こんな感じ。

Aに20,Bに30,列2のC2に10を指定したDataGrid

あともちろん、表示内容にあわせて行高を自動拡張させる場合は
DataGridCell.HeightもAuto(既定値)にする必要がありますよと。

以上をふまえて、まとめ。

A:DataGrid.RowHeightをB:DataGridRow.Heightで上書きしたり、
B:DataGridRow.HeightをC:DataGridCell.Heightで上書きすることはできない。

なので、基本的にA,B,Cを併用するケースなんて無いと思ってよい。
行の高さを指定したいときは、AかBかCか、どれか1つだけ指定して、
あとは既定値Autoのままにしておくべし。

「他は行高20なんだけど、このDataGridだけは行高30にしたい」
というような場合は、BasedOnでStyleを継承するか、
Styleよりも直接プロパティ設定が優先されるというルールを利用すべし。