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

わんくま同盟

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

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

ツリー一括表示

rgbによるbackcolorの表示 /C#初心者 (19/10/08(Tue) 15:04) #92572
Re[1]: rgbによるbackcolorの表示 /魔界の仮面弁士 (19/10/08(Tue) 15:40) #92573
│├ Re[2]: rgbによるbackcolorの表示 /大谷刑部 (19/10/08(Tue) 16:35) #92577
││└ Re[3]: rgbによるbackcolorの表示 /魔界の仮面弁士 (19/10/08(Tue) 17:45) #92578
││  └ Re[4]: rgbによるbackcolorの表示 /大谷刑部 (19/10/09(Wed) 09:35) #92584
│└ Re[2]: rgbによるbackcolorの表示 /C#初心者 (19/10/08(Tue) 16:24) #92575
Re[1]: rgbによるbackcolorの表示 /大谷刑部 (19/10/08(Tue) 16:04) #92574
  ├ Re[2]: rgbによるbackcolorの表示 /C#初心者 (19/10/08(Tue) 16:25) #92576
  └ Re[2]: rgbによるbackcolorの表示 /まりもん (19/10/09(Wed) 10:00) #92586
    ├ Re[3]: rgbによるbackcolorの表示 /C#初心者 (19/10/09(Wed) 10:46) #92588 解決済み
    └ Re[3]: rgbによるbackcolorの表示 /魔界の仮面弁士 (19/10/09(Wed) 12:04) #92593 解決済み


親記事 / ▼[ 92573 ] ▼[ 92574 ]
■92572 / 親階層)  rgbによるbackcolorの表示
□投稿者/ C#初心者 (1回)-(2019/10/08(Tue) 15:04:05)

分類:[C#] 

C# visual studio 2019

C#を初めて間もない初心者です。

C#でのアプリケーション作成でtextboxのBackColorをグラデーション形式で黒から白、白から黒に変更するものを作成したいのですが、途中の表示がうまくいきません。

今のところ
private void Button1_Click_1(object sender, EventArgs e)
{
int j = 255;

for (int i = 0; i <= 255; i++)
{
Thread.Sleep(100);
textBox1.BackColor = Color.FromArgb(i, i, i);
textBox2.BackColor = Color.FromArgb(j, j, j);
textBox3.BackColor = Color.FromArgb(j, j, j);
textBox4.BackColor = Color.FromArgb(i, i, i);
j--;
}
といった形で記述しているのですが、クリックを押して一定時間がたつと色が一気に白から黒、黒から白に代わってしまいます。

なぜこのようになってしまうのかアドバイスを頂けたらと思います。
[ □ Tree ] 返信 編集キー/

▲[ 92572 ] / ▼[ 92577 ] ▼[ 92575 ]
■92573 / 1階層)  Re[1]: rgbによるbackcolorの表示
□投稿者/ 魔界の仮面弁士 (2416回)-(2019/10/08(Tue) 15:40:33)
No92572 (C#初心者 さん) に返信
> C#でのアプリケーション作成でtextboxのBackColorを

背景色だけでなく、文字色も考慮した方がよいかと。


> グラデーション形式で黒から白、白から黒に変更するものを作成したいのですが、途中の表示がうまくいきません。

画面描画は「イベント処理を抜けた後」のアイドル時に行われるためです。
処理中(ビジー状態)は直ちに反映されません。


100 ミリ秒ごとに背景色を変えたいのであれば、ループ処理で実行するのではなく、
Timer を画面に貼り、Interval を 100ミリ秒程度にした上で、
Tick イベントが発生するたびに、BackColor を変更するようにしてみてください。


> Thread.Sleep(100);

画面上 (UI スレッド)から Thread.Sleep を呼び出してはいけません。
また、Click イベントに長時間(数秒以上)の長い処理を行わせるのも避けましょう。
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92573 ] / ▼[ 92578 ]
■92577 / 2階層)  Re[2]: rgbによるbackcolorの表示
□投稿者/ 大谷刑部 (38回)-(2019/10/08(Tue) 16:35:24)
No92573 (魔界の仮面弁士 さん) に返信
> ■No92572 (C#初心者 さん) に返信
>>グラデーション形式で黒から白、白から黒に変更するものを作成したいのですが、途中の表示がうまくいきません。
>
> 画面描画は「イベント処理を抜けた後」のアイドル時に行われるためです。
> 処理中(ビジー状態)は直ちに反映されません。
なので、sleepとプロパティーの設定だけだと肉眼で確認する前に次の色に変わっているという現象になっていると想像されます。

> 100 ミリ秒ごとに背景色を変えたいのであれば、ループ処理で実行するのではなく、
> Timer を画面に貼り、Interval を 100ミリ秒程度にした上で、
> Tick イベントが発生するたびに、BackColor を変更するようにしてみてください。
こっちのやり方の方が結果的に書くコードはシンプルになるのかもしれませんね。
ただまあ、この程度の処理(白⇔黒)であれば、メモリ負荷とかにそれほどクリティカルな影響はないのforループでもいいんじゃないでしょうか?
3原色分全部ループ(255の3重ループ)とかなら明らかにやめた方がいいとは思いますが。

>>Thread.Sleep(100);
>
> 画面上 (UI スレッド)から Thread.Sleep を呼び出してはいけません。
win7 32bitの環境で試した(refleshで肉眼確認)限りでは、
1000MS(1秒)では変化は細かいが若干間延びしてイライラする、10MSだと変化が速すぎてわかりずらい感じを受けるので
どういう手段をとるかは別として間隔は100MSで妥当な気はします。
ただし、マシンスペック、OSが32bitか64bitで見える印象は結構変わると思いますので、質問者さん自身で試して妥当な線を決めた方がいいと思います。

> また、Click イベントに長時間(数秒以上)の長い処理を行わせるのも避けましょう。
クリックイベントならそこまでクリティカルに考えなくてもよいような(あくまで主観ですが)
Form_Load、XX_Activateなど画面全体の描画に絡むイベントでは絶対やらない方がいいですね。
グリッドにデータ反映とかでも行数多すぎるとたまに描画に絡むエラーになったりしますからね。





[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92577 ] / ▼[ 92584 ]
■92578 / 3階層)  Re[3]: rgbによるbackcolorの表示
□投稿者/ 魔界の仮面弁士 (2417回)-(2019/10/08(Tue) 17:45:17)
2019/10/08(Tue) 19:43:03 編集(投稿者)

