1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WPF × R3(ReactiveProperty) × MaterialDesignでトグル風RadioButtonを実装する

Posted at

はじめに

WPFにて、RadioButton+R3(ReactiveProperty)+Material Designで、トグル風RadioButtonを作りました。一つのボタンが活性状態になると、他のボタンが非活性状態となるように実装しました。
意外とネット上にRadioButton+ReactivePropertyの組み合わせを使った実装が見つからなかったので、シェアします。

↓完成品
ToggleButtonControl.gif

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>

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?