none
Timer イベントで while ループから抜けたい RRS feed

  • 質問

  • はじめまして。E-650と申します。今回初めて投稿させて頂きます。

    過去にDelphiを使った事はあるのですが(初心者どまり)、C#に変えてみる事にしました。

    タイマーがスタートして最初のイベントで while ループを抜けるようにしたいのですが、

    試しても試してもうまく行きません。どうかよろしくお願い致します。

     

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;

    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            bool exitFlag = false;

            public Form1()
            {
                InitializeComponent();
                timer1.Enabled = false;
            }

            private void button1_Click(object sender, EventArgs e)
            {
                timer1.Enabled = true;
                timer1.Interval = 10000;
                timer1.Start();
                int i = 0;

                while (true)
                {
                    textBox1.Text = i.ToString();
                    textBox1.Update();
                    i++;

                    if (exitFlag)
                        break;
                }
               
            }

            private void timer1_Tick(object sender, EventArgs e)
            {
                exitFlag = true;
                timer1.Enabled = false;
                timer1.Stop();
            }
        }
    }

    2010年6月13日 7:06

回答

  • Stopwatch などで経過時間を計れば良いと思いますが、正直なところ、メインスレッドで無限ループに近いことを長時間やることは避けるべきです。
    応答なしと表示されたり、白くなることがあるでしょう。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク E-650 2010年6月13日 23:28
    2010年6月13日 13:26
    モデレータ
  • 2つのTimerのTickイベントと、button1のClickイベントの設定を確認してみて下さい。

    completeTimerのTick に「completeTimer_Tick」、

    cycleTimerのTick に「cycleTimer_Tick」、

    button1のClick に「button1_Click」をそれぞれ設定する必要があります。

     

    (補足)イベントが上記のように設定されていて、かつTimerのEnabledを2つともtrueに設定しておくと、ボタンを押さなくても番号表示が始まります。

    • 回答としてマーク E-650 2010年6月17日 22:49
    • 回答としてマークされていない E-650 2010年6月17日 22:50
    • 回答としてマーク E-650 2010年6月17日 22:54
    2010年6月17日 17:05

