ストップウォッチを作ってみよう

今回のC#プログラミング学習は、ストップウォッチを作ります。

スタートボタンを押してからストップボタンを押すまでの時間を計るアプリを作ります。

C#プログラムには、こちらが指定した一定時間ごとにWindowsから通知を受け取る仕組みがあるので、それを使って時間を計ります。

ダウンロード

まずは、ここをクリックしてストップウォッチのサンプルファイルをダウンロードします。

ダウンロードした「step6.zip」を解凍すると、「Step6.txt」と「Compile.bat」になります。

「Step6.txt」は、今回学習するストップウォッチのC#ソースファイルです。

「Compile.bat」は、あなたのWindowsパソコンにはじめからインストールされているC#コンパイラ「csc.exe」を使って、「Step6.txt」をコンパイルして実行ファイルを作るためのバッチファイルです。

「Compile.bat」で右クリックまたはアプリケーションキーを押してメニューを開き、編集を選び「Compile.bat」をメモ帳で開きます。

8行目のコンパイルパス(csc.exeまでのパス)をあなたのパソコン環境に合わせて修正します(たぶんこのままでもOKだと思います)。

修正しなかった場合はそのまま閉じて、コンパイルパスを変更した場合には、上書き保存してから閉じます。

実行

「Compile.bat」を実行して「Step6.txt」から「Step6.exe」を作ります。

完成した「Step6.exe」を実行すると以下のようなストップウォッチが起動します。

ストップウォッチの画像

左側に時間を表示するテキストボックスがあり、右側にスタートボタンがあります。

スタートボタンを押すと1秒経過するごとに音が鳴り、それに合わせてテキストボックス内の時間表示も1秒ずつ増えていきます。

(注)パソコン環境によっては音が鳴らない場合があります。

計測中はボタン表示が「スタート」ではなく「ストップ」になるので、ストップボタンを押すと時間計測を終了します。

ソースコード

ストップウォッチのソースコードです。

using System;
using System.Timers;
using System.Drawing;
using System.Windows.Forms;


/********************
Constantsクラス
定数専用
********************/
static class Constants
{
public const String ApplicationName = "ストップウォッチ"; // アプリケーション名
public const int TimeUp = 3600 *10; // 計測できる最大時間(秒で記述すること)
public const String TextBoxFontName = "MS Gothic"; // テキストボックスに表示される文字の書体
public const int TextBoxFontSize = 31; // テキストボックスに表示される文字の大きさ
public const int TextBoxMaxLength = 10; // 最大文字数
public const int TextBoxWidth = 300; // テキストボックスのよこ幅
public const int TextBoxHeight = 50; // テキストボックスのたて幅
public const String TextBoxHourLength = "時間"; // 時間数の後ろに付ける文字
public const String TextBoxMinuteLength = "分"; // 分数の後に付ける文字
public const String TextBoxSecondLength = "秒"; // 秒数の後に付ける文字
public const String ButtonFontName = "MS UI Gothic"; // ボタンに表示される文字の書体
public const int ButtonFontSize = 30; // ボタンに表示される文字の大きさ
public const int ButtonWidth = 150; // ボタンのよこ幅
public const int ButtonHeight = TextBoxHeight; // ボタンのたて幅
public const String StartButtonLength = "スタート"; // 処理開始ボタンに表示する文字列
public const String StopButtonLength = "ストップ"; // 処理終了ボタンに表示する文字列
}


/********************
MyApplicationクラス
実行開始位置
********************/
class MyApplication
{
public static void Main()
{
Application.Run(new ApplicationWindow());
}
}


