C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[3]: データバインディングでデータソースが更新されない


(過去ログ 112 を表示中)

[トピック内 5 記事 (1 - 5 表示)]  << 0 >>

■66506 / inTopicNo.1)  データバインディングでデータソースが更新されない
  
□投稿者/ ゆうた (1回)-(2013/04/30(Tue) 02:43:03)

分類:[C#] 

はじめまして、ゆうたと申します。よろしくお願いいたします。
環境はVirtualBox4.2.6上のWindows7SP1_64bitでVSE2012forWindowsDesktop、C#を使用しています。

フォーム上で値を変更して保存ボタンを押した際に
バインドしたオブジェクトのプロパティを変更するようにしたいと思っています。

しかし下記のソースコードだとtextBoxCl1F2をフォーム上で変更しても
this.class.Foo2が変更されず
textBoxA_Cl1F2に表示される値はコンストラクタで指定した20のままになってしまいます。
(実際に作成しているプログラムでは下記のソースコードの場合のnumericUpDownCl1F1への変更も
データソースに反映されないのですが、うまく再現できませんでした。)

デバッグしてみると最初のデータソースへの書き込み後に
textBoxCl1F2の値がclass1.Foo2の値に置き換わってしまいます。
これが原因でフォーム上で更新してもデータソースが変更できないようです。

textBoxCl1F2の値が変更されてしまう理由がよくわからず、
どのように対処すればよいのかわかりません。
どなたか原因がわからないでしょうか?
よろしくお願いいたします。

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        Class1 class1;

        public Form1()
        {
            InitializeComponent();

            this.class1 = new Class1(10, 20);
            this.numericUpDownCl1F1.DataBindings.Add("Value", this.class1, "Foo1", false, DataSourceUpdateMode.Never);
            this.textBoxCl1F2.DataBindings.Add("Text", this.class1, "Foo2", false, DataSourceUpdateMode.Never);
            this.numericUpDownCl2V1.DataBindings.Add("Value", this.class1.class2, "Val1", false, DataSourceUpdateMode.Never);
            this.textBoxCl2V2.DataBindings.Add("Text", this.class1.class2, "Val2", false, DataSourceUpdateMode.Never);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.numericUpDownCl1F1.DataBindings["Value"].WriteValue();
            this.textBoxCl1F2.DataBindings["Text"].WriteValue();
            this.numericUpDownCl2V1.DataBindings["Value"].WriteValue();
            this.textBoxCl2V2.DataBindings["Text"].WriteValue();

            this.numericUpDownA_Cl1F1.Value = this.class1.Foo1;
            this.textBoxA_Cl1F2.Text = this.class1.Foo2.ToString();
            this.numericUpDownA_Cl2V1.Value = this.class1.class2.Val1;
            this.textBoxA_Cl2V2.Text = this.class1.class2.Val2.ToString();
        }


    }
}

namespace WindowsFormsApplication
{
    class Class1
    {
        public int Foo1 { get; set; }
        public int Foo2 { get; set; }
        public Class2 class2 { get { return new Class2(); } }

        public Class1(int arg1, int arg2)
        {
            this.Foo1 = arg1;
            this.Foo2 = arg2;
        }
    }
}

namespace WindowsFormsApplication
{
    class Class2
    {
        public int Val1 { get { return aa; } set { aa = value; } }
        public int Val2 { get { return bb; } set { bb = value; } }

        private static int aa;
        private static int bb;

        public Class2()
        {
        }
    }
}

引用返信 編集キー/
■66509 / inTopicNo.2)  Re[1]: データバインディングでデータソースが更新されない
□投稿者/ のぶ (18回)-(2013/04/30(Tue) 14:26:49)
2013/04/30(Tue) 14:34:46 編集(投稿者)

No66506 (ゆうた さん) に返信

===
ぱっとみだけの意見&試してもいないので外していたらごめんなさい。
Neverの意味はお分かりでしょうか??
===

