◆ DataGrid のプレースホルダのオブジェクトは internal
◆ Trigger でイコールか比較したいのでリフレクションで static 変数に入れておく

DataGrid では一番下にプレースホルダというのがあります

wpfsmp12262


ここをクリックして次の行を作って入力します

プレースホルダなのに表示される

DataGridTextColumn や DataGridCheckBoxColumn などでは気にすることはないのですが DataGridTemplateColumn のときには気をつけないといけないです

テンプレートはプレースホルダにも適用されてしまいます
そのため ボタンを入れると プレースホルダにもボタンが出てしまいます
よくあるのは行の削除ボタンを作るときかと思います

<StackPanel>
    <DataGrid ItemsSource="{Binding items}" AutoGenerateColumns="False" HeadersVisibility="Column">
        <DataGrid.Columns>
            <DataGridTextColumn Header="列1" Binding="{Binding column1}" />
            <DataGridTextColumn Header="列2" Binding="{Binding column2}" />
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Command="Delete">×</Button>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    <Button>フォーカス外す用ボタン</Button>
</StackPanel>

一応いれる C# 側も
public class BindingData
{
    public List<Row> items { get; set; } = new List<Row>
    {
        new Row { column1 = "1", column2 = "2" },
        new Row { column1 = "11", column2 = "22" },
    };
}

public class Row
{
    public string column1 { get; set; }
    public string column2 { get; set; }
}

public MainWindow()
{
    InitializeComponent();
    this.DataContext = new BindingData() {};
}

このコードだとこうなります

wpfsmp12261


この一番下のプレースホルダではボタンを消したいです

プレースホルダを判定する

XAML なので Trigger でプレースホルダかどうかを判定できればおっけいです

行ごとに処理されるので この例だと通常は Row のインスタンスが入ってるところにプレースホルダのときは何が入ってるのかをデバッグ実行で調べてみると NewItemPlaceholder というものが入っていました

名前で調べてみると CollectionView.NewItemPlaceholder というのがありました
この値とイコールなら 非表示にする Trigger を作ってみたのですが 非表示になりません

もう少しちゃんと調べてみると DataGrid のプレースホルダは DataGrid.NewItemPlaceholder でした
こっちは internal なので普通にアクセスすることはできません

https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/DataGrid.cs,eca4a34baa0f9921


プレースホルダかどうかの判定したいことはあるのに internal にされているのはちょっと困ります

Converter を使って 行が目的の型(Row)と≠かとか 型名が "NewItemPlaceholder" と一緒ならプレースホルダとみなすとかできなくはないのですが コード量がムダに増えますし 目的の型と比べる方法では型が変わるごとに変えないといけないのも不便です

型名を使うためにリフレクションするのならいっそ internal のをもってくればいっか ということでこういうヘルパクラスを作りました
public class DataGridHelper
{
    static public readonly object NewItemPlaceholder =
        typeof(System.Windows.Controls.DataGrid)
        .GetProperty("NewItemPlaceholder", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
        .GetValue(null);
}

DataGridHelper.NewItemPlaceholder で internal になっているプレースホルダのオブジェクトが取得できます

XAML の方は単純にイコールチェックの Trigger でいいのでシンプルに
<DataTemplate.Triggers>
    <DataTrigger Binding="{Binding}" Value="{x:Static local:DataGridHelper.NewItemPlaceholder}">
        <Setter Property="Visibility" Value="Collapsed"/>
    </DataTrigger>
</DataTemplate.Triggers>

を追加するだけです

全体はこうなります
<StackPanel>
    <DataGrid ItemsSource="{Binding items}" AutoGenerateColumns="False" HeadersVisibility="Column">
        <DataGrid.Columns>
            <DataGridTextColumn Header="列1" Binding="{Binding column1}" />
            <DataGridTextColumn Header="列2" Binding="{Binding column2}" />
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding}" Value="{x:Static local:DataGridHelper.NewItemPlaceholder}">
                                <Setter Property="Visibility" Value="Collapsed"/>
                            </DataTrigger>
                        </DataTemplate.Triggers>
                        <Button Command="Delete">×</Button>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    <Button>フォーカス外す用ボタン</Button>
</StackPanel>

wpfsmp12263

一番下の行では×ボタンが消えていますね