No92577 (大谷刑部 さん) に返信
> なので、sleepとプロパティーの設定だけだと肉眼で確認する前に次の色に変わっているという現象になっていると想像されます。

いいえ。イベント処理中は、そもそもメッセージループが処理されないので、
肉眼でも心眼でも変化しません。背景色が実際に変わるのは、
イベントを抜けた後(のアイドル時)になります。

Click イベントの最中に強制的に再描画させたいのであれば、
BackColor の変更後に Update メソッドを呼ぶなどの処置が必要です。


// ListBox と Button を貼り、
// Button の Click イベントを割り当てておきます
public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
    Controls.Add(editBox1 = new EditBox(listBox1) { Name = "editBox1" });
  }

  private void button1_Click(object sender, EventArgs e)
  {
    listBox1.Items.Insert(0, "==> button1_Click");
    for (int i = 0; i <= 255; i++)
    {
      editBox1.BackColor = Color.FromArgb(i, i, i);
      // editBox1.Update();
    }
    listBox1.Items.Insert(0, "<== button1_Click");
  }

  // TextBox に届く Windows Message を捉えて表示する実験
  private EditBox editBox1;
  private class EditBox : TextBox
  {
    private ListBox listBox1;
    public EditBox(ListBox listBox1) { this.listBox1 = listBox1; }

    private int Count = 0;
    protected override void WndProc(ref Message m)
    {
      // 受信したメッセージを、親フォームの ListBox に表示
      listBox1.Items.Insert(0, ++Count + "\t" + m.ToString());
      base.WndProc(ref m);
    }
  }
}



> ただまあ、この程度の処理(白⇔黒)であれば、メモリ負荷とかに
> それほどクリティカルな影響はないのforループでもいいんじゃないでしょうか?

『await Task.Delay(100);』とかであればいざ知らず、元の処理は
『Thread.Sleep(100);』を使っている点が問題になるかと思います。

