none
TabControlとRadioButtonをBinding生成するとRadioButtonのIsChecked値が突然すべてFalseに・・・ RRS feed

  • 質問

  • TabControl内にRadioButtonをBindingにより動的生成させているのですが、
    生成後に以下の手順による操作を行うと、RadioButtonのIsChecked値がおかしく
    なってしまうのです。どなたか原因または回避方法をご存じないでしょうか?

     

    1.生成された2つのタブを交互に切り替え
    2.RadioButtonの選択を数回切り替え

     

    ※明確な再現方法がないのですが、この手順を高速で何度も繰り返すと、
     あるタイミングで、RadioButtonのIsChecked値がすべてFalseに
     なってしまうのです。
     


    <Window1.xaml>
    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
      <Window.Resources>
        <DataTemplate x:Key="TabItemTemplate">
          <TextBlock Text="{Binding Path=Title}"/>
        </DataTemplate>

        <DataTemplate x:Key="TabContentTemplate">
          <AdornerDecorator>
            <ItemsControl ItemsSource="{Binding Path=ControlValues}">
            <ItemsControl.ItemTemplate>
              <DataTemplate>
                  <StackPanel>
                    <RadioButton  Content="{Binding Path=IsChecked}"
                                  GroupName="{Binding Path=GroupKey}"
                                  IsChecked="{Binding Path=IsChecked}" />
                  </StackPanel>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>
          </AdornerDecorator>
        </DataTemplate>
      </Window.Resources>
     
        <TabControl Name="ConfigTab"
                    ItemTemplate="{DynamicResource TabItemTemplate}"
                    ContentTemplate="{DynamicResource TabContentTemplate}" />
    </Window>


    <Window1.xaml.cs>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using WpfApplication1.Data;
    using System.Collections.ObjectModel;

    namespace WpfApplication1
    {
      public partial class Window1 : Window
      {
        public Window1()
        {
          InitializeComponent();

          Test1 T1 = new Test1();
          Test1 T2 = new Test1();
          Test1 T3 = new Test1();
          Test1 T4 = new Test1();
          ObservableCollection<Test1> TEST = new ObservableCollection<Test1>();
          ObservableCollection<Test1> TEST2 = new ObservableCollection<Test1>();
          Test2 TM = new Test2();
          Test2 TM2 = new Test2();
          ObservableCollection<Test2> TTT = new ObservableCollection<Test2>();

          T1.IsChecked = true;
          T1.GroupKey = "G";
          T2.IsChecked = false;
          T2.GroupKey = "G";
          T3.IsChecked = false;
          T3.GroupKey = "G";
          T4.IsChecked = false;
          T4.GroupKey = "G";
          TEST.Add(T1);
          TEST.Add(T2);
          TEST.Add(T3);
          TEST.Add(T4);
          TM.ControlValues = TEST;
          TM.Title = "aaa";
          TTT.Add(TM);
          TM2.Title = "bbb";
          TTT.Add(TM2);
          ConfigTab.ItemsSource = TTT;

        }

      }
    }

     

    ~以下はタブとRadioButtonのデータクラス
    <Test1.cs >
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Collections.ObjectModel;

    namespace WpfApplication1.Data
    {
      class Test1 : DependencyObject
      {
        public Test1()
        {
        }
     
        //ラジオボタンIsChecked用
        private static readonly DependencyProperty IsCheckedProperty =
          DependencyProperty.Register("IsChecked", typeof(bool), typeof(Test1), new PropertyMetadata(false));
        public bool IsChecked
        {
          get { return (bool)GetValue(IsCheckedProperty); }
          set { SetValue(IsCheckedProperty, value); }
        }

        //ラジオボタンGroupName用
        private static readonly DependencyProperty GroupKeyProperty =
          DependencyProperty.Register("GroupKey", typeof(string), typeof(Test1), new PropertyMetadata(string.Empty));
        public string GroupKey
        {
          get { return (string)GetValue(GroupKeyProperty); }
          set { SetValue(GroupKeyProperty, value); }
        }
      }
    }


    <Test2.cs>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Collections.ObjectModel;

    namespace WpfApplication1.Data
    {
      class Test2 : DependencyObject
      {
        public Test2()
        {
        }

        //ラジオボタン構成要素
        private static readonly DependencyProperty ControlValuesProperty =
          DependencyProperty.Register("ControlValues", typeof(ObservableCollection<Test1>), typeof(Test2), new PropertyMetadata(null));
        public ObservableCollection<Test1> ControlValues
        {
          get { return (ObservableCollection<Test1>)GetValue(ControlValuesProperty); }
          set { SetValue(ControlValuesProperty, value); }
        }

        //タブヘッダ
        private static readonly DependencyProperty TitleProperty =
          DependencyProperty.Register("Title", typeof(string), typeof(Test2), new PropertyMetadata(string.Empty));
        public string Title
        {
          get { return (string)GetValue(TitleProperty); }
          set { SetValue(TitleProperty, value); }
        }

      }
    }


    <Test3.cs >
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Collections.ObjectModel;

    namespace WpfApplication1.Data
    {
      class Test3 : DependencyObject
      {
        public Test3()
        {
        }

        //タブ構成要素
        private static readonly DependencyProperty ControlValuesProperty =
          DependencyProperty.Register("ControlValues", typeof(ObservableCollection<Test2>), typeof(Test3), new PropertyMetadata(null));
        public ObservableCollection<Test2> ControlValues
        {
          get { return (ObservableCollection<Test2>)GetValue(ControlValuesProperty); }
          set { SetValue(ControlValuesProperty, value); }
        }
      }
    }

     

     

    2008年3月4日 12:24

