はじめに
WPFにて、RadioButton+R3(ReactiveProperty)+Material Designで、トグル風RadioButtonを作りました。一つのボタンが活性状態になると、他のボタンが非活性状態となるように実装しました。
意外とネット上にRadioButton+ReactivePropertyの組み合わせを使った実装が見つからなかったので、シェアします。
ToggleButtonコントロール
Model
R3に加えて、ObservableListも使っています。
以下に記載のように、ObservableCollections.R3をnugetで入れておいて、usingディレクティブでインポートすると使えます。
using R3;
using ObservableCollections;
public class ToggleButtonModel
{
public string Name { get; }
public ObservableList<ToggleButtonOption> ToggleOptions { get; }
public ToggleButtonModel(string name, string[] options)
{
Name = name;
ToggleOptions = new ObservableList<ToggleButtonOption>();
foreach (var option in options)
{
ToggleOptions.Add(new ToggleButtonOption(option));
}
ToggleOptions[0].IsChecked.Value = true; // 初期値として、index:0のものを選択状態にする
// 一つのToggleボタンが活性状態になったら、他のボタンは非活性状態にする
ToggleOptions.ForEach(x => x.IsChecked.Subscribe(isChecked => {
if (isChecked)
{
ToggleOptions.Where(option => option.Label != x.Label).ToList().ForEach(option => option.IsChecked.Value = false);
}
}));
}
}
public class ToggleButtonOption
{
public string Label { get; }
public BindableReactiveProperty<bool> IsChecked { get;}
public ToggleButtonOption(string label)
{
Label = label;
IsChecked = new BindableReactiveProperty<bool>();
IsChecked.Value = false;
}
}
ViewModel
ModelのプロパティをViewに公開している程度の役割です
public class ToggleButtonViewModel
{
private ToggleButtonModel _toggleButtonModel;
public string Name { get; }
public NotifyCollectionChangedSynchronizedViewList<ToggleButtonOption> ToggleOptions { get; }
public ToggleButtonViewModel(string name, string[] options)
{
_toggleButtonModel = new ToggleButtonModel(name, options);
ToggleOptions = _toggleButtonModel.ToggleOptions.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
Name = _toggleButtonModel.Name;
}
}
コントロール
(ToggleButtonControl.xaml)
<UserControl x:Class="WpfApp2.ToggleButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" Height="84" Width="513">
<Border BorderBrush="Gray" BorderThickness="1" Padding="10">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="46.893"/>
<ColumnDefinition Width="53.107"/>
<!-- 名前:固定幅 -->
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="19*"/>
<!-- ボタン:残り全部 -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" FontWeight="Bold" Width="100" Grid.ColumnSpan="2"/>
<WrapPanel Grid.Column="2" Orientation="Horizontal" Grid.ColumnSpan="2" Margin="26,0,0,0" >
<ItemsControl ItemsSource="{Binding ToggleOptions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- 横方向に並べるための WrapPanel -->
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Label}"
Margin="2"
IsChecked="{Binding IsChecked.Value, Mode=TwoWay}"
Style="{StaticResource MaterialDesignChoiceChipPrimaryRadioButton}"
>
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</WrapPanel>
</Grid>
</Border>
</UserControl>
作ったToggleコントロールを使う
MainWindowに組み込みます
public class MainViewModel
{
public NotifyCollectionChangedSynchronizedViewList<ToggleButtonViewModel> Toggles { get; }
public MainViewModel()
{
var tog = new ObservableList<ToggleButtonViewModel>();
tog.Add(new ToggleButtonViewModel("Power", new string[] { "ON", "OFF"}));
tog.Add(new ToggleButtonViewModel("Mode", new string[] { "StateA", "StateB", "StateC" }));
Toggles = tog.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
}
}
(MainWindow.xaml)
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ItemsControl ItemsSource="{Binding Toggles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ToggleButtonControl DataContext="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
(MainWindow.xaml.cs)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
MaterialDesignを使うために、nugetで入れたあとにApp.xamlも編集しました
(App.xaml)
<Application x:Class="WpfApp2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="Indigo" SecondaryColor="Orange" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.RadioButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ToggleButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