MSDNより
「更新モードが Never の場合、WriteValue メソッドを使用して、データ ソースを強制的に更新できます。」との事ですね。
上記は無視して下さい。失礼しました。。。
引用返信 編集キー/
■66519 / inTopicNo.3)  Re[1]: データバインディングでデータソースが更新されない
□投稿者/ 魔界の仮面弁士 (204回)-(2013/05/01(Wed) 08:34:30)
No66506 (ゆうた さん) に返信
> しかし下記のソースコードだとtextBoxCl1F2をフォーム上で変更しても
> this.class.Foo2が変更されず
this.class.Foo2 ではなく、実際には
this.class1.Foo2 のことかと思いますが、それはさておき。


> (実際に作成しているプログラムでは下記のソースコードの場合のnumericUpDownCl1F1への変更も
> データソースに反映されないのですが、うまく再現できませんでした。)

Class1 の各プロパティを、get; set; による自動実装プロパティとはせず、明示的に
getter / setter を書くようにしておき、どのタイミングで setter が呼ばれているかを
追跡してみてください。numericUpDownCl1F1 の値を WriteValue するよりも前の段階で、
そのコントロールのバインド元変数に、修正前の値が再代入されていませんか?


> デバッグしてみると最初のデータソースへの書き込み後に
> textBoxCl1F2の値がclass1.Foo2の値に置き換わってしまいます。

WriteValue メソッドが呼ばれた場合、その影響範囲は全プロパティに及びます。
つまり numericUpDownCl1F1.DataBindings["Text"].WriteValue() が呼ばれた場合、
その影響範囲は Foo1/numericUpDownCl1F1 だけには留まらず、実際には
Foo2/textBoxCl1F2 にも影響を与えています。

今回の場合、最初の WriteValue メソッドの呼び出しによって、
class1.Foo1 に対して numericUpDownCl1F1.Value の値が新しく書き込まれます。

そしてそれと同時に、class1 のメンバーである Foo2 も更新されのですが、
class1.Foo2 側はまだ WriteValue されていないため、class1.Foo2 に入るのは
以前の古い値です。そしてこの再セットし直された古い値が今回の問題を引き起こしています。

バインド時に、Foo2 に対する ControlUpdateMode を Never にしていないため、
古い値が class1 に再セットされたときに、その変化がコントロール側にも伝わってしまい、
結果として textBoxCl1F2.Text が古い値に上書きされて戻ってしまうわけです。


> textBoxCl1F2の値が変更されてしまう理由がよくわからず、

上記のような理由から、もしも
  this.numericUpDownCl1F1.DataBindings["Value"].WriteValue();
  this.textBoxCl1F2.DataBindings["Text"].WriteValue();
という元のコードを、その実行順序を入れ替えて
  this.textBoxCl1F2.DataBindings["Text"].WriteValue();
  this.numericUpDownCl1F1.DataBindings["Value"].WriteValue();
に変更したとすれば、今度は class1.Foo2 を保存できるようになるでしょう。
(その代わり、class1.Foo1 が書き換わるタイミングを失いますが)



> どのように対処すればよいのかわかりません。
今回の場合、Never + WriteValue で更新する際には、データの逆流を防ぐため、
WriteValue 前に、「自身以外」の ControlUpdateMode をすべて Never にしてみてください。
そうすれば、自身以外のコントロールが古い値に書き戻される現象を防ぐことができます。

this.textBoxCl1F2.DataBindings["Text"].ControlUpdateMode = ControlUpdateMode.Never;

// numericUpDownCl1F1 の値を反映させる
this.numericUpDownCl1F1.DataBindings["Value"].WriteValue();

this.textBoxCl1F2.DataBindings["Text"].ControlUpdateMode = ControlUpdateMode.OnPropertyChanged;


あるいは、ControlUpdateMode を自分で切り替えるかわりに、
その役目を BindingSource に任せてしまうという手もあります。

この場合、RaiseListChangedEvents プロパティを false にしておくことで
他のプロパティが連動して更新される現象を抑制できます。


