89833


**開発環境**  
windows10  
Visualstudio

**作成物について**  
指定した時刻と現在時刻を比較して同じになった場合、アラーム画面を表示する目覚まし時計のフォームアプリを作成しています。

**問題点**  
別のスレッドからコントロールへアクセスするとSystem.InvalidOperationExceptionになります。  
下記のコードでform1_Loadメソッドの

[code]

    timer.Elapsed += new ElapsedEventHandler(timer_Tick);
    
[/code]

からtimer_Tickメソッドを呼び足していると思うのですが、これをするとtimer_nowメソッドの

[code]

    time_now.Text = time;
    
[/code]

の行でSystem.InvalidOperationExceptionになります。

Windowsフォームのコントロールは、メインスレッドから行う必要があるのは  
分かるのですが、今回の私のコードの場合、どのように改善するべきかがわかりません。

どのようにすれば正常に動作しますでしょうか？

**実際のコード**

[code]

    using System;
    using System.Timers;
    namespace Alarm_Clock
    {
        public partial class Form1 : Form {
    
            
            public static class Global
            {
                public static string datetime_now = "";
                public static string Alarm_set = "";
            }
    
    
        
            public Form1()
            {
                InitializeComponent();
            }
    
            delegate void delegate1(String time);
            private void Form1_Load(object sender, EventArgs e)
            {
         
                System.Timers.Timer timer = new System.Timers.Timer(1000);
    
                timer.Elapsed += new ElapsedEventHandler(timer_Tick);
    
                timer.AutoReset = false;
                timer.Enabled = true;
    
                
    
            }
    
    
    
            private void timer_Tick(object sender, EventArgs e)
            {
                DateTime d = DateTime.Now;
                int hh = d.Hour;
                int mm = d.Minute;
    
                string time = "";
                if (hh < 10)
                {
                    time += "0" + hh;
                }
                else
                {
                    time += hh;
                }
                time += ":";
    
                if (mm < 10)
                {
                    time += "0" + mm;
                }
                else
                {
                    time += mm;
                }
                
    
                Global.datetime_now = time.ToString();//String型に変換する
                time_now.Text = time; //dで時間と分を表示
    
    
                if (onbutton.Checked == true)
                {
                    if (Global.datetime_now == Global.Alarm_set)
                    {
                        Form2 form2 = new Form2();
                        form2.ShowDialog();
                        
    
                    }
                    
                }
                
    
            }
    
            private void button_Click(object sender, EventArgs e)
            {
                Global.Alarm_set = textbox.Text;//アラーム時刻取得
                if (Global.Alarm_set == "")
                {
                    exc.Text = "時刻を入力してください";
                    return;
                }
    
                //入力時刻の時とコロンと分を文字列で取得
                String h_time = Global.Alarm_set.Substring(0, 2);
                String Colon = Global.Alarm_set.Substring(2, 1);
                String m_time = Global.Alarm_set.Substring(3);
    
                //取得した時と分を数値に変換
                int hh_time = int.Parse(h_time);
                int mm_time = int.Parse(m_time);
    
    
                //時と分が入力範囲内であるか。コロンが入力されているか
                
                if ((hh_time >= 0 && hh_time < 24) && (Colon == ":") && (mm_time >= 0 && mm_time < 60))
                {
                    String Alarm = "アラーム時刻：" + Global.Alarm_set;
                    Alarmtime.Text = Alarm;//アラーム時刻表示
                    exc.Text = "";//エラー文非表示
                }
    
                else
                {
                    exc.Text = "正しく時刻を入力してください";
                }
    
    
                
    
            }
       }
    }
    
[/code]

**追記**  
アラームを作成するのであればSystem.Windows.Forms.Timerを使用するのが良いとのことなのでそちらで書き直しました。

以下のコードを実行すると、エラーなくシステムが起動し、実行ボタンを押してform2のアラーム画面を表示することができました。  
しかし、こちらのコードであると、現在時刻と設定したアラーム時刻が同じである限り、一秒に一画面連続で複数のアラーム画面が表示されてしまいます。

当初私もSystem.Windows.Forms.Timerで作成したのですが、  
timer.AutoReset = False;が使用できなかったため他の手段を使用しました。  
以下のSystem.Windows.Forms.Timerを使用したコードで一度飲み画面にアラーム画面を表示するにはどうしたらよいでしょうか？  
**実際のコード**

