ナッキーの「Turbo Delphiはじめて奮戦記」 - 第8回 計算機でエラーチェック

投稿者: : Hitoshi Fujii

概要: 前回はひととおりの機能をつけましたが、エラーチェックなどを加えてプログラムの完成度を高めましょう。

Hide image
nacky75

ナッキー

前回でほとんどできているから、エラーチェックを加えるだけならきっと簡単ね。ちょっと加えるだけでグレードアップするなら楽勝だわ。

 

Hide image
takahashi75

高橋先生

今回紹介するエラーチェックを使いこなせるようになると、本格的なプログラミングにも挑戦できるね。簡単ではないけど、ここまで充分習得できたから、今回もがんばってね。

    例外処理ブロック

じゃあ、まずはじめに、いつものように前回のプロジェクトを開きましょう。Turbo Delphiを起動して、画面中央の「ホームページ」で「Calclator.bdsproj」を選択。もし一覧に表示されていなければ、ツールバーの[プロジェクトを開く(Ctrl+F11)]ボタンをクリックします。「プロジェクトを開く」ダイアログボックスから「Calclator.bdsproj」を探します。

エラーチェックということですが、どういうときにうまく計算できないかを考えないとね。思いつくところだけ箇条書きにしてみます。ボタンごとに分けて考えるといいんだって。

  • 1度計算結果を出したあと、クリアしないでテンキーボタンを押すとき
  • edtInput1に値が入っていないのに演算ボタンを押すとき
  • edtInput2に値が入っているのに演算ボタンを押すとき
  • 値が何も入っていないのに計算ボタンを押すとき
  • edtInput1だけに値が入っていて計算ボタンを押したとき

このくらいですよね、高橋先生。

高橋先生:もうひとつ加えて欲しいな。プログラムの中では0で割ることができないんだ。0を足す、引く、掛けるはOK。0で割ると実行時エラーになってしまう。実行時エラーがどんな表示になるか、見てみよう。


どれどれ。ツールバーの[実行]ボタンをクリックして、実行。edtInput1には好きな数字を入力して、[÷]ボタンをクリックします。次の数値はテンキーから「0」だけ入力します。

010割り

図01 0割り

[計算]ボタンをクリックすると、あ、エラーメッセージが表示されました。

Hide image
02デバッガ例外

図02 デバッガ例外通知

ナッキー:エラーメッセージが出るとドキドキして、つい[Enter]キーを押しちゃったり、適当にクリックしちゃったりするんです。

高橋先生:慌ててしまうことがあるよね。でもメッセージは大切な情報がかかれているから、難しかったとしても一応読んでね。また、設定によっては表示されないこともあるよ。このメッセージは「デバッガ例外通知」。Turbo Delphiの中でプログラムを動かしていて、エラーが出ると表示されるものだ。ユーザーがふだん使うときには見ることはないメッセージなんだ。メッセージボックスの右下の[ブレーク(B)]をクリックすると一時停止してコードを表示する。[継続(C)]ボタンをクリックすると、そのまま処理を進めるんだ。ここでは、[継続(C)]ボタンをクリックしよう。


それでは、[継続(C)]ボタンをクリックして続けます。メッセージボックスがもう一つ表示されます。

Hide image
03実行時エラーのメッセージ

図03 実行時エラーのメッセージ

高橋先生:もしも、[ブレーク(B)]ボタンをクリックしたら、一時停止してコードエディタが表示される。第4回 画面もコードもすっきりさせよう!で「デバッガの使い方-ブレークポイント」の説明をしたけど、ブレークポイントを設定したときと同じように止まるんだ。一時停止してしまっても、ツールバーの[実行]ボタンをクリックすれば、図03のメッセージボックスが表示されるから心配しないで。

こちらの2番目のメッセージボックスは、ユーザーが使うときも表示される。「0による浮動小数点除算.」って表示されても、よくわからないよね。[OK]ボタンをクリックしたらフォームに戻るよ。例外が発生した部分以降の処理はされないで、フォームに戻るんだ。そのまま値を入力しなおせば、計算しなおすこともできる。

ナッキー:こんなのが表示されたらどうしたらいいのかわかりません。それにフォームに戻っても困っちゃう。