回答

  • RadioButtonのIsCheckedにBindingを指定した場合、チェックが自動で外される時に、Bindingも消えてしまうようです。(自動で外されるとは、同じグループ内で別なButtonにCheckが入ると、今までCheckされていたものが自動でUncheckされる動作の事)

     

    RadioButtonのIsCheckedをSourceにしたBindingは大丈夫なんですけどね。

     

    XAML PAD等で、

    Code Snippet

      <StackPanel>
        <CheckBox x:Name="c1" IsChecked="True" Content="C-1"/>
        <CheckBox x:Name="c2" Content="C-2"/>
        <CheckBox x:Name="c3" Content="C-3"/>
        <RadioButton x:Name="a1" GroupName="A" Content="A-1" IsChecked="{Binding ElementName=c1, Mode=TwoWay, Path=IsChecked}"/>
        <RadioButton x:Name="a2" GroupName="A" Content="A-2" IsChecked="{Binding ElementName=c2, Mode=TwoWay, Path=IsChecked}"/>
        <RadioButton x:Name="a3" GroupName="A" Content="A-3" IsChecked="{Binding ElementName=c3, Mode=TwoWay, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a1, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a2, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a3, Path=IsChecked}"/>
      </StackPanel>

     

    みたいなXAMLを試すと、動作がわかるかも・・・。

    2008年3月5日 0:48
  • 回避方法としては、グループでのON/OFFを自分で行うようにして、RadioButtonではなく、外観をRadioButton風にしたCheckBoxにする。

     

    とか、

     

    可能であれば、RadioButtonのIsCheckedにBindingを設定するのではなく、

    Source側にBindingをMode=TwoWayで設定する。

     

    そんな事くらいしか思いつきません。

     

    もっと、良い回避方法を知っている方がいれば、よろしくお願いします。

    2008年3月5日 0:52
  • その変更で、Test1.cs内のIsCheckedプロパティが画面からチェック変更時に正常に更新されますか ?

     

    画面からのチェック変更時に、RadioButtonのIsCheckedに設定されているBindingが消えてしまうようなので、Bindingが消えてしまった後は、画面からチェックを変更しても、データ(Test1.cs内のIsCheckedプロパティ)が更新されない状態になるはずです。

    (サンプルで言うと、a1~3のチェックを画面から変更しても、c1~3のチェックが変更されない状態)

    2008年3月5日 4:46