すべての返信

  • クリック処理とタイマー処理はシングルスレッドで動作しているため、button1_Click()メソッドが終了しない限り、timer1_Tick()メソッドは呼ばれません。

    2010年6月13日 8:24
  • 早速のご回答ありがとうございます。

    少し変えてみました。でも、これも駄目な様ですね。自分で考えてみます。ヒントありがとうございました。

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            bool exitFlag = false;

            public Form1()
            {
                InitializeComponent();
                timer1.Enabled = false;
            }

            private void button1_Click(object sender, EventArgs e)
            {
                Loop();
            }

            private void timer1_Tick(object sender, EventArgs e)
            {
                exitFlag = true;
                timer1.Enabled = false;
                timer1.Stop();
            }

            public void Loop()
            {
                timer1.Enabled = true;
                timer1.Interval = 10000;
                timer1.Start();
                int i = 0;

                while (! exitFlag)
                {
                    textBox1.Text = i.ToString();
                    textBox1.Update();
                    i++;
                }
                MessageBox.Show("抜けました");
            }
        }
    }

    2010年6月13日 10:37
  • そもそも何をしたいのでしょうか?
    コードを読む限り、「10秒間にいくつカウントアップできるかの確認」のように見えますが。
    もし本当にそんなことをしたらCPU使用率が100%になって何もできないプログラムにしかなりません。

    そのため一般的なGUIは逆のアプローチで、どのタイミングで更新(この場合のカウントアップ)をしたいのかという実装をします。
    なので、例えば、10msごとに更新するのであればそのようにタイマーを組みます。

    となると当初の目的の答えとしては、「10秒 / 更新タイミング」回カウントアップできる、となるため、そもそもこのコードが無意味になってしまいます。

    2010年6月13日 10:56
  • 確かに、自分でも何だか分からくなって来ました。少し頭を冷やして、整理して考えたいと思います。

    お返事ありがとうございます。

    2010年6月13日 11:33
  • もう少し具体的に書いてみました。ビンゴのソフトの一部です。

    アルゴリズムの勉強をもっともっとした方がいいと思いました。

    基礎からやるべきだと痛感しました。

    開始時間を記録して、終了時間(暫定的に15秒間)との差で while ループを抜けられるように考えます。

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            bool[] ball = new bool[76];

            public Form1()
            {
                InitializeComponent();

                for (int i = 1; i < 76; i++)
                {
                    ball[i] = true;
                }
            }

            private void button1_Click(object sender, EventArgs e)
            {
                byte num;
                int seed = Environment.TickCount;
                Random rnd = new Random(seed);

                // (暫定的に)15秒という長さのwavファイルを再生する
                //
                // 音の再生の処理をここに書く。音が鳴っている間も数字を表示して行く

                while (true) // 15秒間はこれを繰り返す
                {
                    num = (byte)rnd.Next(75 + 1);
                    if (ball[num])
                    {
                        ball[num] = false;
                        textBox1.Text = num.ToString();
                        System.Threading.Thread.Sleep(70);
                        textBox1.Update();
                    }

                }

                // 15秒経ったらここに来るようにしたい
            }
        }
    }

    • 編集済み E-650 2010年6月13日 12:22 舌足らずな文章を補足
    2010年6月13日 12:07
  • Stopwatch などで経過時間を計れば良いと思いますが、正直なところ、メインスレッドで無限ループに近いことを長時間やることは避けるべきです。
    応答なしと表示されたり、白くなることがあるでしょう。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク E-650 2010年6月13日 23:28
    2010年6月13日 13:26
    モデレータ
  • アドバイスありがとうございます。

    Delphi には

    Application.ProcessMessage();

    というのがあって、これで Windows に制御を移す事が出来る様です。

    実際、使っていました。

    > 正直なところ、メインスレッドで無限ループに近いことを長時間やることは避けるべきです。
    > 応答なしと表示されたり、白くなることがあるでしょう。

    参考になりました。肝に銘じます。ありがとうございます。

    2010年6月13日 23:28
  • Delphi には
    Application.ProcessMessage();
    というのがあって、これで Windows に制御を移す事が出来る様です。
    名前から察するに、.NET Framework の Application.DoEvents と同等のことをしているのでしょうか。
    ただし、Application.DoEvents の利用は避けるべきです。
    安易な利用は不具合を招くだけです。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年6月14日 13:53
    モデレータ
  • Delphi には
    Application.ProcessMessage();
    というのがあって、これで Windows に制御を移す事が出来る様です。
    名前から察するに、.NET Framework の Application.DoEvents と同等のことをしているのでしょうか。
    ただし、Application.DoEvents の利用は避けるべきです。
    安易な利用は不具合を招くだけです。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    遅くなりすみません。では、どのようにするのがいいのでしょうか? ヒントだけでも頂けたらと思います。

    どうかよろしくお願い致します。

    2010年6月16日 11:38
  • まず、15 秒間全力で数字を選び続ける必要はありません。
    画面に反映されるのに時間がかかるし、そもそも人の目に数ミリ秒での変化は捉えられません。

    Timer あたりで、100ms とか、500ms とか、求めるスピードを満たしつつ、やり過ぎない間隔(Interval)で変更していけば良いでしょう。
    15 秒経過後の最初の Tick イベントでタイマーを止めれば、15 秒間は数値をランダムで選択しつつ、無限ループにならない構造になるでしょう。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年6月16日 13:34
    モデレータ
  • E-650さま、こんにちは。

     

    試しにコードを書いてみたら、こんな風になりました。

    ご参考になれば幸いです。

    public partial class Form1 : Form
    {
      public Form1()
      {
        InitializeComponent();
      }
    
      private void button1_Click(object sender, EventArgs e)
      {
        completeTimer.Start();
        cycleTimer.Start();
      }
    
      private void completeTimer_Tick(object sender, EventArgs e)
      {
        completeTimer.Stop();
        cycleTimer.Stop();
    
        Button1ClickComplete();
      }
    
      private void Button1ClickComplete()
      {
        MessageBox.Show("完了しました");
      }
    
      NumberSelector selector = new NumberSelector();
    
      private void cycleTimer_Tick(object sender, EventArgs e)
      {
        int number = selector.SelectNumber();
        textBox1.Text = number.ToString();
      }
    
      private class NumberSelector
      {
        List<int> numbers = Enumerable.Range(1, 75).ToList();
        Random random = new Random();
    
        int selection;
    
        public int SelectNumber()
        {
          selection = numbers[random.Next(numbers.Count)];
          return selection;
        }
      }
    }
    

    このコードでは、Timerを2つ使っています。

    数字を更新するTimer(cycleTimer)と、 そのTimerを止めるTimer(completeTimer)です。

    Intervalをそれぞれ適当な値に設定してお試し下さい。

     

    whileループは使わずにすみました。

    2010年6月16日 21:27
  • Azulean さんと Alfred360 さんの返信を参考に致します。

    ありがとうございます。

    2010年6月17日 10:48
  • はじめまして。ご丁寧にありがとうございます。

    早速試してみました。

    cycleTimer を 500

    completeTimer を 10000

    にしてみました。

    ボタンを押しても表示が出ず、10000ms 待っても

    MessageBox.Show("完了しました");

    が出ませんでした。2つのタイマーの Enabled は true にしています。

    タイマーの Interval をいろいろ変えてみましたが今の所、うまく行っていません。

     

    せっかくご丁寧に対応して下さってありがたいと思い、返信させて頂きました。

    リストのコピーミスとかはないと思います。こちらでも何か自分のミスがあるかもしれないと思い、

    考えています。リスト中にまだ知らないC#の機能がありますので、それも自分で詳しく勉強したい

    と思います。

    2010年6月17日 11:23
  • Interval を設定し、Enabled = true で何も起きないのであれば、Tick イベントを設定できていないだけでは?
    コードに書いただけでは、配置した Timer との関連づけはできません。
    必ず、プロパティの雷アイコンを押して Tick イベントのところに割り付けるなどの対応をしてください。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年6月17日 14:09
    モデレータ
  • 2つのTimerのTickイベントと、button1のClickイベントの設定を確認してみて下さい。

    completeTimerのTick に「completeTimer_Tick」、

    cycleTimerのTick に「cycleTimer_Tick」、

    button1のClick に「button1_Click」をそれぞれ設定する必要があります。

     

    (補足)イベントが上記のように設定されていて、かつTimerのEnabledを2つともtrueに設定しておくと、ボタンを押さなくても番号表示が始まります。

    • 回答としてマーク E-650 2010年6月17日 22:49
    • 回答としてマークされていない E-650 2010年6月17日 22:50
    • 回答としてマーク E-650 2010年6月17日 22:54
    2010年6月17日 17:05
  • 手取り足取り、本当にありがとうございました。

    きちんと動きました。やはり基礎が全く出来てなかったっです。

    基礎を固める為に頑張りたいと思います。いつの日か他の人のフォローが出来る様に

    なりたいです。感謝しています。ありがとうございます。

    2010年6月17日 22:54
  • 返信ありがとうございます。Delphi もそうでした。うっかりしていました。

     

    2010年6月17日 22:56