OS にとってのメモリ負荷や CPU 負荷が軽微であったとしても、
元のコードだと 256 × 100msec なのですから、Click するだけで
25 秒以上も自アプリが待機状態(フリーズ状態)になりますよね…?

そもそも Sleep を実施している間、画面の再描画も行われないため、
このような待ち合わせ方法には、あまり意味が無いでしょう。

Sleep はスレッドの動作を一時的に止めてしまうため、
先のループ処理の間、キーボード入力やフォームの移動はおろか、
OS の再起動要求も受けられないことになるわけで、
Windows App の実装方法としては、かなり問題があるかと思います。



Sleep メソッドのリファレンスより引用:
> このメソッドは、標準 COM/SendMessage ポンピングを実行しません。

Sleep API のリファレンスより引用:
> Sleep 関数と、ウィンドウを直接的または間接的に作成するコードを組み合わせて
> 使う場合は、注意が必要です。1 つのスレッドがウィンドウを作成した場合、
> そのスレッドはそのウィンドウに関係するメッセージを処理しなければなりません。
> また、メッセージのブロードキャスト(同報送信)が発生した場合、
> システム内のすべてのウィンドウへそのメッセージが送信されます。


ウィンドウメッセージを受け取らないスレッド
(コンソールアプリやワーカースレッド等)であれば、
Sleep メソッドで待機しても問題ありません。
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92578 ] / 返信無し
■92584 / 4階層)  Re[4]: rgbによるbackcolorの表示
□投稿者/ 大谷刑部 (39回)-(2019/10/09(Wed) 09:35:05)
No92578 (魔界の仮面弁士 さん) に返信
> 2019/10/08(Tue) 19:43:03 編集(投稿者)
>
> ■No92577 (大谷刑部 さん) に返信
>>なので、sleepとプロパティーの設定だけだと肉眼で確認する前に次の色に変わっているという現象になっていると想像されます。
>
> いいえ。イベント処理中は、そもそもメッセージループが処理されないので、
> 肉眼でも心眼でも変化しません。背景色が実際に変わるのは、
> イベントを抜けた後(のアイドル時)になります。
そんなことはないでしょう、refleshで見た目は変わりましたよ。

> OS にとってのメモリ負荷や CPU 負荷が軽微であったとしても、
> 元のコードだと 256 × 100msec なのですから、Click するだけで
> 25 秒以上も自アプリが待機状態(フリーズ状態)になりますよね…?
人の感覚次第かもしれませんが、質問者さんは色の移り変わりを見たいのでしょ?
であれば25秒はさほど長いとは思いません。
仮に、お客さんに納品するPGとしても、そうそう、数秒でイラつくお客さんばっかりじゃないでしょう。
現実問題、4分以上処理中メッセージも何も出ず、ただひたすら砂時計が処理中モードになるだけというプログラムは実際に存在します。

> そもそも Sleep を実施している間、画面の再描画も行われないため、
> このような待ち合わせ方法には、あまり意味が無いでしょう。
>
> Sleep はスレッドの動作を一時的に止めてしまうため、
> 先のループ処理の間、キーボード入力やフォームの移動はおろか、
> OS の再起動要求も受けられないことになるわけで、
> Windows App の実装方法としては、かなり問題があるかと思います。
問題はおっしゃるような厳密性がこのケースで必要かどうかです。

質問者もタイマーイベントで解決とおっしゃっているので、Sleepの是非についてはこのあたりでよろしいんじゃないでしょうか?


