[XAML] WPF で Resources に Binding を定義して、共通化して使用する方法
通常は Style の Setter を使用することで、共通のバインドを定義することができますが、別々の Style を適用したい場合などは別の方法が必要です。
ResourceDictionary に x:Key を指定して Binding を定義しても、StaticResource から参照することができません。
Markup を実装することで、共通の Binding を1回だけ定義して、共有して使用する方法です。
環境
- .NET 8.0.100
- C# 12.0
- Visual Studio 2022 Version 17.8.5
- Windows 11 Pro 22H2 22621.3007
前提
本記事の実装方法は、XAML の IntelliSense が完全に機能し、タイプセーフなコードになる実装方法にしています。
コードの記述量や性能よりも、タイプセーフを重視しています。
タイプセーフな XAML については、以下の記事を参照してください。
[XAML] WPF で IntelliSense による入力補完やリファクタ機能を可能にする記述方法
バインドするプロパティをタイプセーフに記述します。WPF で ReSharper (Rider) を使用する想定です。
バインドのプロパティ名に誤りがあれば、IDE 上で警告されます。
定義へ移動(Go to Declaration)、すべての参照を検索(Find Usages)、リファクタによる名前変更(Rename)などの機能もフルに利用可能です。
結果
使用箇所のコードを抜粋して示します。詳しいコードは後述の使用例を参照してください。
まずバインドするために、独自のマークアップ拡張を実装します。
using System;
using System.Diagnostics;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApp.Samples;
public sealed class BindingResource
{
public Binding Binding { get; set; } = null!;
}
[MarkupExtensionReturnType(typeof(object))]
public sealed class BindingResourceExtension : MarkupExtension
{
public BindingResource Resource { get; set; } = null!;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Resource.Binding.ProvideValue(serviceProvider);
}
}
BindingResource
を使用して XAML の Resoures に共通の Binding を定義します。
これを BindingResourceExtension
マークアップで参照します。
<Window.Resources>
<!-- 1. ここで共通の Binding を定義します -->
<samples:BindingResource x:Key="Key.Sample" Binding="{Binding IsSampleBool, Mode=OneWay}" />
</Window.Resources>
<!-- 2. ここで共通の Binding を参照します -->
<TextBox Text="1" IsEnabled="{samples:BindingResource Resource={StaticResource Key.Sample}}" />
DataGridColumn で使用する場合は、BindingProxy を使用します。
💡 Proxy を参照する際は、Mode=OneTime
ではなく Mode=OneWay
でバインドする必要があります。
<Window.Resources>
<!-- 1. ViewModel をバインドする Proxy を定義します -->
<samples:SampleViewModelBindingProxy x:Key="Proxy.ViewModel" Value="{Binding}" />
<!-- 2. DataGridColumn で使用するため Source に Proxy 参照します (OneTime ではなく OneWay にします) -->
<markup:BindingResource x:Key="Key.Sample" Binding="{Binding Source={StaticResource Proxy.ViewModel}, Path=Value.IsSampleBool, Mode=OneWay}" />
</Window.Resources>
<!-- 3. ここで共通の Binding を参照します -->
<DataGridTextColumn Header="1" Width="100" IsReadOnly="{markup:BindingResource Resource={StaticResource Key.Sample}}" />
使用例1. 通常のコントロールにバインドする例
Window と ViewModel クラス例です。
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp.Samples
{
public sealed partial class BindingResourceWindow
{
public BindingResourceWindow()
{
InitializeComponent();
}
}
public sealed class BindingResourceViewModel : INotifyPropertyChanged
{
// この bool がバインド用のプロパティです
public bool IsSampleBool { get => _isSampleBool; set => SetProperty(ref _isSampleBool, value); }
private bool _isSampleBool=true;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
XAML 側の使用例です。
<Window x:Class="WpfApp.Samples.BindingResourceWindow"
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"
xmlns:samples="clr-namespace:WpfApp.Samples"
Title="BindingResourceWindow"
Width="800"
Height="450"
d:DataContext="{d:DesignInstance {x:Type samples:BindingResourceViewModel}}">
<Window.DataContext>
<samples:BindingResourceViewModel />
</Window.DataContext>
<Window.Resources>
<!-- 1. ここで共通の Binding を定義します -->
<samples:BindingResource x:Key="Key.Sample" Binding="{Binding IsSampleBool, Mode=OneWay}" />
</Window.Resources>
<StackPanel>
<CheckBox IsChecked="{Binding IsSampleBool, Mode=TwoWay}" />
<!-- 2. ここで共通の Binding を参照します -->
<TextBox Text="1" IsEnabled="{samples:BindingResource Resource={StaticResource Key.Sample}}" />
<TextBox Text="2" IsEnabled="{samples:BindingResource Resource={StaticResource Key.Sample}}" />
<TextBox Text="3" IsEnabled="{samples:BindingResource Resource={StaticResource Key.Sample}}" />
</StackPanel>
</Window>
通常は Style の Setter を使用することで、共通のバインドを定義することができます。
しかし、3 個の TextBox に別々の Style を適用したい場合は、この方法が有効と思います。
使用例2. DataGridColumn にバインドする例
使用例1 のクラスを使用します。追加で Proxy クラスを定義します。
BindingProxy については、こちら を参照してください。
public sealed class SampleViewModelBindingProxy : BindingProxy<BindingResourceViewModel>;
XAML 側の使用例です。
<Window x:Class="WpfApp.Samples.BindingResourceWindow"
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"
xmlns:samples="clr-namespace:WpfApp.Samples"
Title="BindingResourceWindow"
Width="800"
Height="450"
d:DataContext="{d:DesignInstance {x:Type samples:BindingResourceViewModel}}">
<Window.DataContext>
<samples:BindingResourceViewModel />
</Window.DataContext>
<Window.Resources>
<!-- 1. ViewModel をバインドする Proxy を定義します -->
<samples:SampleViewModelBindingProxy x:Key="Proxy.ViewModel" Value="{Binding}" />
<!-- 2. DataGridColumn で使用するため Source に Proxy 参照します (OneTime ではなく OneWay にします) -->
<markup:BindingResource x:Key="Key.Sample" Binding="{Binding Source={StaticResource Proxy.ViewModel}, Path=Value.IsSampleBool, Mode=OneWay}" />
</Window.Resources>
<DataGrid>
<DataGrid.Columns>
<!-- 3. ここで共通の Binding を参照します -->
<DataGridTextColumn Header="1" Width="100" IsReadOnly="{markup:BindingResource Resource={StaticResource Key.Sample}}" />
<DataGridTextColumn Header="2" Width="100" IsReadOnly="{markup:BindingResource Resource={StaticResource Key.Sample}}" />
<DataGridTextColumn Header="3" Width="100" IsReadOnly="{markup:BindingResource Resource={StaticResource Key.Sample}}" />
<!-- 本来ここで Binding を個別に定義する場合は、OneTime でも動作します -->
<DataGridTextColumn Header="4" Width="100" IsReadOnly="{Binding Source={StaticResource Proxy.ViewModel}, Path=Value.IsSampleBool, Mode=OneTime}" />
</DataGrid.Columns>
</DataGrid>
</Window>
感謝
関連記事
新着記事