C#のRadioButtonを試してみた

C#のWPFで、ラジオボタン(Radio Button)を試してみました。@ITのコード例を参考にしました。

目次

  1. ラジオボタン
  2. ラジオボタンで選んでもらった後はどうする?
    1. ひとつひとつ調べる
    2. データ保持用のクラスをチェックボックスにバインドさせる

ラジオボタン

ラジオボタンといってもピンときませんが、下図の様なやつです。

160407-1-01

ユーザーに複数の選択肢を提示して1つ選んでもらうときに使うものです。

選択肢1つ毎にRadioButtonコントロールを配置します。

複数のRadioButtonをグループ化することで、RadioButton同士の選択を排他にすることができます。そもそも選択肢を提示するためのコントロールですから、グループ化して利用するというのが前提だと思います。

RadioButtonをグループ化するには、パネルの中にRadioButtonを配置するか、またはGroupName属性を設定するという方法があります。GroupName属性が同一のRadioButtonは、選択が排他制御されます。

よく使いそうなプロパティは下記です。

名前

説明

Name

string

識別名。

Content

object

ラジオボタンの表示内容。Content Controlからの継承。

GroupName

string

複数のラジオボタンをグループ化したときの、そのグループの名前。

IsChecked

bool

チェックされていればtrue、チェックが外れていればfalse。

ラジオボタンで選んでもらった後はどうする?

ここまではとてもわかりやすくて簡単そうと思ったのですが、ユーザーが選択した結果を取得するにはどうすれば良いかということで考えてしまいました。

ひとつひとつ調べる

ユーザーがRadioButtonを選択したときに発生するイベントを使って、各RadioButtonのIsCheckedプロパティの状態(true/false)を1件ずつ確認すれば、どのRadioButtonが選択されたかわかると思います。

RadioButtonにはcheckedというイベントがあって、このイベントハンドラにRadioButtonのIsCheckedプロパティを調べるコードを書けばいけると思います。

思います・・・というのは訳があって、この方法なら確実にできるだろうけど綺麗じゃないなぁなどと考えてしまったのです。(アマチュア的発想)

データ保持用のクラスをチェックボックスにバインドさせる

WPFっぽく、データ保持用のクラスを作ってそのプロパティをRadioButtonにバインドしてみます。@ITのコード例を参考にさせていただきました。(一部コードを変えました。)

試してみた

まず、ビューです。

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200" >
    <Grid>
        <StackPanel>
            <StackPanel x:Name="radiobuttonCandidate">
                <RadioButton Content="まどか" IsChecked="{Binding M}" />
                <RadioButton Content="ほむら" IsChecked="{Binding H}" />
                <RadioButton Content="さやか" IsChecked="{Binding S}" />
            </StackPanel>
            <TextBlock Name="textblock" Text="{Binding Value}" />
        </StackPanel>
    </Grid>
</Window>

TextBlockはStackPanelの外に出しました。TextBlockとRadioButtonを別々にバインドするためです。実際に使うときには、TextBoxを一緒にバインドすることはなかろうということです。

そしてコードです。

using System;
using System.ComponentModel;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            SelectedCandidate selection = new SelectedCandidate();
            radiobuttonCandidate.DataContext = selection;
            textblock.DataContext = selection;
        }

        public enum Candidates { Undefined, Madoka, Homura, Sayaka }

        public class SelectedCandidate : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            private Candidates _value;
            public Candidates Value
            {
                get { return _value; }
                set
                {
                    if (_value == value) return;
                    if (!Enum.IsDefined(typeof(Candidates), value)) throw new ArgumentOutOfRangeException();
                    _value = value;
                    var handler = PropertyChanged;
                    if (handler != null)
                    {
                        handler(this, new PropertyChangedEventArgs("Value"));
                        handler(this, new PropertyChangedEventArgs("M"));
                        handler(this, new PropertyChangedEventArgs("H"));
                        handler(this, new PropertyChangedEventArgs("S"));
                    }
                }
            }

            public bool M
            {
                get { return Value == Candidates.Madoka; }
                set { Value = Candidates.Madoka; }
            }
            public bool H
            {
                get { return Value == Candidates.Homura; }
                set { Value = Candidates.Homura; }
            }
            public bool S
            {
                get { return Value == Candidates.Sayaka; }
                set { Value = Candidates.Sayaka; }
            }
        }
    }
}

@ITのサンプルコードよりもかなり簡素にしました。

サンプルコードではStackPanelにデータ保持用クラスのインスタンスをいきなりバインドしてますが、実際には他のクラスからデータ保持用インスタンスのプロパティを参照するようにするだろうと考えて、あえてインスタンスを作ってからStackPanelとTextBlockにバインドしてみました。

また、サンプルコードではPropertyChangedEventHandlerの呼び出しにInvokeを使っているのですが、今ひとつInvokeを使う意味がわからないので外しました。(←勉強不足)

さらに、M/H/Sの各プロパティのセッターにFalseがセットされた場合の処理が記述されていたのですが、「ユーザーがセットするときはTrueしかないよな」と思いまして、Falseのときの処理を省きました。

サンプルコードから削除したものが多いのですが、単純に冗長なコードなのか、ロバスト性を上げるためのコードなのか、再利用を考えてのコードなのか、理由が想像できないというところが未熟ですね。→自分

説明してみる

コードを見ているだけだとよくわからなかったので、説明を書いておきます。

各コンポーネントとクラス(インスタンス)の関係は、下図の様になっています。

160407-1-02

データ保持用のクラスに、各RadioButtonと連動するBool型のプロパティと、選択結果を保持するプロパティ(列挙型)を作ります。選択結果のプロパティは列挙型でなくても大丈夫と思います。選択結果を各RadioButtonとTextBlockに反映するのにPropertyChangedイベントを使いますので、INotifyPropertyChangedインターフェースを継承して、PropertyChangedイベントを定義します。

RadioButtonに連動するプロパティの動作は、下図の様になっています。

160407-1-03

ユーザーがRadioButtonを選択するとセッターが呼び出されますので、呼び出されたら選択結果のプロパティを変更します。

選択結果を保持するプロパティの動作は、下図の様になっています。

160407-1-04

入力された値を検査して、問題無ければ各プロパティの変更イベントを発行します。そうすると、各プロパティにバインドされているコントロールがイベント通知を受け取って、コントロールの表示内容を自動的に変更します。

動かしてみた

ビルドして実行してみました。

160407-1-05

160407-1-06

160407-1-07

いろいろ省略しましたけど、選択結果がきちんとTextBlockに反映されますね。

補足

@ITのサンプルコードの説明によると、この方法ではコード側からRadioButtonの選択を変更した場合に問題がでるようです。XAMLの方でコンバーターを使う方法が推奨されてます。 コンバーターか・・・調べれば調べるほど、どんどん勉強が必要になってくるあたりが、奥深いというか、敷居が高いというか。

更新日
公開日

広告