高橋先生:実行時エラーがあっても、プログラムは動いたままなんだ。うまく続けられるよう変更しよう。変更は2つ。1つ目は入力をやり直せるように入力項目をクリアして、次の計算ができるようにする。2つ目はメッセージの内容をもっとわかりやすくするよ。

ナッキー:このメッセージボックスは、プログラマが表示しているのではなくて、Turbo Delphiが表示しているんですよね。それを変えちゃうことができるの?

高橋先生:例外処理ブロックを使って、Turbo Delphiのメッセージボックスは取り替えることができるんだ。先に例外の話をしよう。

ナッキー:「例外」って特殊な場合ってことですか?

高橋先生:プログラムを動かす上でのエラーをTurbo Delphiでは「例外」と呼んでいる。例外が発生すると、Turbo Delphiは、そのことをプログラマに伝えるために、例外オブジェクトを作っているんだ。例外オブジェクトにはいろいろな種類がある。例外オブジェクトは、例外の情報や、例外を取り扱う機能が入っているものだよ。例外の内容を示したメッセージも、例外オブジェクトの種類によって異なる。

ナッキー:例外はエラーなんだ。例外オブジェクトは、プログラマが例外を扱いたいときに使うものなのね。

高橋先生:「図02デバッガ例外通知」のメッセージを見てみよう。「例外クラスEZeroDivide(メッセージ:'0による浮動小数点除算.')を送出しました。」と表示されている。例外クラスは、例外オブジェクトの型を表している。つまり、例外オブジェクトの型が「EZeroDivide」ってことがわかる。

今回の例外オブジェクトは、Turbo Delphiが内部で自動的に作成したものだけど、プログラマが独自に例外を発生させたりできるんだ。

ナッキー:へえー、そうなんだ。でも、どうやって、発生した例外を使うことができるの?

高橋先生:そうだね。例外の発生を捕らえるには、例外処理ブロック「try...except文」を使うんだ。ここに、例外が発生したときの処理を書くことができる。使い方は

try
  //例外が発生するかもしれないコード①
  //続きのコード②
except
  //例外が発生したときのコード③
end;
//続きのコード④

例外が発生しないときは①、②の次は④の順で処理をする。例外が発生したときだけ、①、③、④の順で実行される。③のコードは例外が発生しなければ実行されないので気をつけて。

tryとexceptの間に0割りするかもしれない文を置いて、exceptからend;の間にメッセージボックスの表示と、入力値のクリアを行おう。

ナッキー:うーんと、0割りしそうなのは割り算の部分だけよね。if文の中にはどうやって書き加えるんですか?

高橋先生:if文の中にtry except文を書き込めるよ。注意して欲しいのはtry except文では例外が発生しても続きの処理をしてしまうんだ。処理をストップしてしまいたければ、Exitで抜けるようにしてね。

ナッキー:抜けるってどこに?

高橋先生:Exitを呼び出すと、そのプロシージャから抜けるんだ。だから、この場合は、イベントハンドラから抜ける。でも、処理を続けたくないんだったら、これでいいよね。


なるほど、分かったような分からないような。とりあえず、やってみるか。じゃあ、まずtry except文の部分だけ考えてみます。最初に入力項目をクリアするんだったっけ。エディットのクリアは、長さ0の文字列を代入するのね。

try
  Ans := Num1 / Num2;
except
  edtInput2.Text := '';
end;

次に、メッセージボックスを表示。メッセージの内容は「0による浮動小数点除算.」ではわからないので、「0で割ることはできません。クリアボタンをクリックするか、正しい値を入力してください。」にします。

try
  Ans := Num1 / Num2;
except
  edtInput2.Text := '';
  ShowMessage('0で割ることはできません。クリアボタンをクリックするか、正しい値を入力してください。');
  Exit;
end;

ナッキー:どう、あってる?

高橋先生:try except文はこれでいいから、if文の中に書き込んでいいよ。ただし、if文の中だからtry except文の末尾の「end」は「;」をつけないでね。


そっか、間違えないようにしないと。コードを記述します。画面をコードエディタに切り替えて、btnKeisanClickイベントハンドラで、割り算をしているif文を探します。「Ans := Num1 / Num2」の前に空白行を入れて「try」を記述。勢いあまって改行しちゃった。うげ、なんか変な表示が!! 教えて、高橋先生!

Hide image
04try文入力

図04 try文入力

