WinFormsのTimerで学ぶイベント処理の基本
Windows Formsでは、一定時間ごとに処理を実行したい場面があります。
例えば、
- 時計を1秒ごとに更新する
- 一定時間ごとに画面を更新する
- ゲームやアニメーションの処理を定期的に行う
- 一定時間経過したらメッセージを表示する
といった処理です。
このような処理では、System.Windows.Forms.Timerを使用できます。
今回は、1秒ごとにイベントを発生させる簡単なプログラムを使って、Timerだけでなく、C#のイベント処理の仕組みについても確認していきます。

- 1. サンプルコード
- 2.
using Timer = System.Windows.Forms.Timer;とは - 3. なぜTimerに別名を付けるのか
- 4. Timerオブジェクトを作る
- 5. なぜローカル変数ではなくフィールドにするのか
- 6. Intervalで時間間隔を設定する
- 7. Tickイベント
- 8.
+=は何をしているのか - 9. イベントハンドラーとは
- 10. メソッドを呼び出していないことに注意
- 11. イベント発生時の流れ
- 12. Startメソッド
- 13. なぜイベント登録をStartより先に書くのか
- 14.
senderとは何か - 15.
object?の?は何か - 16.
EventArgs eとは - 17. イベントハンドラーには決められた形がある
- 18. Tickイベントの内部ではデリゲートが使われている
- 19. ボタンのClickイベントも同じ仕組み
- 20.
+=で複数のメソッドを登録できる - 21.
-=でイベントを解除できる - 22. 匿名関数でも書ける
- 23. ラムダ式で書く
- 24. 名前付きメソッドとラムダ式の使い分け
- 25. Windows FormsのTimerの特徴
- 26. 時計を表示してみる
- 27. Timerの処理に時間をかけすぎない
- 28. Intervalは厳密な時間保証ではない
- 29. Timerを破棄する
- 30. 最初のコードから学べること
- 31. まとめ
サンプルコード
using Timer = System.Windows.Forms.Timer;
namespace TimerSample
{
public partial class Form1 : Form
{
private readonly Timer timer;
public Form1()
{
InitializeComponent();
timer = new Timer();
timer.Interval = 1000;
timer.Tick += MyTimerEvent;
timer.Start();
}
private void MyTimerEvent(object? sender, EventArgs e)
{
MessageBox.Show("タイマーイベント発生");
}
}
}
実行すると、フォームが表示されたあと、1秒ごとに次のメッセージが表示されます。
タイマーイベント発生
この短いコードの中には、Windows Formsのイベント処理を理解するための重要な要素がいくつも含まれています。
using Timer = System.Windows.Forms.Timer;とは
最初の行を見てみましょう。
using Timer = System.Windows.Forms.Timer;
これは、System.Windows.Forms.Timerという型に、Timerという別名を付けています。
このような書き方をusingエイリアスといいます。
本来であれば、次のように書くこともできます。
System.Windows.Forms.Timer timer =
new System.Windows.Forms.Timer();
しかし、毎回完全な名前を書くのは長くなります。
そこで、
using Timer = System.Windows.Forms.Timer;
と書いておけば、
Timer timer = new Timer();
と短く記述できます。
なぜTimerに別名を付けるのか
.NETには、Timerという名前を持つクラスが複数存在します。
代表的なものには次のようなものがあります。
System.Windows.Forms.Timer
System.Timers.Timer
System.Threading.Timer
そのため、複数の名前空間を使用していると、
Timer
だけでは「どのTimerなのか」が分からなくなることがあります。
そこで、
using Timer = System.Windows.Forms.Timer;
と明示することで、
このプログラムでTimerと書いた場合は、Windows Forms用のTimerを意味する
と指定しています。
Timerオブジェクトを作る
続いて次の部分です。
private readonly Timer timer;
これは、Form1クラスがTimerオブジェクトを保持するためのフィールドです。
コンストラクターでは、
timer = new Timer();
として、Timerオブジェクトを生成しています。
newは、クラスから実際に使用するオブジェクトを作成するための演算子です。
イメージとしては、
Timerクラス
↓ new
Timerオブジェクト
となります。
なぜローカル変数ではなくフィールドにするのか
次のように、コンストラクター内だけで宣言することもできます。
Timer timer = new Timer();
簡単なサンプルでは動作する場合がありますが、実際のアプリではフィールドとして保持する方が分かりやすくなります。
private readonly Timer timer;
としておけば、後から別のメソッドでも、
timer.Stop();
のように操作できます。
例えば、
private void StopButton_Click(object sender, EventArgs e)
{
timer.Stop();
}
とすることもできます。
Timerをフォームが管理するオブジェクトとして考えると、フィールドにしておく方が自然です。
Intervalで時間間隔を設定する
次のコードを見てみましょう。
timer.Interval = 1000;
Intervalは、Timerイベントが発生する間隔を指定するプロパティです。
単位はミリ秒です。
1000ミリ秒 = 1秒
そのため、
timer.Interval = 1000;
は、
1秒間隔でTimerイベントを発生させる
という意味になります。
例えば500ミリ秒にしたい場合は、
timer.Interval = 500;
とします。
5秒ごとであれば、
timer.Interval = 5000;
となります。
Tickイベント
Timerで特に重要なのが、次のコードです。
timer.Tick += MyTimerEvent;
TickはTimerが持っているイベントです。
設定された時間が経過すると、
Tickイベントが発生する
という仕組みになっています。
今回の場合、
timer.Interval = 1000;
なので、およそ1秒ごとにTickイベントが発生します。
+=は何をしているのか
ここはC#のイベントを理解するうえで非常に重要です。
timer.Tick += MyTimerEvent;
これは、
Tickイベントが発生したら、MyTimerEventメソッドを実行してください
という登録です。
イメージすると、
Timer
│
│ 1秒経過
↓
Tickイベント発生
│
↓
MyTimerEvent()
となります。
+=は、イベントにイベントハンドラーを登録するために使われています。
イベントハンドラーとは
今回登録しているメソッドは次の部分です。
private void MyTimerEvent(object? sender, EventArgs e)
{
MessageBox.Show("タイマーイベント発生");
}
このように、イベントが発生したときに呼び出されるメソッドをイベントハンドラーと呼びます。
つまり、
MyTimerEvent
はTickイベントを処理するイベントハンドラーです。
メソッドを呼び出していないことに注意
ここで重要なのは、
timer.Tick += MyTimerEvent;
と書いており、
timer.Tick += MyTimerEvent();
とは書いていないことです。
通常、メソッドを実行するときには、
MyTimerEvent();
のように()を付けます。
しかし今回はメソッドをその場で実行したいわけではありません。
C#に対して、
後でTickイベントが起きたら、このメソッドを呼び出してほしい
と伝えています。
そのため、
MyTimerEvent
というメソッドそのものを登録しています。
イベント発生時の流れ
このプログラムの流れを整理すると、次のようになります。
フォーム作成
↓
Timer作成
↓
Interval = 1000
↓
TickイベントにMyTimerEventを登録
↓
Timer.Start()
↓
1秒経過
↓
Tickイベント発生
↓
MyTimerEventが呼び出される
↓
MessageBoxを表示
↓
さらに1秒経過
↓
再びTickイベント発生
この処理がTimerを停止するまで繰り返されます。
Startメソッド
Timerを作成しただけでは、まだ時間の計測は始まりません。
次のコードによってTimerを開始しています。
timer.Start();
これ以降、Intervalで指定した時間ごとにTickイベントが発生します。
停止する場合は、
timer.Stop();
を使用します。
例えば、
private void MyTimerEvent(object? sender, EventArgs e)
{
timer.Stop();
MessageBox.Show("タイマーイベント発生");
}
とすると、一度イベントが発生したところでTimerを停止できます。
なぜイベント登録をStartより先に書くのか
サンプルでは、
timer.Tick += MyTimerEvent;
timer.Start();
という順番にしています。
先に、
Tickイベントが発生したら何をするのか
を設定し、その後Timerを開始しています。
つまり、
イベント処理を準備
↓
Timer開始
という流れです。
この順番にしておくと、コードの意味も分かりやすくなります。
senderとは何か
イベントハンドラーをもう一度見てみます。
private void MyTimerEvent(object? sender, EventArgs e)
第1引数の、
sender
には、基本的にイベントを発生させたオブジェクトが渡されます。
今回であれば、Tickイベントを発生させたTimerです。
イメージすると、
Timer
│
│ Tickイベント発生
↓
MyTimerEvent(sender, e)
↑
Timer
となります。
例えば次のように確認することもできます。
private void MyTimerEvent(object? sender, EventArgs e)
{
if (sender is Timer timer)
{
MessageBox.Show(timer.Interval.ToString());
}
}
これならイベントを発生させたTimerのIntervalを取得できます。
object?の?は何か
今回のイベントハンドラーでは、
object? sender
となっています。
?は、この変数にnullが入る可能性があることを示しています。
つまり、
object?
は、
object型のオブジェクト、またはnull
を表します。
最近の.NETではNullable Reference Typesの仕組みにより、このように?を付けるコードをよく見かけます。
EventArgs eとは
第2引数は、
EventArgs e
です。
これはイベントに関する追加情報を受け取るための引数です。
ただし、TimerのTickイベントでは特別な追加情報はありません。
そのため、このサンプルでは、
e
を使用していません。
それでもイベントハンドラーの決められた形式に合わせて、
object? sender, EventArgs e
と記述します。
イベントハンドラーには決められた形がある
Tickイベントに登録するメソッドは、基本的に次の形になっている必要があります。
void メソッド名(object? sender, EventArgs e)
例えば、
private void MyTimerEvent(object? sender, EventArgs e)
です。
全く違う形のメソッド、
private int MyTimerEvent()
などをそのままTickイベントに登録することはできません。
これは、イベントが呼び出せるメソッドの形が決められているためです。
この仕組みには、C#のデリゲートが関係しています。
Tickイベントの内部ではデリゲートが使われている
イベントを理解するには、デリゲートの存在を知っておくと理解しやすくなります。
非常に簡略化すると、デリゲートは、
呼び出すメソッドを記憶できる型
です。
例えば、
timer.Tick += MyTimerEvent;
と書くことで、
Tick
└─ MyTimerEventを呼び出す
という情報が登録されます。
その後Tickイベントが発生すると、登録されているメソッドが呼び出されます。
つまり、
イベント
↓
デリゲート
↓
イベントハンドラー
という関係があります。
ボタンのClickイベントも同じ仕組み
TimerのTickイベントは特別な仕組みではありません。
Windows Formsでよく使うButtonのClickイベントも基本的には同じです。
例えば、
button1.Click += Button1_Click;
とすると、
button1
↓
クリック
↓
Clickイベント
↓
Button1_Click()
となります。
Timerの場合も、
timer.Tick += MyTimerEvent;
なので、
timer
↓
時間経過
↓
Tickイベント
↓
MyTimerEvent()
です。
発生条件が違うだけで、C#のイベントとしての仕組みは同じです。
+=で複数のメソッドを登録できる
イベントには複数のイベントハンドラーを登録することもできます。
例えば、
timer.Tick += MyTimerEvent;
timer.Tick += AnotherTimerEvent;
とします。
するとTickイベントが発生したとき、
Tick
├─ MyTimerEvent
└─ AnotherTimerEvent
のように、登録されている複数のメソッドが呼び出されます。
これも+=が使われている理由の一つです。
-=でイベントを解除できる
イベントに登録したメソッドは、-=を使って解除できます。
timer.Tick -= MyTimerEvent;
すると、以降のTickイベントではMyTimerEventが呼び出されなくなります。
つまり、
+=
はイベント登録、
-=
はイベント解除
と考えることができます。
匿名関数でも書ける
イベントハンドラーは、必ず名前付きメソッドで作る必要はありません。
例えば次のようにも書けます。
timer.Tick += delegate
{
MessageBox.Show("タイマーイベント発生");
};
これは匿名メソッドと呼ばれる書き方です。
名前のある、
MyTimerEvent
というメソッドを作らず、その場でイベント処理を書いています。
ラムダ式で書く
現在のC#では、さらに簡潔なラムダ式を使うこともできます。
timer.Tick += (sender, e) =>
{
MessageBox.Show("タイマーイベント発生");
};
この、
=>
がラムダ演算子です。
読み方としては、
senderとeを受け取って、右側の処理を実行する
と考えると分かりやすいでしょう。
つまり、
private void MyTimerEvent(object? sender, EventArgs e)
{
MessageBox.Show("タイマーイベント発生");
}
と、
timer.Tick += (sender, e) =>
{
MessageBox.Show("タイマーイベント発生");
};
は、この場合ほぼ同じ役割を持っています。
名前付きメソッドとラムダ式の使い分け
処理がある程度長い場合は、
timer.Tick += MyTimerEvent;
として、別のメソッドに分けた方が読みやすくなります。
一方、
timer.Tick += (sender, e) =>
{
label1.Text = DateTime.Now.ToString();
};
のように非常に短い処理であれば、ラムダ式も便利です。
無理にラムダ式を使えば良いというわけではありません。
コードの読みやすさを考えて使い分けます。
Windows FormsのTimerの特徴
System.Windows.Forms.Timerには重要な特徴があります。
Tickイベントは基本的にWindows FormsのUIスレッド上で実行されます。
そのため、
private void MyTimerEvent(object? sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString();
}
のように、そのままLabelなどのコントロールを操作できます。
これはWindows Formsでは非常に便利です。
時計を表示してみる
Timerの代表的な使い方として、現在時刻をLabelに表示してみましょう。
using Timer = System.Windows.Forms.Timer;
namespace TimerSample
{
public partial class Form1 : Form
{
private readonly Timer timer;
public Form1()
{
InitializeComponent();
timer = new Timer();
timer.Interval = 1000;
timer.Tick += MyTimerEvent;
timer.Start();
}
private void MyTimerEvent(object? sender, EventArgs e)
{
label1.Text = DateTime.Now.ToString("HH:mm:ss");
}
}
}
これで1秒ごとに、
10:15:31
10:15:32
10:15:33
のようにLabelの表示が更新されます。
Timerの処理に時間をかけすぎない
System.Windows.Forms.TimerのTickイベントはUIスレッド上で実行されます。
そのため、Tickイベントの中で時間のかかる処理をすると、
画面が反応しない
状態になることがあります。
例えば、
private void MyTimerEvent(object? sender, EventArgs e)
{
Thread.Sleep(10000);
}
のような処理を入れるのは適切ではありません。
10秒間UIスレッドを止めるため、その間フォームの操作ができなくなります。
Windows FormsのTimerは、
UIを定期的に少し更新する
ような処理に向いています。
Intervalは厳密な時間保証ではない
もう一つ注意したいのが、
timer.Interval = 1000;
としたからといって、必ず厳密に1000ミリ秒ごとに処理されるとは限らない点です。
UIスレッドが別の処理を行っている場合などには、Tickイベントの実行が遅れることがあります。
そのため、Windows Forms Timerは、
厳密な時間計測
よりも、
一定間隔で画面を更新する
用途に適しています。
Timerを破棄する
Timerも不要になったら破棄することができます。
例えばフォームが閉じられるときに、
timer.Stop();
timer.Dispose();
とすることができます。
ただし、Visual StudioのデザイナーからTimerをフォームに配置した場合は、通常はフォームのcomponentsによって管理されます。
コードから自分でTimerを生成するときには、
誰がそのオブジェクトを管理するのか
という視点も大切です。
最初のコードから学べること
今回のコードは非常に短いものですが、C#の重要な仕組みが多数含まれています。
timer.Tick += MyTimerEvent;
という1行だけでも、
Timer
イベント
イベントハンドラー
デリゲート
メソッド参照
+=演算子
sender
EventArgs
といったC#の重要な概念につながっています。
さらに、
timer.Tick += (sender, e) =>
{
};
と書けば、
匿名関数
ラムダ式
デリゲート
イベント
へと学習を広げることができます。
まとめ
Windows FormsのTimerは、単に「一定時間ごとに処理をするためのクラス」として覚えるだけではもったいない題材です。
今回の中心となるコードは、
timer.Tick += MyTimerEvent;
です。
これは、
Tickイベントが発生したとき
↓
MyTimerEventを実行する
という登録を行っています。
そして、
private void MyTimerEvent(object? sender, EventArgs e)
がイベントを受け取るイベントハンドラーです。
この仕組みはTimerだけのものではありません。
button1.Click += Button1_Click;
も、
textBox1.TextChanged += TextBox1_TextChanged;
も、
timer.Tick += MyTimerEvent;
も、基本的には同じC#のイベントの仕組みです。
Windows Formsを学ぶときは、
ボタンをダブルクリックしたらイベント処理を書く
という操作だけを覚えるのではなく、
イベントが発生する
↓
登録されているイベントハンドラーが呼ばれる
というC#の仕組みとして理解しておくと、その後のコードがかなり読みやすくなります。










ディスカッション
コメント一覧
まだ、コメントがありません