WPF の RadioButton の IsChecked プロパティに Enum 値を Binding させたいシーンが結構よくあります。地味に使うケースがあるんだけど、記述方法にひと手間あるんでその内容を記録しました。
この記事の内容はもしかすると、過去にも書いていてもおかしくありません……が、検索しても(自分の記事には)出てこなかったので改めてメモすることにしています。
重複していたら悔しいので CommunityToolkit.Mvvm を利用しています。
XAML View
ポイントは、Enum の値を Binding するために Converter を用意している点だけです。
Converter はConverterParameter で Binding の値に相当する文字列を手がかりにして、RadioButton との Binding を成立させます。
ConverterParameter の文字列を記入する部分は、インテリセンスの効かない範囲なので、入力ミスをするとわかりづらいバグに繋がる恐れがあります。nameof()
みたいなことができるといいんだけど xaml は、かゆいところがあると思います。
<Window x:Class="RadioEnumBindingTest.MainWindow" 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:local="clr-namespace:RadioEnumBindingTest" mc:Ignorable="d" d:DataContext="{d:DesignInstance local:MainWindowViewModel}" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <local:MainWindowViewModel x:Name="ViewModel" /> </Window.DataContext> <Window.Resources> <local:BoolToEnumConverter x:Key="BoolToEnumConverter" /> </Window.Resources> <StackPanel Orientation="Vertical"> <RadioButton Margin="10" GroupName="test" IsChecked="{Binding Sample, ConverterParameter=A, Converter={StaticResource BoolToEnumConverter}}" Content="A radio" /> <RadioButton Margin="10" GroupName="test" IsChecked="{Binding Sample, ConverterParameter=B, Converter={StaticResource BoolToEnumConverter}}" Content="B radio" /> <RadioButton Margin="10" GroupName="test" IsChecked="{Binding Sample, ConverterParameter=C, Converter={StaticResource BoolToEnumConverter}}" Content="C radio" /> <TextBlock Margin="10" Text="{Binding Text, StringFormat={}selected radio: {0}}" /> </StackPanel> </Window>
ViewModel
ViewModel は素直に自分で binding プロパティを用意してもいいけど、めんどくさいので MVVM Toolkit を使って用意します。
MVVM ToolKit は(動的な)ソースジェネレータってすごいっていうのを実感できるので、一度は使ってみるといいと思います。
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace RadioEnumBindingTest; public partial class MainWindowViewModel : ObservableObject { [ObservableProperty] [NotifyPropertyChangedFor(nameof(Text))] private SampleEnum _sample = SampleEnum.A; public string Text => $"{Sample}."; /// <summary> /// <see cref="MainWindowViewModel"/> クラスの新しいインスタンスを初期化します。 /// </summary> public MainWindowViewModel() { } }
NotifyPropertyChangedFor
は、わかりやすい通知のサンプルになっていると思います。
このほかのやり方として、オーバーロードを実装して Sample
が変更されたとき/される前に Text を更新する、という方法もとれると思います。こっちのほうが oldvalue/newvalue を引数に持てたりもするから、通知ロジックとして強力。
partical void OnSampleChanged(SampleEnum value) { Console.WriteLine($"Enum value is about to changed to {value}."); }
Converter
IValueConverter は今更といった感じですが。Enum の比較方法はいろいろなやり方があると思います。手を抜くなら、文字列にして比較するだけでも(大体は)いいし。
わかりやすく比較していくと、下例のようになりました。
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; namespace RadioEnumBindingTest; public class BoolToEnumConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null || parameter == null) { return DependencyProperty.UnsetValue; } if (Enum.IsDefined(value.GetType(), value) == false) { return DependencyProperty.UnsetValue; } var isResult = false; var canParse = Enum.TryParse(value.GetType(), parameter.ToString(), out object? result); if (canParse && result != null) { isResult = (int)result == (int)value; } else { isResult = false; } return isResult; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { var parameterText = parameter?.ToString() ?? ""; if (parameterText == "") { return DependencyProperty.UnsetValue; } return Enum.Parse(targetType, parameterText); } }
サンプル
今回のコードを公開しておきます。
参考
なし