[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92573 ] / 返信無し
■92575 / 2階層)  Re[2]: rgbによるbackcolorの表示
□投稿者/ C#初心者 (2回)-(2019/10/08(Tue) 16:24:14)
No92573 (魔界の仮面弁士 さん) に返信
> ■No92572 (C#初心者 さん) に返信
>>C#でのアプリケーション作成でtextboxのBackColorを
>
> 背景色だけでなく、文字色も考慮した方がよいかと。
>
>
>>グラデーション形式で黒から白、白から黒に変更するものを作成したいのですが、途中の表示がうまくいきません。
>
> 画面描画は「イベント処理を抜けた後」のアイドル時に行われるためです。
> 処理中(ビジー状態)は直ちに反映されません。
>
>
> 100 ミリ秒ごとに背景色を変えたいのであれば、ループ処理で実行するのではなく、
> Timer を画面に貼り、Interval を 100ミリ秒程度にした上で、
> Tick イベントが発生するたびに、BackColor を変更するようにしてみてください。
>
>
>>Thread.Sleep(100);
>
> 画面上 (UI スレッド)から Thread.Sleep を呼び出してはいけません。
> また、Click イベントに長時間(数秒以上)の長い処理を行わせるのも避けましょう。


ご返信ありがとうございます。
先ほどtimerを使用したやり方を試したところ無事に表示することができました。

迅速に回答いただき本当にありがとうございます。
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92572 ] / ▼[ 92576 ] ▼[ 92586 ]
■92574 / 1階層)  Re[1]: rgbによるbackcolorの表示
□投稿者/ 大谷刑部 (36回)-(2019/10/08(Tue) 16:04:19)
No92572 (C#初心者 さん) に返信
> といった形で記述しているのですが、クリックを押して一定時間がたつと色が一気に白から黒、黒から白に代わってしまいます。
>
> なぜこのようになってしまうのかアドバイスを頂けたらと思います。

環境(PCの処理能力等)にもよりますが、プロパティーの設定上の色の変更スピードに描画が追い付いてないだけに思えるので、
設定後にそれぞれのテキストボックスをRefreshするようにすれば、肉眼で確認はできるようになると思います。

弁さんのおっしゃる技術的べき論はひとまず置いておいての話ですが。
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92574 ] / 返信無し
■92576 / 2階層)  Re[2]: rgbによるbackcolorの表示
□投稿者/ C#初心者 (3回)-(2019/10/08(Tue) 16:25:55)
No92574 (大谷刑部 さん) に返信
> ■No92572 (C#初心者 さん) に返信
>>といった形で記述しているのですが、クリックを押して一定時間がたつと色が一気に白から黒、黒から白に代わってしまいます。
>>
>>なぜこのようになってしまうのかアドバイスを頂けたらと思います。
>
> 環境(PCの処理能力等)にもよりますが、プロパティーの設定上の色の変更スピードに描画が追い付いてないだけに思えるので、
> 設定後にそれぞれのテキストボックスをRefreshするようにすれば、肉眼で確認はできるようになると思います。
>
> 弁さんのおっしゃる技術的べき論はひとまず置いておいての話ですが。

ご返信ありがとうございます。
timerを使用したところ無事に表示することができ来ました。

描画処理についても今使用しているOCでは若干不安なところもありますので、今後表示時間を変更等する際に参考にさせていただきます。

ありがとうございます。
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92574 ] / ▼[ 92588 ] ▼[ 92593 ]
■92586 / 2階層)  Re[2]: rgbによるbackcolorの表示
□投稿者/ まりもん (1回)-(2019/10/09(Wed) 10:00:51)
No92574 (大谷刑部 さん) に返信
> ■No92572 (C#初心者 さん) に返信
>>といった形で記述しているのですが、クリックを押して一定時間がたつと色が一気に白から黒、黒から白に代わってしまいます。
>>
>>なぜこのようになってしまうのかアドバイスを頂けたらと思います。
>
> 環境(PCの処理能力等)にもよりますが、プロパティーの設定上の色の変更スピードに描画が追い付いてないだけに思えるので、
> 設定後にそれぞれのテキストボックスをRefreshするようにすれば、肉眼で確認はできるようになると思います。
>
> 弁さんのおっしゃる技術的べき論はひとまず置いておいての話ですが。

このような書き方ですと、質問者さんの提示されたコードでも環境によっては色が変化するように取れてしまいますが
それはありえないのではないですか?
提示のコードプロパティの値を変化させてただけでは再描画が行われるタイミングは魔界の仮面弁士さんのおっしゃるように
イベントを抜けた後になります。
Refreshすれば変わると仰ってますが、Refreshして再描画をそこで行うようにしているからで、プロパティを変化させただけで
再描画がそのタイミングで行われるわけではないでしょう
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92586 ] / 返信無し
■92588 / 3階層)  Re[3]: rgbによるbackcolorの表示
□投稿者/ C#初心者 (4回)-(2019/10/09(Wed) 10:46:28)
様々な意見をいただいていますが、自分はまだ始めたばかりなのでわからないことが多いです。
現状としては目的の動きをさせることができましたので、またわからないことがあれば質問させていただきます。
解決済み
[ 親 92572 / □ Tree ] 返信 編集キー/