[code]

    using System;
    using System.Windows.Forms;
    
    
    namespace Alarm_Clock
    {
        public partial class Form1 : Form {
    
            
            public static class Global
            {
                public static string datetime_now = "";
                public static string Alarm_set = "";
            }
    
    
        
            public Form1()
            {
                InitializeComponent();
            }
    
            
            private void Form1_Load(object sender, EventArgs e)
            {
                System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
                timer.Interval = 1000;
                timer.Enabled = true;
                timer.Tick += new EventHandler(timer_Tick);
                timer.Start();
    
    
                
                
    
                
    
            }
    
    
    
            private void timer_Tick(object sender, EventArgs e)
            {
                DateTime d = DateTime.Now;
                int hh = d.Hour;
                int mm = d.Minute;
    
                string time = "";
                if (hh < 10)
                {
                    time += "0" + hh;
                }
                else
                {
                    time += hh;
                }
                time += ":";
    
                if (mm < 10)
                {
                    time += "0" + mm;
                }
                else
                {
                    time += mm;
                }
                
    
                Global.datetime_now = time.ToString();//String型に変換する
                time_now.Text = time; //dで時間と分を表示
    
    
                if (onbutton.Checked == true)
                {
                    if (Global.datetime_now == Global.Alarm_set)
                    {
                        Form2 form2 = new Form2();
                        form2.ShowDialog();
                        
    
                    }
                    
                }
                
    
            }
    
            private void button_Click(object sender, EventArgs e)
            {
                Global.Alarm_set = textbox.Text;//アラーム時刻取得
                if (Global.Alarm_set == "")
                {
                    exc.Text = "時刻を入力してください";
                    return;
                }
    
                //入力時刻の時とコロンと分を文字列で取得
                String h_time = Global.Alarm_set.Substring(0, 2);
                String Colon = Global.Alarm_set.Substring(2, 1);
                String m_time = Global.Alarm_set.Substring(3);
    
                //取得した時と分を数値に変換
                int hh_time = int.Parse(h_time);
                int mm_time = int.Parse(m_time);
    
    
                //時と分が入力範囲内であるか。コロンが入力されているか
                
                if ((hh_time >= 0 && hh_time < 24) && (Colon == ":") && (mm_time >= 0 && mm_time < 60))
                {
                    String Alarm = "アラーム時刻：" + Global.Alarm_set;
                    Alarmtime.Text = Alarm;//アラーム時刻表示
                    exc.Text = "";//エラー文非表示
                }
    
                else
                {
                    exc.Text = "正しく時刻を入力してください";
                }
    
    
                
    
            }
       }
    }
    
    
[/code]


CC BY-SA 4.0
2022-07-08T01:26:57.313
89825
2022-07-08T05:41:39.460
2022-07-08T05:40:30.773
47546
47546
question
c# visual-studio
別スレッドからコントロールにアクセスするとSystem.InvalidOperationExceptionになる


WinForms なら `System.Windows.Forms.Timer` にするのが楽かと。これだと UI
スレッド上でタイマイベントが発行されるのでスレッドまたぎを気にしなくて済み余計なコードを書かずに済みます（これがあなたの要望に合致するかは別途検討）

* * *

えっとタイマーの類をデザイナ画面を使わずに手書きしているのでしょうか？提示コードでは `System.Windows.Forms.Timer timer =
new System.Windows.Forms.Timer();` が `Form1_Load()` の中に記述されています。これだと `timer` は
(C/C++ でいう) 自動変数であって `Form1_Load()` から脱出した時点で gc 対象になってしまいます（＝ `timer`
はそのうち処分されてしまい機能しなくなります）

デザイナ画面でツールボックス→コンポーネントから `Timer` を配置すると正しく `Form1`
のメンバとして追加されますので途中で機能しなくなるようなことはありません。


CC BY-SA 4.0
2022-07-08T01:49:25.353
89828
2022-07-08T04:04:12.050
2022-07-08T04:04:12.050
8589
8589
89825
answer


`System.Windows.Forms.Timer`を使えない場合の処理として、[`MethodInvoker`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.windows.forms.methodinvoker?view=windowsdesktop-6.0)を使う方法もあります。  
下記のサンプルコードは、フォームに`time_now`テキストボックスを貼り付けて実行すると`time_now`の時刻を更新し続けます。

**サンプルコード**

[code]

    using System;
    using System.Timers;
    using System.Windows.Forms;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                Load += Form1_Load;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                System.Timers.Timer timer = new System.Timers.Timer(1000);
    
                timer.Elapsed += new ElapsedEventHandler(timer_Tick);
    
                timer.AutoReset = true; //繰り返し実行
                timer.Enabled = true;
            }
    
            private void timer_Tick(object sender, ElapsedEventArgs e)
            {
                // メインスレッドで実行する処理を記述
                MethodInvoker updateClock = () =>
                {
                    time_now.Text = DateTime.Now.ToString("HH:mm:ss");
                };
                // MethodInvokerを呼び出す
                time_now.Invoke(updateClock);
            }
        }
    }
    
[/code]

参考資料:

  * [スレッド セーフなコントロールの呼び出し](https://so-zou.jp/software/tech/programming/c-sharp/thread/thread-safe-call.htm)


CC BY-SA 4.0
2022-07-08T02:16:29.430
89829
2022-07-08T02:36:53.083
2022-07-08T02:36:53.083
9820
9820
89825
answer


これでどうでしょうか？

[code]

    this.Invoke((Action)delegate ()
    {
        time_now.Text = time;
    }
    
[/code]


CC BY-SA 4.0
2022-07-08T05:41:39.460
89833
2022-07-08T05:41:39.460
24490
89825
answer