/********************
ApplicationWindowクラス
Formクラスを継承
********************/
class ApplicationWindow : Form
{
private bool bCountFlag = false; // カウント中のみtrue
private int nCounter = 0; // 1秒ごとに数値を増やす
private System.Timers.Timer timer;
private TextBox textbox;
private Button button;

/* コンストラクタ */
public ApplicationWindow()
{
SetTimer();
FormDesign();
}

/* SetTimerメソッド */
/* タイマーをセットする */
public void SetTimer()
{
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(MyTimer);
timer.Interval = 1000; // ミリ秒
timer.AutoReset = true;
}

/* FormDesignメソッド */
/* ウィンドウをデザインする */
public void FormDesign()
{
StartPosition = FormStartPosition.CenterScreen;

MaximizeBox = false; // 最大化コマンドの無効化
FormBorderStyle = FormBorderStyle.Fixed3D; // サイズ変更コマンドの無効化

Text = Constants.ApplicationName;
BackColor = SystemColors.Window;
ClientSize = new Size(Constants.TextBoxWidth + Constants.ButtonWidth, Constants.ButtonHeight);

// テキストボックスを作る
textbox = new TextBox();
textbox.Font = new Font(Constants.TextBoxFontName, Constants.TextBoxFontSize);
textbox.Width = Constants.TextBoxWidth;
textbox.Height = Constants.TextBoxHeight;
textbox.Text = Convert.ToString(nCounter) + Constants.TextBoxSecondLength;
textbox.Multiline = false; // 1行テキストボックス
textbox.TextAlign = HorizontalAlignment.Right; // 右寄せ
textbox.MaxLength = Constants.TextBoxMaxLength;
textbox.ReadOnly = true; // 読み取り専用
textbox.Parent = this;
textbox.Location = new Point(0, 0);
textbox.TabIndex = 1;
textbox.TabStop = true;

// ボタンを作る
button = new Button();
button.Font = new Font(Constants.ButtonFontName, Constants.ButtonFontSize);
button.TextAlign = ContentAlignment.MiddleCenter; // 中段中央
button.Width = Constants.ButtonWidth;
button.Height = Constants.ButtonHeight;
button.Text = Constants.StartButtonLength;
button.BackColor = SystemColors.Control;
button.Parent = this;
button.Location = new Point(Constants.TextBoxWidth, 0);
button.Click += new EventHandler(ButtonClick);
button.TabIndex = 0;
button.TabStop = true;
}

/* ButtonClickメソッド */
/* ボタンが押された */
public void ButtonClick(object sender, EventArgs e)
{
if (bCountFlag) // カウント中なので停止する
{
timer.Stop();
button.Text = Constants.StartButtonLength;
textbox.Focus();
}
else // 停止中なのでカウントを始める
{
nCounter = 0;
textbox.Text = Convert.ToString(nCounter) + Constants.TextBoxSecondLength;
timer.Start();
button.Text = Constants.StopButtonLength;
}

bCountFlag = !bCountFlag;
}

/* MyTimerメソッド */
/* 1秒ごとに呼び出される */
public void MyTimer(object sender, ElapsedEventArgs e)
{
nCounter++;
int nSecond = nCounter;

if (nSecond >= Constants.TimeUp)
{
button.PerformClick(); // ボタンを押す
}

textbox.Text = "";
if (nSecond >= 3600) // 1時間(3600秒)以上ある場合
{
int nHour = nSecond / 3600;
nSecond %= 3600;
textbox.Text = Convert.ToString(nHour) + Constants.TextBoxHourLength;
}

if (nSecond >= 60) // 1分(60秒)以上ある場合
{
int nMinute = nSecond / 60;
nSecond %= 60;
textbox.Text += Convert.ToString(nMinute) + Constants.TextBoxMinuteLength;
}

textbox.Text += Convert.ToString(nSecond) + Constants.TextBoxSecondLength;
System.Media.SystemSounds.Beep.Play(); // 音を鳴らす(一般の警告音)
}

/* OnFormClosingメソッド */
/* ウィンドウが閉じるときに呼び出される */
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
DialogResult dr;

if (bCountFlag) // カウント中なら
{
dr = MessageBox.Show("カウント中ですが終了してもよいですか?", Constants.ApplicationName, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
if (dr == DialogResult.Yes) // 途中終了
{
timer.Stop();
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
else // 停止中であれば
{
e.Cancel = false;
}
}
}

プログラム解説

ストップウォッチのソースコードについて解説します。

使用DLLの宣言

まずはこのプログラムで使うDLLファイルを宣言します。

「using System;」で「System.dll」の使用宣言となります。

この辺は前回と同じですね。

違うところは、新たに「System.Timers.dll」を追加しているところです。

Constantsクラス

プログラム内で使用する定数をまとめて管理するConstantsクラスです。

このクラスは定数専用です。

クラス名とメンバ変数名を「.」でつなぐことにより、ほかのクラスから定数の内容を呼び出すことができます。

例えば、「Constants.ApplicationName」とすれば、「ストップウォッチ」という文字列を呼び出すことができます。

MyApplicationクラス

ストップウォッチプログラムを実行させるのがMyApplicationクラスです。

このクラスが実行開始位置です。

このクラスのMainメソッドから実行されます。

Application.Run命令で、ApplicationWindowクラスを呼び出し実行させていきます。

ApplicationWindowクラス

ストップウォッチのプログラム本体を管理するのがApplicationWindowクラスです。

Windowsの提供する各種機能を使うためFormクラスを継承しています。

ApplicationWindowメソッド

このメソッドはコンストラクタです。

コンストラクタとは、このクラス(この場合はApplicationWindowクラス)が呼ばれたときに、自動的に実行されるメソッドのことです。

ここでは、FormDesignメソッドと、SetTimerメソッドを呼び出しています。

SetTimerメソッド

SetTimerメソッドは、プログラム起動時に前述のコンストラクタから呼び出されてタイマーをセットします。

「timer = new System.Timers.Timer()」でタイマーオブジェクトを作ります。

「timer = new Timer()」と書いてもよいような気がしますが、C#プログラムにはいくつかタイマーの種類があり、「System.Windows.Forms.Timer」というのもあるので、明確にするためこのような書き方をしています。

これで一定時間ごとにウィンドウズ側からこのプログラムに向けてイベントを起こしてくれます。

なので次は、時間設定とイベントの内容を指定します。

「timer.Interval = 1000」で、1000ミリ秒(1秒)ごとにイベントを発生させるように設定しています。

「timer.Elapsed += new ElapsedEventHandler(MyTimer)」で、イベントが起こったときは、MyTimerメソッドを呼ぶように指定しています。

「timer.AutoReset = true」で、タイマーを継続して呼び出せるように設定しています(TRUEはデフォルト値)。

一度しか呼び出さないのであればFALSEを指定します。

これで、1秒ごとにMyTimerメソッドが呼ばれるようにセット完了です。

FormDesignメソッド

ここではウィンドウの内容をデザインします。

まずは、「StartPosition = FormStartPosition.CenterScreen」でモニタの中央にウィンドウを表示させることを指定します。

次に、ウィンドウ自体の外見を指定していきます。

「MaximizeBox = false」で最大化ボタンを無効化し、「FormBorderStyle = FormBorderStyle.Fixed3D」でサイズ変更を無効化しています。

次に、ウィンドウタイトルと背景色とサイズを指定します。

Text = Constants.ApplicationName;
BackColor = SystemColors.Window;
ClientSize = new Size(Constants.TextBoxWidth + Constants.ButtonWidth, Constants.ButtonHeight);

ウィンドウサイズは、実際にウィンドウ内の描画領域となるクライアントサイズのみを指定し、ウィンドウの境界線やタイトルバーなどの大きさはお任せしています。

クライアントサイズは、経過時間を表示するテキストボックス、スタートまたはストップと表示が切り替わるボタンが1つの合計2つのアイテムが並んで置けるように指定します。

これでウィンドウの外枠ができたので、次は中身を作っていきます。

まずは、テキストボックスを作ります。

textbox = new TextBox();
textbox.Font = new Font(Constants.TextBoxFontName, Constants.TextBoxFontSize);
textbox.Width = Constants.TextBoxWidth;
textbox.Height = Constants.TextBoxHeight;
textbox.Text = Convert.ToString(nCounter) + Constants.TextBoxSecondLength;
textbox.Multiline = false; // 1行テキストボックス
textbox.TextAlign = HorizontalAlignment.Right; // 右寄せ
textbox.MaxLength = Constants.TextBoxMaxLength;
textbox.ReadOnly = true; // 読み取り専用
textbox.Parent = this;
textbox.Location = new Point(0, 0);
textbox.TabIndex = 1;
textbox.TabStop = true;

テキストボックスを作ることを宣言し、大きさ、使用フォント、1行テキストボックスで右寄せなどなど必要な指定を行います。

これは経過時間を表示するだけのテキストボックスですから、ユーザーが入力できないようにする必要がありますよね、なので読み取り専用にします。

ここまでの学習ができていれば、わからないところはないと思います。

次はボタンを作ります。

button = new Button();
button.Font = new Font(Constants.ButtonFontName, Constants.ButtonFontSize);
button.TextAlign = ContentAlignment.MiddleCenter; // 中段中央
button.Width = Constants.ButtonWidth;
button.Height = Constants.ButtonHeight;
button.Text = Constants.StartButtonLength;
button.BackColor = SystemColors.Control;
button.Parent = this;
button.Location = new Point(Constants.TextBoxWidth, 0);
button.Click += new EventHandler(ButtonClick);
button.TabIndex = 0;
button.TabStop = true;

ボタンを作ることを宣言し、大きさ、表示位置と文字とフォント、ボタンが押された時のイベントハンドラなどなどを指定します。

ここも以前学習した内容と同じですから難しいところはないと思います。

これでウィンドウの外見が完成しました。

ButtonClickメソッド

ButtonClickメソッドはボタンが押された時に呼び出されます。

今回のプログラムでは、ボタンが押されたときの状況によりその後の動作を変化させています。

具体的には、アプリ起動時ボタンの表示文字は「スタート」です。

ボタンが押されると時間を計り始め、ボタンの表示文字を「ストップ」にします。

そして次にボタンが押されたとき(時間計測中にボタンが押されたとき)は、時間を計ることをやめて、ボタンの表示文字を再び「スタート」にします。

if (bCountFlag) // カウント中なので停止する
{
timer.Stop();
button.Text = Constants.StartButtonLength;
textbox.Focus();
}
else // 停止中なのでカウントを始める
{
nCounter = 0;
textbox.Text = Convert.ToString(nCounter) + Constants.TextBoxSecondLength;
timer.Start();
button.Text = Constants.StopButtonLength;
}

スタートボタンが押された時、1秒ずつカウントしていく変数nCounterを初期化し、テキストボックスに反映させます。

続いて、「timer.Start()」でタイマーをスタートさせて、ボタンの表示文字を「スタート」から「ストップ」にします。

ストップボタンが押された時は、「timer.Stop()」でタイマーを止めて、ボタンの表示文字を「ストップ」から「スタート」にします。

そして「textbox.Focus()」でテキストボックス(計測した時間)にフォーカスを当てます。

最後にどんな状態であれボタンが押された時の共通処理として、「bCountFlag = !bCountFlag」で、bCountFlag変数の内容を逆にしています。

bCountFlag変数は、カウント中ならTRUE、それ以外はFALSEを格納します。

「bCountFlag = !bCountFlag」のように頭に「!」を付けることにより、それ以降の意味が逆になります。

つまり、TRUEであればFALSEになり、FALSEならばTRUEになるわけです。

これでボタンを押された時の処理終了です。

MyTimerメソッド

スタートボタンが押されて「timer.Start()」でタイマーが起動したら、事前にSetTimerメソッドで設定した時間ごとに呼び出されます。

今回であれば1秒ごとに呼び出されます。

まず「nCounter++」で、変数nCounterに1を足します。

このMyTimerメソッドは1秒ごとに呼び出されるので、変数nCounterは経過秒数を格納していることになります。

なので、「int nSecond = nCounter」で現在までの経過秒数を、nSecond変数に格納しておきます。

if (nSecond >= Constants.TimeUp)
{
button.PerformClick(); // ボタンを押す
}

次に永遠に秒数をカウントされても困るので、タイムアップ(強制終了ライン)を決め、もし強制終了する必要がある場合には、ボタンを押してタイマーを止めます。

強制終了する必要のない場合は、以下が実行されます。

まずは、「textbox.Text = ""」で、テキストボックスの内容を空にします。

次に、現在までの経過秒数を読みやすいかたちに整形してテキストボックスに表示させます。

if (nSecond >= 3600) // 1時間(3600秒)以上ある場合
{
int nHour = nSecond / 3600;
nSecond %= 3600;
textbox.Text = Convert.ToString(nHour) + Constants.TextBoxHourLength;
}

1時間は3600秒です。

なのでそれ以上ある場合には、3600秒で割った答えが経過時間数になります。

割り出した経過時間数をテキストボックスに書き出します。

次は、残りの秒数を求めます。

「nSecond %= 3600」で3600秒で割った余りをnSecond変数に格納しています。

これでnSecond変数の中身が3600秒以下(1時間以下)になりました。

if (nSecond >= 60) // 1分(60秒)以上ある場合
{
int nMinute = nSecond / 60;
nSecond %= 60;
textbox.Text += Convert.ToString(nMinute) + Constants.TextBoxMinuteLength;
}

ここでは残りの秒数が何分になるかを計算しています。

数字が違うだけで、前述の時間数を求めるやり方と基本的には同じです。

textbox.Text += Convert.ToString(nSecond) + Constants.TextBoxSecondLength;

残りは秒数だけなので、そのままテキストボックスに反映させます。

System.Media.SystemSounds.Beep.Play(); // 音を鳴らす(一般の警告音)

最後に、音を鳴らしてこのメソッド終了です。

(注)パソコン環境によっては、音が鳴らない場合があります。

OnFormClosingメソッド

OnFormClosingメソッドはウィンドウが閉じられようとしているときに呼び出されます。

今回はもともとC#プログラミングのシステム側にあったものを書き換えて使います。

冒頭の「base.OnFormClosing(e)」は、メソッドをオーバーライド(書き換え)するときに書いておくことがマイクロソフトから推奨されています。

DialogResult dr;

if (bCountFlag) // カウント中なら
{
dr = MessageBox.Show("カウント中ですが終了してもよいですか?", Constants.ApplicationName, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
if (dr == DialogResult.Yes) // 途中終了
{
timer.Stop();
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
else // 停止中であれば
{
e.Cancel = false;
}

このメソッドはウィンドウが閉じられようとしているときに呼び出されるので、この性質を利用して、呼び出された時もし時間計測していた場合には、警告を出しています。

今まで出てきたメッセージボックスは、OKボタンしかありませんでした。

でもそれだと困るので、ユーザーが「はい」か「いいえ」を選択できるメッセージボックスにしています。

「はい」を選択したときは、タイマーを止めてウィンドウを閉じます。

「いいえ」を選択したときは、ウィンドウを閉じずに計測を続けます。

もちろん、計測中でなかったときは、メッセージボックスは出さずにウィンドウを閉じます。

これでプログラム解説は以上です。

まとめ

今回のプログラミング学習は、前回とかなり似ているところが多かったので楽だったかと思います。

一定時間ごとに特定のメソッドを呼び出して何らかの処理をさせることができると、今回のように時間を計ること以外にも、描画したボールを少しずつ動かすといったことができるようになります。

C#プログラムでゲームを作るときには必要な知識ですので、覚えておいてください。

以上、ありがとうございました。