▲[ 92586 ] / 返信無し
■92593 / 3階層)  Re[3]: rgbによるbackcolorの表示
□投稿者/ 魔界の仮面弁士 (2420回)-(2019/10/09(Wed) 12:04:12)
2019/10/09(Wed) 16:47:18 編集(投稿者)

No92584 (大谷刑部 さん) に返信
> 問題はおっしゃるような厳密性がこのケースで必要かどうかです。
> 質問者もタイマーイベントで解決とおっしゃっているので、

理屈までいきなり説明しても追いきれないでしょうし、初心者向けには、
『Form 上で Sleep を呼んではいけない』と伝えるようにしています。
>>> 画面上 (UI スレッド)から Thread.Sleep を呼び出してはいけません。

この原則を守ることで問題になることは無いと思いますよ。


そのための代替案としては、
案1: Thread.Sleep(t) の代わりに await Task.Delay(t) を使ってもらう
案2: ループ処理を分割して、Timer でのイベント呼び出しに置き換えてもらう
案3: Sleep 処理を BackgroundWorker 内に押し込んで、BeginInvoke で BackColor を変更させる
などが思い当たります。
No92573 の提案は案2 でしたが No92578 で案1 にも少しだけ言及しています。


=== 以下蛇足 ===

>>背景色が実際に変わるのは、
>>イベントを抜けた後(のアイドル時)になります。
> そんなことはないでしょう、refleshで見た目は変わりましたよ。

大文字小文字もスペルも間違っているようですが、それはさておき。

メッセージさえ処理されればよいので、Application.DoEvents(); や
textBox1.Update(); や textBox1.Refresh(); などを呼び出せば、
イベントを抜ける前であっても再描画処理が行われます。

それが先の回答の
>> Click イベントの最中に強制的に再描画させたいのであれば、
>> BackColor の変更後に Update メソッドを呼ぶなどの処置が必要です。
という箇所に当たります。この点については、Sleep を呼んでいるかどうかは影響しません。


一方、元質問者である C#初心者 さんのコードでは、Refresh や Update 等の
呼び出しが行われていたわけではありませんので、
>> イベントを抜けた後(のアイドル時)になります。
という説明にさせていただきました。


実際に、先の実験コードを使って確認してもいますが、
イベント脱出後でないと WndProc が処理されないことを確認しています。
ループ中で Update や Refresh した場合は、即時処理されるのですけれどね。

Refresh は Invalidate + Update を呼び出したのと同義となりますが、
今回のケースでは、BackColor の変更が発生しているわけなので、
Update だけでも事足りると思います。

とはいえ、Update や Refresh によって処理されるメッセージは描画関係に限定されます。

Label ならまだしも、今回の対象は TextBox ですから、
その他の処理(ユーザー入力等)も阻害されてしまうのはまずいので、
どちらにせよ Sleep の出番は無さそうです。


> 人の感覚次第かもしれませんが、質問者さんは色の移り変わりを見たいのでしょ?
> であれば25秒はさほど長いとは思いません。
> 仮に、お客さんに納品するPGとしても、そうそう、数秒でイラつくお客さんばっかりじゃないでしょう。

何か長い処理をしている間、次の処理が行えないとか、
砂時計カーソルになるとか、キャンセルできないといった話をしているなら、
それは別に構わないと思います。(それは単にアプリの仕様というだけの話)

背景色が変化していくアニメーション効果として、
25 秒間かけて変化させることもまったく問題ありません。問題なのは
25 秒間応答不能になってしまう点のみであり、「長い処理の間」も
メッセージループを回すような仕組みにさえなっていれば OK です。

(たとえば HWND_BROADCAST 向けのメッセージを止めてしまうと、
 自アプリは良くても、他アプリに迷惑がかかります)
解決済み
[ 親 92572 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -