トップ回答者
C# numericUpDown のデータ確定のタイミングについて知りたいです。

質問
-
0
Visual Studio Community 2017 Version15.7.2 を使っております。
numericUpDownのイベントで値を送信しているのですが変更される前の値が送信されています。
▼▲をクリックしたときにクリックする前の値を送信してその後に値が更新されているようです。
試しにクリックしたときにタイマーを走らせてタイマー後に送信したら更新後の値が送信できるようになったのですが使い方として正しいのかがわかりません。
【プログラムの中身】
Varu_send(); という関数の中でnumericUpDown1.Text の値を送信しています。
private void UpDown_V1_ValueChanged(object sender, EventArgs e) { Varu_send(); }
これですと更新される前の値が送信されます。なので
private void UpDown_V1_ValueChanged(object sender, EventArgs e) { timer1.Start(); }
として
private void timer1_Tick(object sender, EventArgs e){timer1.Stop(); Varu_send();}
としましたらやりたいことは実現できました。
出来たは出来たのですが本来の使い方として正しいのかがちょっとわかりません。
ご意見いただけますと大変有難いです。何卒宜しくお願い致します。
回答
-
NumericUpDown.ValueChanged Event発生時にはまだTextの値は更新されませんので、
numericUpDown1.Textではなく、numericUpDown1.Valueを使用すればよいかと。- 回答としてマーク kinto-to-clave 2019年7月24日 5:36
すべての返信
-
numericUpDownのイベントで値を送信しているのですが変更される前の値が送信されています。
ここでいう「変更される前」とは、どの時点との比較でしょうか?
たとえばデータバインドしている場合、ValueChanged は値の補正前に呼ばれることが知られています。
下記は「小数点以下 3 桁」の NumericUpDown に対する実験コードです。
- ユーザーが NumericUpDown1 に「1.234567」と手入力する
- 入力後、Tab キーあるいはクリックで、他のコントロールにフォーカスを遷移させる
- ValueChanged が発生するが、このときの値は小数 3 桁ではなく、元の 6 桁値のまま返される
- ただし NumericUpDown は小数 3 桁の「1.235」と表示されている
- しかしバインド先には残りの小数部も伝わっているので、別のレコードに移動すると、DataGridView 上にも 6 桁値として表示される
DataSet ds; private void Form1_Load(object sender, EventArgs e) { // 実験用の DataSet ds = new DataSet("Example"); var tbl = ds.Tables.Add("Tbl1"); tbl.PrimaryKey = new DataColumn[] { tbl.Columns.Add("ID", typeof(int)) }; tbl.Columns.Add("Val", typeof(decimal)); for (int r = 10; r < 30; r++) { tbl.Rows.Add(r, 100.456M + r); } ds.AcceptChanges(); // 整数部 3 桁 + 小数部 3 桁な入力を想定した NumericUpDown
// 実際には小数部 4 桁以上の値も入力できてしまう numericUpDown1.DecimalPlaces = 3; numericUpDown1.Minimum = -999.999M; numericUpDown1.Maximum = 999.999M; dataGridView1.ReadOnly = true; // データバインド dataGridView1.DataSource = tbl; numericUpDown1.DataBindings.Add("Value", tbl, "Val"); } private void numericUpDown1_ValueChanged(object sender, EventArgs e) { listBox1.Items.Insert(0, numericUpDown1.Value); listBox1.SelectedIndex = 0; }UpdateEditText メソッドをオーバーライドすれば、補正後の値になるのですけれどね。
protected override void UpdateEditText() { decimal multiple = Convert.ToDecimal(Math.Pow(10, this.DecimalPlaces)); decimal newValue = Decimal.Truncate(Decimal.Multiply(this.Value, multiple)); this.Value = Decimal.Divide(newValue, multiple); base.UpdateEditText(); }
データバインドの話ではないのだとしたら、「変更される前」というのがどの時点の値を指しているのか追加説明を頂けるとありがたいです。- 編集済み 魔界の仮面弁士MVP 2019年7月24日 3:13
-
たとえばデータバインドしている場合、ValueChanged は値の補正前に呼ばれることが知られています。
失礼しました。バインドの有無は関係ないですね。
numericUpDown1.DecimalPlaces = 3; numericUpDown1.Minimum = -999.99M; numericUpDown1.Maximum = 999.99M;
として状態で、ユーザーが「1.234567」と入力してフォーカスを外れた時点で ValueChanged が呼ばれ、その時点で .Text は "1.235"、.Value は 1.234567M な状態になっていました。
ただこの問題は、Timer で遅延処理しても同じ結果になるものなので、今回の質問の内容からは外れていますね。先の発言は無視してください。
-
NumericUpDown.ValueChanged Event発生時にはまだTextの値は更新されませんので、
numericUpDown1.Textではなく、numericUpDown1.Valueを使用すればよいかと。- 回答としてマーク kinto-to-clave 2019年7月24日 5:36
-
Value は試してみてるのですが記述の際にエラーが出たため出来ないと思っていました。
ですがもう一度確認しましたらエラーの原因が分かりました。
【実際のプログラム】
string str;
double d;
int len;for (i = 1; i <= 4; i++)
{
d = Convert.ToDouble(Controls[$"UpDown_A{i}"].Text);
str = Convert.ToString(d);
len = str.Length;
byte[] varu = System.Text.Encoding.ASCII.GetBytes(str);
for (j = 0; j < len; j++) { TxData[c] = varu[j]; c++; }
TxData[c] = 0xFE; c++;
}上のd = Convert.ToDouble(Controls[$"UpDown_A{i}"].Text);のTextをValueに変えた時にエラーが出ました。
ですがエラーの原因はValueはControlsでは使えないという内容でした。Controlsを使用せずdecimalで変数を宣言してValueの値を入れましたら上手く行きました。でも今回はControlsを使いたいのでどうするかは考えます。
とにかく原因がわかりました。ありがとうございました。
-
d = Convert.ToDouble(Controls[$"UpDown_A{i}"].Text);
そもそも何故 double に? NumericUpDown が扱うのは decimal ですよね。
Controls[~] から返される Control 型も、実際のコントロールの固有型にキャストして、このように書く必要があるでしょう。decimal dec = ((NumericUpDown)Controls[$"UpDown_A{i}"]).Value;
それと蛇足ではありますが、NumericUpDown でチェックされているのは「Minimum ~ Maximum の間の数値が入力されているかどうか」だけなので、DecimalPlaces についてはあまり考慮されていないことに注意しておいてください。
たとえば、DecimalPlaces が 0 に設定されている NumericUpDown に対して、プログラムから「numericUpDown1.Value = 45.678M;」と入力した場合、画面上は "46" という整数が表示されているように見えますが、内部値は 45.678 がそのまま格納されます。
この状態で「x = (int)numericUpDown.Value;」などで再取得すると、画面上は 46 なのに、変数 x には 45 が渡されるといった食い違いが発生します。また、▲▼ で値を変更させたときも、このときの小数桁は維持されたままとなります。
プログラムからではなく、手入力で「45.678」と打って Enter キーを押したような場合も同様にも、どうようの問題が生じます。
なので自分の場合は、先に述べたように UpdateEditText をオーバーライドして、小数部まで補正された値が得られるようにしています。参考までに。
- 編集済み 魔界の仮面弁士MVP 2019年7月24日 6:07
-
そもそも何故 double に? NumericUpDown が扱うのは decimal ですよね。
↑こういったおそらく常識と思われるものが私にとっては「そうなのだ!」ということだと思います。
doubleにしたのは扱う値が小数点第一位の値を対象としていたためです。
実際は10倍しています。
d = Convert.ToDouble(Controls[$"UpDown_A{i}"].Text);
d = d * 10;
str = Convert.ToString(d);また型キャストについてもまだまだ知識が追い付いていませんね。
こういった貴重なご返信を少しづつ蓄えて行きたいと思います。
大変ありがとうございます。
-
もう一歩進めて、コントロールの配列を予め作ってしまえば、キャストも要らなくなるかもです。
var upDowns = new[] { UpDown_A1, UpDown_A2, UpDown_A3, UpDown_A4, }; foreach(var upDown in upDowns){ var str = upDown.Value.ToString(); var bytes = Encoding.ASCII.GetBytes(str); bytes.CopyTo(TxData, c); c += bytes.Length; TxData[c++] = 0xFE; }
# 元のコードは整数/小数どちらを想定しているのかよくわかりませんでした。doubleを使っているので明確に小数を期待しているのでしょうか?
-
今回の手順では ToDouble で処理してもさほど問題にはならないでしょうから、無理にとは言いませんが… double を採用する積極的な理由が無い場合は、NumericUpDown.Value から返される decimal のまま扱うことをお奨めしておきます。
decimal 型は 10進小数を扱えますが、double 型は 2進小数に相当するためです。0.5 (½)や 0.25 (¼) といった値ならば 2 進表現でも誤差無く保持できますが、0.1 という値は 2 進表現では割り切れないため、double 型で扱うと誤差を生じる可能性があります。decimal なら 0.1 単位でも誤差無く表現できるのですが。
private void button1_Click(object sender, EventArgs e) { // for (decimal d = 0.0M; d < 1.0M; d += 0.1M)
for (double d = 0.0; d < 1.0; d += 0.1) { listBox1.Items.Add(d); listBox2.Items.Add(d * 10); } }
- 編集済み 魔界の仮面弁士MVP 2019年7月24日 7:28