private Class1 class1;
private BindingSource bndSrc0, bndSrc1;

public Form1()
{
    InitializeComponent();

    this.class1 = new Class1(10, 20);

    // 直接バインドしない。
    // this.numericUpDownCl1F1.DataBindings.Add("Value", this.class1, "Foo1", false, DataSourceUpdateMode.Never);
    // this.textBoxCl1F2.DataBindings.Add("Text", this.class1, "Foo2", false, DataSourceUpdateMode.Never);
    // this.numericUpDownCl2V1.DataBindings.Add("Value", this.class1.class2, "Val1", false, DataSourceUpdateMode.Never);
    // this.textBoxCl2V2.DataBindings.Add("Text", this.class1.class2, "Val2", false, DataSourceUpdateMode.Never);

    // BindingSource に中継させるようにする。
    this.bndSrc0 = new BindingSource() { DataSource = this.class1 };
    this.bndSrc1 = new BindingSource(this.bndSrc0, "class2");

    this.numericUpDownCl1F1.DataBindings.Add("Value", this.bndSrc0, "Foo1", false, DataSourceUpdateMode.Never);
    this.textBoxCl1F2.DataBindings.Add("Text", this.bndSrc0, "Foo2", false, DataSourceUpdateMode.Never);
    this.numericUpDownCl2V1.DataBindings.Add("Value", this.bndSrc1, "Val1", false, DataSourceUpdateMode.Never);
    this.textBoxCl2V2.DataBindings.Add("Text", this.bndSrc1, "Val2", false, DataSourceUpdateMode.Never);
}


private void button1_Click(object sender, EventArgs e)
{
    this.bndSrc0.RaiseListChangedEvents = false;   // WriteValue する前に false にしておく

    this.numericUpDownCl1F1.DataBindings["Value"].WriteValue();
    this.textBoxCl1F2.DataBindings["Text"].WriteValue();
    this.numericUpDownCl2V1.DataBindings["Value"].WriteValue();
    this.textBoxCl2V2.DataBindings["Text"].WriteValue();

    this.bndSrc0.RaiseListChangedEvents = true;   // 処理が終わったので true に戻す

    this.numericUpDownA_Cl1F1.Value = this.class1.Foo1;
    this.textBoxA_Cl1F2.Text = this.class1.Foo2.ToString();
    this.numericUpDownA_Cl2V1.Value = this.class1.class2.Val1;
    this.textBoxA_Cl2V2.Text = this.class1.class2.Val2.ToString();
}



> private static int aa;
> public int Val1 { get { return aa; } set { aa = value; } }

これだと、インスタンスメンバーにしたいのか、クラスメンバーにしたいのか、
中途半端な実装に見えます。もしもインスタンスメンバーとして実装した上で
データバインドしたいのであれば、クラスに変更通知が必要になるかと。

引用返信 編集キー/
■66538 / inTopicNo.4)  Re[2]: データバインディングでデータソースが更新されない
□投稿者/ ゆうた (1回)-(2013/05/03(Fri) 00:47:01)
のぶさん
ご返信ありがとうございます。
私も最初、Neverだと更新されないのではと思っていました。



魔界の仮面弁士 さん
ご返信ありがとうございます。
BindingSourceを使用することで、うまくいくようになりました。

ControlUpdateModeが既定のOnPropertyChangedになっているために、
最初のWriteValueによって、データソースのthis.class1が変更されると
データソース側の値がコントロールに反映されてしまうのですね。

だから、class2に対するバインディングの際に
this.numericUpDownCl2V1.DataBindings.Add("Value", this.class1, "class2.Val1", ....)
のようにすると同じく、コントロールへの変更がデータソースに反映されないのですね。

一方で同じデータソースである「this.class1.class2」を使用しているのに
class2の方はnumericUpDownのWriteValue後でもtextBoxの値は変更されません。
この部分が理解できないのですが、どうしてなのでしょうか?
下記の変更通知が関連しているのでしょうか?


