[XAML] WPF で IntelliSense による入力補完やリファクタ機能を可能にする記述方法

更新: 2024-01-22 (月) 投稿: 2023-03-27 (月)

バインドするプロパティをタイプセーフに記述します。WPF で ReSharper (Rider) を使用する想定です。
バインドのプロパティ名に誤りがあれば、IDE 上で警告されます。
定義へ移動(Go to Declaration)、すべての参照を検索(Find Usages)、リファクタによる名前変更(Rename)などの機能もフルに利用可能です。

環境

  • .NET 7.0.200
  • C# 11.0
  • Visual Studio 2022 Version 17.5.0
  • ReSharper 2022.3.3
  • Windows 10 Pro 64bit 22H2 19045.2728

前提

ReSharper (Rider) を使用する環境で動作確認しています。

本記事で示すコードを利用するには、xmlns の指定が必要なケースがあります。
ビルドエラーになる場合は、xmlns の追加と mc:Ignorable="d" も設定してください。

<ResourceDictionary
    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"
    mc:Ignorable="d">

実装

d:DataContext で d:DesignInstance を指定する

d:DataContext="{d:DesignInstance viewModels:SampleViewModel}" のように指定します。

<UserControl x:Class="SampleApp.SampleView"
             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:viewModels="clr-namespace:SampleApp.SampleModels"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance viewModels:SampleViewModel}">
</UserControl>

Style で d:Style.DataContext を指定する

<d:Style.DataContext><x:Type> を指定します。

<Style x:Key="SampleKey" TargetType="TextBlock">
    <d:Style.DataContext>
        <x:Type Type="models:SampleModel" />
    </d:Style.DataContext>
    <Setter Property="Text" Value="{Binding SampleProperty}" />
</Style>

d:Style.DataContext="{d:DesignInstance models:SampleModel}" を使用して、1行で指定することもできます。
ただし、指定するクラスに引数なしのコンストラクタが必要です。

<Style x:Key="SampleKey" TargetType="TextBlock" d:Style.DataContext="{d:DesignInstance models:SampleModel}">
    <Setter Property="Text" Value="{Binding SampleProperty}" />
</Style>

<d:Style.DataContext> を使用せずに、Path=() 形式でも指定できます。

<Style x:Key="SampleKey" TargetType="TextBlock">
    <Setter Property="Text" Value="{Binding Path=(models:SampleModel.SampleProperty)}" />
    <Setter Property="ToolTip" Value="{Binding Path=(models:SampleModel.SampleProperty)}" />
</Style>

ただし、d:Style.DataContext と違いがあります。

  • Binding 毎に指定する必要があります。
  • 実行時に SampleModel 型以外がバインドされていると動作しません。
    • d:Style.DataContext の場合は、IntelliSense のみに影響します。
    • d:Style.DataContext では、実行時にバインドされた型に SampleProperty が定義されていれば、SampleModel 型以外でも動作します。

DataTemplate で DataType を指定する

Resources の配下などに定義する場合は、DataType を指定します。

<Application>
    <Application.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="SampleKey" DataType="models:SampleModel">
                <TextBlock Text="{Binding SampleProperty}" />
            </DataTemplate>
        </ResourceDictionary>
    </Application.Resources>
</Application>
<UserControl>
    <UserControl.Resources>
        <Style x:Key="SampleKey" TargetType="ComboBox">
            <Setter Property="ItemTemplate">
                <Setter.Value>
                    <DataTemplate DataType="models:SampleModel">
                        <TextBlock Text="{Binding SampleProperty}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>
</Application>

なお、ItemsControl.ItemsSource をバインドしている場合、DataTemplateDataType を指定しなくても IntelliSense が効きます。
例えば、DataGridTemplateColumn.CellTemplateItemsControl.ItemTemplate などです。

<DataGrid ItemsSource="{Binding Items}">
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <!-- ItemsSource で指定しているため、DataTemplate の DataType を指定しなくて良い -->
                <DataTemplate>
                    <TextBlock Text="{Binding SampleProperty}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <!-- ItemsSource で指定しているため、DataTemplate の DataType を指定しなくて良い -->
        <DataTemplate>
            <TextBlock Text="{Binding SampleProperty}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