高橋先生:ブロック補完が働いたんだね。tryで始まる文はtry except文のほかにtry finally文というのもあるんだ。今回は使わないから余計な部分は削除してね。


あーびっくりした。補完してくれるのは便利だけれど、予想外のこともしちゃうのね。では、気を取り直して、コードを入力します。太字部分を追加します。特にtry except文の「end」の後に「;」をつけないようにします。

procedure TfrmCalc.btnKeisanClick(Sender: TObject);
var
  Num1 : Integer;
  Num2 : Integer;
  Ans : Double;
begin
  Num1 := StrToIntDef(edtInput1.Text, 0);
  Num2 := StrToIntDef(edtInput2.Text, 0);
  if lblEnzan.Caption = '+' then
    Ans := Num1 + Num2
  else
  if lblEnzan.Caption = '-' then
    Ans := Num1 - Num2
  else if lblEnzan.Caption = '×' then
    Ans := Num1 * Num2
  else if lblEnzan.Caption = '÷' then
    try
      Ans := Num1 / Num2;
    except
      edtInput2.Text := '';
      ShowMessage('0で割ることはできません。クリアボタンをクリックするか、正しい値を入力してください。');
      Exit;
    end
  else
    Ans := Num1;
  edtInput2.Color := clYellow;
  edtKekka.Color := clWhite;
  lblEqual.Visible := True;
  edtKekka.Text := FloatToStr(Ans);
end;

できたら、ツールバーの[すべて保存]ボタンで保存して、[実行]ボタンで実行してみます。先ほどと同じように0で割るようにしてみましょう。前回と同じように「デバッガ例外通知」が表示されますので[継続(C)]をクリックします。次に表示されるメッセージが作成したメッセージなら成功ね。

Hide image
05メッセージ変更

図05 メッセージ変更

ナッキー:これで、エラーを制御したってことになるんですね。ちょっと難しかったけれど、なんだか本格的♪

    イベントハンドラの共有

次はエラーチェックをもう少し進めます。最初にあげたチェック項目から「1度計算結果を出したあと、クリアしないでテンキーボタンを押すとき」について考えてみよっ。まずは結果を表示しているかどうかを調べるんですよね。

高橋先生:テンキーボタンを押したとき、まだ計算結果をクリアしていなければedtKekkaエディットに文字が入っている。また、lblEqualラベルのVisibleプロパティがTrueになっていたね。どちらかをチェックすれば、結果表示中かどうかがわかる。今回はif文を使うから、TrueかFalseの結果が出るlblEqualのVisibleプロパティのほうがいいかな。もし結果を表示中なら、btnClearボタンのOnClickイベントハンドラを呼び出して、入力内容をすべてクリアすればいいよ。

ナッキー:結果を表示していれば、クリアボタンのイベントハンドラを使ってクリアするんですね。イベントハンドラの呼び出しってどうやるんでしたっけ?

高橋先生:前回やったところだよ、しょうがないな。書き方はこう

イベント名(Sender);

だよ。Senderはコンポーネント名を入力してもいいけど、一般的にはSenderパラメータを流用するんだったね。

ナッキー:そっか、思い出した。イベントハンドラが呼び出される原因を作ったコンポーネントが入ってるSenderをパラメータに使うんでしたね。


思い出したところで、コードを書いてみます。テンキーを押したところでチェックしたいのでsbtn1ボタンのOnClickイベントハンドラで挑戦。コードエディタで、sbtn1Clickを探します。エディットに値をセットする前にクリアのコードを書いたほうがいいんだって。「=」が見えていれば、計算結果が表示されているから、lblEqualのVisibleがTrueのときクリアボタンのイベントハンドラを呼ぶのね。beginの下に空白行を作って、太字部分を追加します。

procedure TfrmCalc.sbtn1Click(Sender: TObject);
begin
  if lblEqual.Visible then
    btnClearClick(Sender);

//  edtInput1.Text := edtInput1.Text + '1';

  if lblEnzan.Caption = '' then
    edtInput1.Text := edtInput1.Text + '1'
  else
    edtInput2.Text := edtInput2.Text + '1';
end;

これを残りのテンキーのボタン、全部にコピーするんですね。

高橋先生:イベントハンドラを共有してみよう。実は、1つのイベントハンドラを異なるコンポーネントや、異なるイベントで一緒に使うことが簡単にできるんだ。

ナッキー:わ、同じ処理をするときには便利~

