none
複数のデータバインドしたTextBox入力で、エラー表示のまま維持したい RRS feed

  • 質問

  • VS2013 C# Fromアプリで開発しています。

    質問がこちらの内容と同じなのですが、どうも解決しないので再度質問させてください。
    https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/5db7c0b1-28ba-425e-8320-c209b78a1af3/textboxvalidate?forum=vbgeneralja

    複数のTextBoxにそれぞれInt型のプロパティをデータバインドしてあります。
    TextBoxに文字列を入力するとエラーになり、フォーカスが外れなくなるため、AutoValidate を EnableAllowFocusChangeにしてフォーカスを外れるようにしました。
    ここで、TextBoxに文字列を入力してエラー状態にしている時に、他のTextBoxに正常な値を入力するとエラー状態の全てのTextBoxの値がバインドしているプロパティの値に更新されてしまいます。
    この更新をさせないようにする方法はないでしょうか。

    手元にVS2005が無いために純粋にテストできませんが、IDataErrorInfoもBindingSourceも.NET Framework 2.0に存在しますから、同じようにできるのではないかと思います。
    また、私の説明不足で申し訳ありませんでしたが、本意はIDataErrorInfoを使用することではなく、リンク先にあるようにUIバインド用オブジェクトを用意し、そこにバインドするということなのです。UIバインド用オブジェクトは何でも受け取れるようにしておき、受け取ってからエラーを判断するというのがポイントです。このエラーを判断する上で便利に使えるのがIDataErrorInfoなのです。
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/

    直接Int型ではなくString型のバインド用オブジェクトにするべきでしょうか?

    2015年4月14日 3:08

回答

  • http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Binding.cs

    深く追えてませんが、Source(Modelインスタンス)が更新されたタイミングでControlの更新を行うような気がするので、メンバ別の制御は一括で設定するのは難しそうです。

    前回質問時の内容を見ましたが、最後のTH01さんの案で良いのでは。
    追加でLoadのタイミングなどで初期値として一度読み込みを行ってください。

    public partial class Form1 : Form
    {
        private Binding[] binds;
        public Form1()
        {
            InitializeComponent();
    
            this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange;
            var BindingSource = new BindingSource() { DataSource = new BindClass() };
    
            binds = new Binding[] 
            {
                this.textBox1.DataBindings.Add("Text", BindingSource, "A", true),
                this.textBox2.DataBindings.Add("Text", BindingSource, "B", true),
                this.textBox3.DataBindings.Add("Text", BindingSource, "C", true)
            };
            
            foreach(var current in binds)
            {
                current.ControlUpdateMode = ControlUpdateMode.Never;
                current.BindingComplete += current_BindingComplete;
            }
    
            errorProvider1.DataSource = BindingSource;
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            foreach (var current in binds)
            {
                current.ReadValue();
            }
        }
    
        void current_BindingComplete(object sender, BindingCompleteEventArgs e)
        {
            if(e.BindingCompleteState == BindingCompleteState.Success)
                e.Binding.ReadValue();
        }
    
        public class BindClass
        {
            public String A { get; set; }
            public int B { get; set; }
            public decimal C { get; set; }
        }
    
    }


    2015年4月14日 6:54
    モデレータ
  • バインド用のオブジェクトを作成するぐらいならバインドではなくTextChangedイベント等でコードを書いたほうが良い気がしたので少し疑問でした。

    Code Recipeにサンプルを上げました。

    WindowsフォームにおけるIDataErrorInfoの実装サンプル
    https://code.msdn.microsoft.com/WindowsIDataErrorInfo-637ebea1

    バインド用オブジェクトがなぜ必要なのかについても軽く触れています。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク kitunechan 2015年4月17日 0:30
    2015年4月16日 6:24
    モデレータ

