[XAML] WPF で IntelliSense による入力補完やリファクタ機能を可能にする記述方法
バインドするプロパティをタイプセーフに記述します。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
をバインドしている場合、DataTemplate
の DataType
を指定しなくても IntelliSense が効きます。
例えば、DataGridTemplateColumn.CellTemplate
、ItemsControl.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: ComboBox
の SelectedValuePath
にバインドする場合は、{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 は明示的な型を指定する
DataGridColumn
に ViewModel
のプロパティをバインドしたい場合、BindingProxy
を使用することがあります。
- [WPF] MVVMパターンにおいて、DataGridTextColumnがDataContextを参照する方法 - Qiita
- DataGridColumnにデータバインドするには - やる気駆動型エンジニアの備忘録
- [WPF] How to bind to data when the DataContext is not inherited - Thomas Levesque’s .NET Blog
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}" />
感謝
- d:DataContext
- BindingProxy
- d:Style.DataContext
- 公式
更新履歴
- 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}"
による指定方法を記載
関連記事
新着記事