高橋先生:設定方法は簡単だよ。フォームデザイナのオブジェクトインスペクタの[イベント]ページでイベントハンドラを設定する際、ダブルクリックではなくて右端のプルダウンボタンで共有したいイベントハンドラ名を選択する。

Hide image
06イベント共有

図06 イベントハンドラの共有

まずは、sbtn1Clickの処理をすべてのテンキーボタンで共有したいので、分かりやすい名前に変更しよう。イベントハンドラの名前変更は、オブジェクトインスペクタの[イベント]ページでできる。

Hide image
07イベントハンドラ名変更

図07 イベントハンドラ名の変更


イベントハンドラ名を変更します。フォームデザイナで、sbtn1ボタンを選択。オブジェクトインスペクタの[イベント]ページで「入力」の「OnClick」に「TenKeyClick」と入力します。変更を確認するため、入力した「TenKeyClick」の部分をダブルクリック。ちゃんと1行目のsbtn1ClickがTenKeyClickになっていますね。

procedure TfrmCalc.TenKeyClick(Sender: TObject);
begin
  if lblEqual.Visible then
    btnClearClick(Sender);

//  edtInput1.Text := edtInput1.Text + '1';

  if lblEnzan.Caption = '' then
    edtInput1.Text := edtInput1.Text + '1'
  else
    edtInput2.Text := edtInput2.Text + '1';
end;

次に残りのテンキーのイベントハンドラをすべて「TenKeyClick」に変更します。テンキーボタンを選択してオブジェクトインスペクタの[イベント]ページで「入力」の「OnClick」でプルダウンボタンをクリックして、「TenKeyClick」を選択します。

高橋先生:イベントハンドラを共有しちゃったけど、クリックするボタンごとに入力される数値が異なる点には注意してね。全部同じ処理にしちゃうと、どのキーを押しても同じ数値しか入らない。

ナッキー:そうか~、単純じゃなかったのね。

高橋先生:テンキーの処理みたいに、大枠は同じだね。クリックされたボタンによって、ちょっとだけ違う処理をするときには、Tagプロパティを使って区別するといいんだ。Tagプロパティはプログラマが識別に使う、見た目に影響のないプロパティ。SenderのTagプロパティを見れば、どの処理をすればいいか区別がつくわけだ。

ナッキー:Tagプロパティにテンキーの数字を入れておけば、どのキーが押されたか分かるって訳ね。

高橋先生:そういうこと。でも、TagプロパティはInteger型だから、表示用に文字列として扱うには、型変換しなきゃね。

ナッキー:型変換関数を使って、変換するんですよね。

高橋先生:覚えていたかな。Integer型からstring型に変換するので「IntToStr」関数だよ。テンキーの数値とTagプロパティの値をリンクさせればコードの中で使うことができる。イベントハンドラのSenderパラメータからTagプロパティを参照する。ほんとは、SenderがTSpeedButton型だと良かったんだけど、型が違うのでSenderも型変換が必要だね。変換には「as」演算子を使用するよ。

A := Sender as 型名;

A変数の型を、型変換する型名とそろえておいてね。今回はAをTSpeedButton型で用意します。

ナッキー:コンポーネントも型変換しちゃうんだ。

高橋先生:そうしないと、Tagプロパティは、TObject型のSenderのままでは参照できないからね。


Tagプロパティから設定します。フォームデザイナでsbtn1ボタンを選択してオブジェクトインスペクタの[プロパティ]ページで「その他」の「Tag」プロパティに「1」を入力します。同様にしてsbtn2には「2」のように、テンキーボタンすべてにTagプロパティを設定します。sbtn0は「0」のままでいいので変更しません。

sbtn1

カテゴリ名

プロパティ名

設定値

その他

Tag

1

sbtn2

カテゴリ名

プロパティ名

設定値

その他

Tag

2

sbtn3

カテゴリ名

プロパティ名

設定値

その他

Tag

3

sbtn4

カテゴリ名

プロパティ名

設定値

その他

Tag

4

sbtn5

カテゴリ名

プロパティ名

設定値

その他

Tag

5

sbtn6

カテゴリ名

プロパティ名

設定値

その他

Tag

6

sbtn7

カテゴリ名

プロパティ名

設定値

その他

Tag

7

sbtn8

カテゴリ名

プロパティ名

設定値

その他

Tag

8