すべての返信

  • こんにちは。

    リンク先はVB.NETの2.0のようですがそれは無視して大丈夫です?
    現状かつミニマムなコードを提示して頂けると話が早いと思います。

    複数のテキストボックスがあって、その全てに同一インスタンスのプロパティをバインドさせているイメージであってますか。
    その場合、

    他のTextBoxに正常な値を入力するとエラー状態の全てのTextBoxの値がバインドしているプロパティの値に更新されてしまいます。

    上記動作は標準の正常動作だと思うのですが、実現したい動作は何でしょうか?

    リンク先では「エラーがあったときは○○」みたいなことも記載されていましたが、問題点のみで最小限に整理し記載して頂けると。

    2015年4月14日 3:34
    モデレータ
  • 言語やバージョンは無視してもらっても構わないとおもいます。

    TextBox1、2、3を別で追加して下さい。

    public partial class Form1: Form {
    	public Form1() {
    		InitializeComponent();
    
    		var BindingSource = new BindingSource();
    			BindingSource.DataSource = new BindClass();
    			this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding("Text", BindingSource, "A", true));
    			this.textBox2.DataBindings.Add(new System.Windows.Forms.Binding("Text", BindingSource, "B", true));
    			this.textBox3.DataBindings.Add(new System.Windows.Forms.Binding("Text", BindingSource, "C", true));
    
    	}
    
    	public class BindClass {
    		public String A { get; set; }
    		public int B { get; set; }
    		public decimal C { get; set; }
    	}
    }
    

    例えば、TextBox2に「aaaa」と入れている状態でTextBox3に数値を入れるとTextBox2の内容が消えてしまうので、消えずに「aaaa」のままを維持できないでしょうか

    2015年4月14日 4:16
  • http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Binding.cs

    深く追えてませんが、Source(Modelインスタンス)が更新されたタイミングでControlの更新を行うような気がするので、メンバ別の制御は一括で設定するのは難しそうです。

    前回質問時の内容を見ましたが、最後のTH01さんの案で良いのでは。
    追加でLoadのタイミングなどで初期値として一度読み込みを行ってください。

    public partial class Form1 : Form
    {
        private Binding[] binds;
        public Form1()
        {
            InitializeComponent();
    
            this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange;
            var BindingSource = new BindingSource() { DataSource = new BindClass() };
    
            binds = new Binding[] 
            {
                this.textBox1.DataBindings.Add("Text", BindingSource, "A", true),
                this.textBox2.DataBindings.Add("Text", BindingSource, "B", true),
                this.textBox3.DataBindings.Add("Text", BindingSource, "C", true)
            };
            
            foreach(var current in binds)
            {
                current.ControlUpdateMode = ControlUpdateMode.Never;
                current.BindingComplete += current_BindingComplete;
            }
    
            errorProvider1.DataSource = BindingSource;
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            foreach (var current in binds)
            {
                current.ReadValue();
            }
        }
    
        void current_BindingComplete(object sender, BindingCompleteEventArgs e)
        {
            if(e.BindingCompleteState == BindingCompleteState.Success)
                e.Binding.ReadValue();
        }
    
        public class BindClass
        {
            public String A { get; set; }
            public int B { get; set; }
            public decimal C { get; set; }
        }
    
    }


    2015年4月14日 6:54
    モデレータ
  • 理解できました。
    ありがとうございました。

    2015年4月14日 8:45
  • 直接Int型ではなくString型のバインド用オブジェクトにするべきでしょうか?

    Windows フォームのデータバインドだと、型が違うと強制的にエラーになって、うまく制御しきれなかったと思います。ですから、本来の型とは違うプロパティを持ったバインド用のオブジェクトを用意したり、nullを受け付けられるように例えばint?型にするなどした例を多く見かけました。ちなみにWPFだとint型のままでも問題ありません。WPFの特徴として、バインドが強化されています。
    と、これだけではわかりにくと思いますので、今日は時間がありませんが、自分用の検証も兼ねて、近いうちにサンプルコードを書きたいと思います。

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年4月15日 8:30
    モデレータ
  • 回答ありがとうございます。

    ですから、本来の型とは違うプロパティを持ったバインド用のオブジェクトを用意したり、nullを受け付けられるように例えばint?型にするなどした例を多く見かけました。

    バインド用のオブジェクトを作成するぐらいならバインドではなくTextChangedイベント等でコードを書いたほうが良い気がしたので少し疑問でした。

    WPFだと赤枠でエラー表示もされて便利になってますね。


    2015年4月16日 0:43
  • バインド用のオブジェクトを作成するぐらいならバインドではなくTextChangedイベント等でコードを書いたほうが良い気がしたので少し疑問でした。

    Code Recipeにサンプルを上げました。

    WindowsフォームにおけるIDataErrorInfoの実装サンプル
    https://code.msdn.microsoft.com/WindowsIDataErrorInfo-637ebea1

    バインド用オブジェクトがなぜ必要なのかについても軽く触れています。


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク kitunechan 2015年4月17日 0:30
    2015年4月16日 6:24
    モデレータ
  • 大変勉強になりました。
    ありがとうございました。
    2015年4月17日 0:37