string へのバインドは nameof を使用する

プロパティ名を取得するためのマークアップ拡張を作成します。

using System;
using System.Windows.Data;
using System.Windows.Markup;

[MarkupExtensionReturnType(typeof(string))]
public sealed class NameOfExtension : MarkupExtension
{
    private readonly string _path;

    public NameOfExtension(Binding binding)
    {
        _path = binding.Path.Path;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var span = _path.AsSpan().TrimEnd(')');

        if (span.LastIndexOf('.') is var index and >= 0)
        {
            span = span.Slice(index + 1);
        }

        return span.Length == _path.Length ? _path : span.ToString();
    }
}

使用例1: 文字列指定の DataGridTemplateColumn SortMemberPath にバインドする場合は、{markup:NameOf {Binding Items[0].SampleProperty}} のように指定します。

<DataGrid ItemsSource="{Binding Items}">
    <DataGrid.Columns>
        <DataGridTemplateColumn SortMemberPath="{markup:NameOf {Binding Items[0].SampleProperty}}">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding SampleProperty}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

使用例2: ComboBoxSelectedValuePath にバインドする場合は、{markup:NameOf {Binding (ComboBoxItem.Tag)}} のように指定します。

<ComboBox SelectedValuePath="{Binding Path=(ComboBoxItem.Tag)}">
    <ComboBoxItem Content="日曜日" Tag="{x:Static system:DayOfWeek.Sunday}" />
    <ComboBoxItem Content="月曜日" Tag="{x:Static system:DayOfWeek.Monday}" />
</ComboBox>

<ComboBox ItemsSource="{Binding Items}"
          SelectedValuePath="{markup:NameOf {Binding (models:SampleModel.Id)}}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding FirstName}" />
                <TextBlock Text="{Binding LastName}" />
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

BindingProxy は明示的な型を指定する

DataGridColumnViewModel のプロパティをバインドしたい場合、BindingProxy を使用することがあります。

BindingProxy.Data プロパティが object 型だと IntelliSense が効かないため、明示的な型を指定します。
ジェネリックな BindingProxy<T> を作成します。

public class BindingProxy<T> : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy<T>();
    }

    public T Data
    {
        get => (T)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
        nameof(Data),
        typeof(T),
        typeof(BindingProxy<T>),
        new UIPropertyMetadata(null));
}

実際に使用する箇所に合わせて、<T> に明示的な型を指定して、継承したクラスを作成します。

public class MainViewModelBindingProxy : BindingProxy<MainViewModel>
{
}

例えば、UserControl.DataContext を参照したい場合は、<UserControl.Resources>MainViewModelBindingProxy を定義します。

<UserControl.Resources>
    <local:MainViewModelBindingProxy x:Key="Proxy.MainViewModel" Value="{Binding}" />
</UserControl.Resources>

BindingProxy.Data を参照するには、{Binding Source={StaticResource Proxy.MainViewModel}, Path=Data.Names} のように使用します。
Data プロパティが明示的な型として IntelliSense が効きます。

<DataGridComboBoxColumn Binding="{Binding Name}"
                        ItemsSource="{Binding Source={StaticResource Proxy.MainViewModel}, Path=Data.Names}"
                        SelectedItemBinding="{Binding SelectedName}" />

感謝

更新履歴

  • 2024/01/27
    • アンカーリンクの URL を日本語から英数に変更
  • 2024/01/22
    • d:Style.DataContext に引数なしコンストラクタが必要な旨を追記
  • 2023/12/22
    • 前提を追加 (ReSharper, Rider と xmlns の指定について)
    • NameOfExtension クラスを sealed にして、MarkupExtensionReturnType 属性を追加
  • 2022/09/28
    • d:Style.DataContext="{d:DesignInstance models:SampleModel}" による指定方法を記載
更新: 2024-01-22 (月) 投稿: 2023-03-27 (月)