すべての返信

  • RadioButtonのIsCheckedにBindingを指定した場合、チェックが自動で外される時に、Bindingも消えてしまうようです。(自動で外されるとは、同じグループ内で別なButtonにCheckが入ると、今までCheckされていたものが自動でUncheckされる動作の事)

     

    RadioButtonのIsCheckedをSourceにしたBindingは大丈夫なんですけどね。

     

    XAML PAD等で、

    Code Snippet

      <StackPanel>
        <CheckBox x:Name="c1" IsChecked="True" Content="C-1"/>
        <CheckBox x:Name="c2" Content="C-2"/>
        <CheckBox x:Name="c3" Content="C-3"/>
        <RadioButton x:Name="a1" GroupName="A" Content="A-1" IsChecked="{Binding ElementName=c1, Mode=TwoWay, Path=IsChecked}"/>
        <RadioButton x:Name="a2" GroupName="A" Content="A-2" IsChecked="{Binding ElementName=c2, Mode=TwoWay, Path=IsChecked}"/>
        <RadioButton x:Name="a3" GroupName="A" Content="A-3" IsChecked="{Binding ElementName=c3, Mode=TwoWay, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a1, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a2, Path=IsChecked}"/>
        <TextBlock Text="{Binding ElementName=a3, Path=IsChecked}"/>
      </StackPanel>

     

    みたいなXAMLを試すと、動作がわかるかも・・・。

    2008年3月5日 0:48
  • 回避方法としては、グループでのON/OFFを自分で行うようにして、RadioButtonではなく、外観をRadioButton風にしたCheckBoxにする。

     

    とか、

     

    可能であれば、RadioButtonのIsCheckedにBindingを設定するのではなく、

    Source側にBindingをMode=TwoWayで設定する。

     

    そんな事くらいしか思いつきません。

     

    もっと、良い回避方法を知っている方がいれば、よろしくお願いします。

    2008年3月5日 0:52
  • FC-Shiroさん、ご返信ありがとうございました。

    提示して頂いたサンプルも試させて頂き、RadioButtonのIsCheckedにBindingを設定する場合の

    注意点がよくわかりました。

     

    今回の回避方法ですが、実現したい内容が以下の通りでしたので、

    根本的な解決ではないのですが、目的を達成できました。

    <実現したい内容>

    1.データ読み込み時に、クラス(Test1.cs)内のIsChecked値をRadioButtonのIsCheckedへ反映させたい。

      (データ→RadioButtonへの通知)

    2.画面で操作したRadioButtonIsCheckedをクラス(Test1.cs)内のIsChecked値へ格納したい。

      (RadioButton→データへの通知)

    ※一見するとMode=TwoWayによる相互通信なのですが、画面表示時に裏側でクラス(Test1.cs)内のIsChecked値を

      変更することで、画面側のRadioButton表示を変えるような動作の必要がないので、クラス側でPropertyChanged

      イベントをRadioButtonへ通知する必要がありません。(操作中はRadioButton→クラスへの単方向通信

      これが今回の回避方法のポイントになりました。

     

    回避方法は以下の通りです。

    1.Test1.csの「DependencyObject」継承を「INotifyPropertyChanged」継承へ変更

    2.Test1.cs内のIsCheckedプロパティで、PropertyChangedイベントを発生させない。

    3.XAML側のRadioButtonは、IsChecked="{Binding Path=IsChecked , Mode=TwoWay}"とする。

     

    <変更したTest1.cs> 

    Code Snippet

    class Test1 : INotifyPropertyChanged
      {
        public Test1()
        {}

       

      private bool _IsChecked;

       

      public bool IsChecked
        {
          get
          { return _IsChecked; }
          set
          {
            _IsChecked = value;
            //OnPropertyChanged("IsChecked");  ←※ このイベントを画面側に通知しない!!
          }
        }
       
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
          if (PropertyChanged != null)
          {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
          }
        }

      }

     

     

    この変更により、これまで無条件に画面側へ通知されていたPropertyChangedイベントを任意で停止させる

    ことができました。

     

    こうすることで、

    1.画面初期表示、タブ遷移からの復帰のタイミングでは、画面側のRadioButtonへクラス値を反映

    2.画面側のRadioButton操作時は、クラス側へ一方通行の通信(クラス側からのPropertyChangedイベントを受け取らない)

    が実現でき、結果的に意図しない「全てFalseになる」問題を回避できました。

     

    はっきりした原因はわかりませんが、クラス側からのPropertyChangedイベントを受け取ったRadioButtonの処理と、

    タブ遷移による、画面再構成の処理のタイミングに問題のひとつがあるように思えました。

     

    重ねて申し上げますが、この回避では、Test1.cs内のIsChecked値を裏側で変更することによる

    RadioButton表示反映ができません。よって、根本的な解決ではありません。

    (たまたま、今回作成する動作の中にそのような処理がなかったのでラッキーだったというだけ。)

     

    根本的な解決をするためには、FC-Shiroさんのアドバイス通り、

    RadioButton風にしたCheckBoxを作成するのが良いと思いました。

     

    アドバイスありがとうございました。

    また何かあった時は、よろしくお願いいたします。

     

     

    2008年3月5日 3:46
  • その変更で、Test1.cs内のIsCheckedプロパティが画面からチェック変更時に正常に更新されますか ?

     

    画面からのチェック変更時に、RadioButtonのIsCheckedに設定されているBindingが消えてしまうようなので、Bindingが消えてしまった後は、画面からチェックを変更しても、データ(Test1.cs内のIsCheckedプロパティ)が更新されない状態になるはずです。

    (サンプルで言うと、a1~3のチェックを画面から変更しても、c1~3のチェックが変更されない状態)

    2008年3月5日 4:46
  • FC-Shiroさん、ご確認のコメント大変ありがとうございます。

     

    確かに、ご提示頂いたサンプルの動作ではCheckBoxへのBindingが無くなってしまうのか
    ご教授頂いた通りの結果ですよね。

    RadioButton:IsChecked→→[Binding通知]→→??・・CheckBox
    (CheckBoxのCheckedイベント、UnCheckedイベント反応なし)

     

    ところが私の書いたプログラムでは、少し動作が異なりTest1.cs内のIsCheckedプロパティへの
    Binding通知が途切れる事がないのです。

    RadioButton:IsChecked→→[Binding通知]→→Test1.cs:IsChecked
    (Test1.csのIsCheckedプロパティ:PropertyChangedイベントで受信)

     

    つまり同じバインディング問題でも、コントロールとデータクラス(自作)では動作が異なり、
    問題の根本原因が違うのかも知れません。

     

    FC-Shiroさんからご提示頂いたサンプルで、RadioButtonのみ着目するとRadioButton自体の
    True,False値は正常で、CheckBoxへのBindingが無くなってしまう現象により、結果CheckBoxの値が
    期待するものにならない。という事なんですよね。

     

    では、私の書いたプログラムではどうか?というと、上記で書かせて頂いた通り、
    Binding通知が途切れる事がないのです。・・じゃあ、何が問題なのか?って事なのですが、

    正常の場合は、この様に通知されています。

    (XAMLにRadioButtonが4つあると仮定)
    ①・・RadioButton:IsChecked=True
    ②・・RadioButton:IsChecked=False
    ③・・RadioButton:IsChecked=False
    ④・・RadioButton:IsChecked=False

     

    ②のRadioButtonを選択するように操作した場合
    1.クラス側:②RadioButton対応するTest1.cs:IsCheckedプロパティ=True→PropertyChangedイベント発生
    2.クラス側:①RadioButton対応するTest1.csのIsCheckedプロパティ=False→PropertyChangedイベント発生
    3.XAML側: ①RadioButton:IsChecked=False→Unchekedイベント発生
    4.XAML側: ②RadioButton:IsChecked=True→Chekedイベント発生

    このように、クラス側の値が変更された後に、XAML側が変更されます。
    この動作は問題ありません。

     

    ところが、画面操作を続けていると、あるタイミングで動きがおかしくなります。

     

    ②のRadioButtonを選択するように操作した場合
    1.クラス側:②RadioButton対応するTest1.cs:IsCheckedプロパティ=True→PropertyChangedイベントが2回発生
    2.XAML側: ②RadioButton:IsChecked=True→Chekedイベント発生
    3.クラス側:②RadioButton対応するTest1.cs:PropertyChangedイベントが2回発生
    4.クラス側:②RadioButton対応するTest1.cs:IsCheckedプロパティ=False→PropertyChangedイベントが3回発生
    5.XAML側: ①RadioButton:IsChecked=False→Unchekedイベント発生
    6.クラス側:①RadioButton対応するTest1.csのIsCheckedプロパティ=False→PropertyChangedイベントが1回発生
    7.XAML側: ②RadioButton:IsChecked=False→Unchekedイベント発生(再発生)
    8.XAML側: ②RadioButton:IsChecked=True→Chekedイベント発生
    9.XAML側: ①RadioButton対応するTest1.cs:PropertyChangedイベントが1回発生
    10.クラス側:①RadioButton対応するTest1.cs:IsCheckedプロパティ=False→PropertyChangedイベントが5回発生
    11.XAML側: ②RadioButton:IsChecked=False→Unchekedイベント発生(再再発生)
    12.XAML側: ②RadioButton:IsChecked=False→Chekedイベント発生

     

    相互をトレースした結果なのですが、こんな感じでもうメチャメチャな感じです。
    着目点として、クラス側が発生させているPropertyChangedイベントを止めれば、
    循環エラー??とも思えるおかしな現象を止めれるかも?という推論からの行為でした。

     

    ただし、タブ遷移によるRadioButton初期化動作とRadioButtonのIsCheckedバインドの
    組み合わせが非同期に起きた時に発生する問題なのだろうか・・・?
    などと考えてしまいました。

    だらだらした長文で申し訳ございませんが、現時点ではこのような状態です。

     

    先ほどは、御説明不足で申し訳ありませんでした。

     

     

    2008年3月5日 6:29
  • こんにちは。栗原麻里 です。

     

    FC-Shiro さん、回答ありがとうございます。

     

    ともき さん、フォーラムのご利用ありがとうございます。
    有用な情報だと思いましたので、FC-Shiro さんの回答へ回答済みチェックをつけさせていただきました。

     

    回答済みチェックが付くことにより、フォーラムをご利用していただいている皆様が、有用な情報を
    見つけやすくなります。
    ですので、回答された情報が有用だと思われましたら、ぜひ回答済みボタンを押してチェックを付けて
    くださいね!

     

    ともき さんはチェックを解除することもできますので、ご確認ください。

     

    それでは、ぜひまたご活用ください!

    2008年4月2日 7:29
  • 同じように、 RadioButton の IsChecked にデータバインディングすることをやってます。
    同じような現象が発生していて、 調査中…

    現在までに分かったこと:
    バインドしたバリューオブジェクトで、 PropertyChangedEventHandler を発火させる順序によって、 バインディングが 「外れる」 現象が出るようです。
    ○ … IsChecked が True だったものを False にしてから、 False だったものを True に。
    × … IsChecked が False だったものを True にしてから、 True だったものを False に。
    つまり、 同じグループ内の RadioButton で IsChecked が True になっているものが、 一瞬でも複数存在する状態になると、 バインディングが 「外れる」みたいです。
    2008年7月7日 7:19
  • 同様な現象が、 すでに MS Connect に上がっていました。

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=278531
    > RadioButton.IsCheckedProperty losing databindings

    昨年の 5月にフィードバックされたものです。
    添付されているソースコードを眺めた限りでは、 今回の問題と同じもののように思えます。

    MS からは、 再現した (reproduce) ので Visual Studio のチームに廻す、 という回答が同じ 5月に付いていますが、 それっきりのようです。
    同じ問題に突き当たっている方、 どうぞこのフィードバックに 「評価」 (レイティング) を付けてあげてください。
    2008年8月28日 2:17
  • biacさんコメントありがとうございます。

    ○ … IsChecked が True だったものを False にしてから、 False だったものを True に。

    先日このパターンでもバインディングが 「外れる」 現象が発生しました。
    まことに悲しい限りです。

    2008年12月5日 13:12