>>private static int aa;
>>public int Val1 { get { return aa; } set { aa = value; } }
> 
> これだと、インスタンスメンバーにしたいのか、クラスメンバーにしたいのか、
> 中途半端な実装に見えます。もしもインスタンスメンバーとして実装した上で
> データバインドしたいのであれば、クラスに変更通知が必要になるかと。

インスタンスメンバーとして実装しています。
実際のプログラムでは、別のクラスが管理しているデータに対してget・setしているので
その部分を簡略するためにクラスメンバーの変数を利用してみました。

引用返信 編集キー/
■66540 / inTopicNo.5)  Re[3]: データバインディングでデータソースが更新されない
□投稿者/ 魔界の仮面弁士 (205回)-(2013/05/03(Fri) 11:40:13)
No66538 (ゆうた  さん) に返信
> 一方で同じデータソースである「this.class1.class2」を使用しているのに
> class2の方はnumericUpDownのWriteValue後でもtextBoxの値は変更されません。
> この部分が理解できないのですが、どうしてなのでしょうか?

現在の設計のままで、その目的を果たそうとするのであれば、
Class2 に対する ControlUpdateMode を OnPropertyChanged から Never に変更するか、
もしくは、Class2 に対する BindingSrouce (bndSrc1) の RaiseListChangedEvents を
false にセットする必要があるかと。


> 下記の変更通知が関連しているのでしょうか?

それもありますが、そもそもの実装が不自然です。最初に提示されたコードだと、
class1.class2 にアクセスするたびに、新しいインスタンスが返されてしまいますよね。

その具体的な箇所を挙げると、

  class Class1
  {
    public Class2 class2 { get { return new Class2(); } }
  }

と書かれている部分のことです。

この実装のままですと、後で値を取り出すための
 this.numericUpDownA_Cl2V1.Value = this.class1.class2.Val1;
 this.textBoxA_Cl2V2.Text = this.class1.class2.Val2.ToString();
という部分で、1回目と2回目の「this.class1.class2」が別のオブジェクトを指すことになります。

また、最初に提示いただいたバインドコードの
 this.numericUpDownCl2V1.DataBindings.Add("Value", this.class1.class2, "Val1", false, DataSourceUpdateMode.Never);
 this.textBoxCl2V2.DataBindings.Add("Text", this.class1.class2, "Val2", false, DataSourceUpdateMode.Never);
という部分も、両者にバインドされるオブジェクトが別物であるため、
 numericUpDownCl2V1.DataBindings[0].DataSource != textBoxCl2V2.DataBindings[0].DataSource
という関係になってしまいます。


今回の実装では、実質的に Class2 の各メンバーが static な値を返すので、別インスタンスであることに
気づきにくいかも知れませんが、これでは階層構造としてのデータバインドに使うには都合が悪すぎます。

そのままバインドさせるようにしたいなら、毎回生成するのではなく、
  class Class1
  {
    private Class2 _class2 = new Class2();
    public Class2 class2 { get { return _class2; }  }
  }
のように、常に同じインスタンスを返すようにした方が良いでしょう。

仮に、どうしても毎回違うインスタンスを返す必要があるのなら、「class2 プロパティ」と
するのではなく、「CreateClass2 メソッド」「NewClass2 メソッド」などとして
実装した方が自然かと思いますよ。


> >>private static int aa;
> >>public int Val1 { get { return aa; } set { aa = value; } }
> インスタンスメンバーとして実装しています。
このコードだと、すべてのインスタンスで同じ値が共有されてしまうので、
実質的には static な値管理になってしまいますけれどね。

# インスタンス管理が中途半端だったので、もしかして、Class2 をシングルトンとして
# 実装しようとしているのかな、と勝手に想像していました。


> 実際のプログラムでは、別のクラスが管理しているデータに対してget・setしているので
> その部分を簡略するためにクラスメンバーの変数を利用してみました。
そういうことだったのですね。
元のコードにコメント等で一筆書いておいてもらえると、意図を理解できたかも。

引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -