Delphi入門

AからZまでを打つタイピングソフトを作ってみる!

関数の定義

さて、プログラムは基本的なものばかりでつまらないと思いますので、 いよいよ簡単なタイピング「AからZまでをタイピングする!」というプログラムを考える事にします。 これはハッキリ言っていままでの知識を使えばさほど難しくはありません。 キーボードが押された時はどうすればいいの?と思うでしょうけど、実はすごい簡単です。

ボタンがクリックされた時のイベントと同じように、フォームのイベントに、 キーボードが押されたらというイベントがありますので、そこに書けばいいだけなのです。 フォームの「オブジェクト インスペクタ」を見て下さい。フォームのどこでもいいのでクリックすると切り替わるはずです。 次のような画面になると思います。

オブジェクト・インスペクタ

このような画面になると思います。 ちなみにこれは何度も言うように「Delphi5」のものです。「Delphi3」などでは、 「OnMouseWheel」(マウスホイールを感知するイベント)とかはありません。 ここの「OnKeyPress」という所があります。 ここは、主にキーボードから任意のキーが押された時のイベントを感知してイベントを実行します。下にも、

  • OnKeyDown
  • OnKeyUp
  • OnKeyPress

などがありますが、これは「Shft」「Alt」「Ctrl」が同時に押された時などに使うもので今はあまり考えないで下さい。 今は「AからZまでを押されたかどうか?」のプログラムだけを考えて下さい。

そして、ここの空欄をダブルクリックして下さい。 そうすると、次のような画面に切り替わるはずです。ここで、ちょっと不思議に思うのが、 今までは確か、何やら(Sender:TObject)と書かれていた場合が多かったですが、 今回は「var Key: Char」などと見慣れないものが書かれています。 ここで新たに勉強しなくてはならない概念があります。それは「関数」です。 「関数」とは数学でのあの「関数」と対して変わりません。というより、そこから派生しているものです。

y = f(x) = x2 + x + 1

というのを考えて見る事にしましょう。この関数は曲線のグラフとか書くときによく使われますね。 中学時代に習った基本的な形だと思います。xの取る値によって、yの値はどんどん変化します。 これと同じです。結果は、

  • f(0) = 02 + 0 + 1 = 1
  • f(1) = 12 + 1 + 1 = 3
  • f(2) = 22 + 2 + 1 = 7
  • f(3) = 32 + 3 + 1 = 13

というのはお分かりでしょうか。 この場合に置いて、f(x) の xの事(0、1、2、3)を引数(ひきすう)、 結果の事(1、3、7、13)の事を戻り値とか返り値といいます。これは必ず覚えて下さい。 また、「返り値」というと「返り血」とか変換で出てきて個人的にイヤなので、 ここでは「戻り値」を使って説明を進めて行きたいと思います。 これが関数です。プログラムでも全く同じです。今回の場合は、

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin

end ;

ですが、この()内が引数となっています。つまり、引数に「Key : Char」というのがあります。 「Sender」というのはちょっとまだ理解しなくても構いません!無視して下さい。 また、「Char」というのは型です。文字列型でも、数値型でもないです。 「Char」は文字一文字を表す型です。’A’とか’B’とかはChar型です。 それに対し、’AB’は2文字以上なので文字列型です。 キーボードで押されるキーは通常は「A」とか「B」とか一文字ですよね?

それで、これの意味する所は、 先程の例で考えると、「3が代入されたら結果13を返す…」というものを「Keyの値が代入されたら、 結果を返す…」という事になります。 この場合の結果は「begin」〜「end」で囲まれた処理で行われる結果という事になります。

なんだかよく分からないという方もいると思います。 実際に、自分もつい最近まではプログラムをずっとやってたのに「引数」という概念を知りませんでした(汗)。 ですから、別にそこまで詳しく知らなくても大丈夫という訳です。 関数というのもプログラムをやるのに当たってのひとつの大きな壁だと思いますので、 じっくりと力を付けて行って下さい。では、簡単にキーボードからキーを押した時に、 簡単なメッセージを表示するプログラムを考えて見る事にしましょう。

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
   KeyPreview := True ;
   if Key = 'a' then showmessage('キーボードのキー「a」が押されました!') ;
end ;

というのが使い方の例です。Keyというのは変数です。変数宣言は何故しないの?と思われますが、 引数で受け渡されるからです。といっても、これでは自分でも何説明してるか分からないので、 簡単にいいますと、キーボードからキーを押すと、 その押したキーが引数Keyに値が格納される訳です。今は分からなくても構いません。 そういうものなんだ!でいつも通り構いませんので、感覚を取りあえずはつかんで欲しいと思います。

取りあえずどんな感じかをつかむ

それでは先程のを実行して見て下さい。 実行されたら、「キーボード」で「A」を押して下さい。メッセージが出ましたか?出たら成功です。 このように使います。さて、これを使えばもう「AからZまでをタイピングする」という事も実現できそうです。 「北斗の拳」の「激打」のスタイルのように、

abcdefghijklmnopqrstuvwxyz

というものを正しく打てたら先頭からどんどん消して行くというのを考えましょう。 間違えたらミスをカウントするというシステムは搭載しても構わないのですが、 そういう発展的な事は後回しとして、今は只「AからZ」までをタイピングするプログラムだけを考える事にします。 フォームのデザインは自分の好きで構いませんが、一応、僕は次のようなフォームを作って見ました。

サンプルフォーム(A to Z)

今まであまり説明しなかった「TLabel」というコンポーネントを使って見て下さい。 これはウィンドウの任意の場所に文字を配置する時に使います。 コンポーネントパレット(コンポーネントがあるツールバーの事をコンポーネントパレットといいます。)というマークの コンポーネントがあるので配置して見て下さい。

また、文字列の変更はお馴染み「Caption」の値を「Label1」は「◆AからZまでをタイプして下さい!」、 「Label2」には「abcd・・・xyz」と書いて下さい。 そして、タイピングしたら、 文字列をどんどん消すのでこれをまた「abcd・・・xyz」と「Label2」の「Caption」を元に戻すためのボタンを作って起きます。 これで何回も練習できるようになる訳ですね。

「Label」の文字のフォントやフォントスタイルを変える時には、 「オブジェクト インスペクタ」の「Font」の部分を各自設定して下さい。簡単なので説明は省きます。 フォントの設定の仕方は「Word」や「MSペイント」などと同じですのですぐ分かると思います。 別に文字色とかも黒でなくてもいいですよ。さて、これで準備は整いました。 ウィンドウの設計はこれで終了です。残るはプログラムのみですね。ここまではOKでしょうか。

では、プログラムを書いて行く事にしましょう。 まずは、簡単な「元に戻す」ボタンのプログラムから書いて行きます。これは簡単だと思います。 次のプログラムを見て頂ければ一目瞭然だと思います。 「TLabel」も「TButton」とかもコンポーネントの扱い方はだいたい同じです。 「Button1.Caption」で「Button1」の名前が変更できたように、 「Label1.Caption」で「Label」の文字も変更できます。

procedure TForm1.Button1Click(Sender: TObject);
begin
   Label2.Caption := 'abcdefghijklmnopqrstuvwxyz' ;
end ;

でボタンを押すと、元通りに戻すプログラムは完成しました。 さて、後はメインプログラムですね。と言ってもほんの数行で実現できます。 それが「Delphi」の優れた所です。結構、新しい構文とか出てきますので、 まずはサンプルプログラムを見て何となく納得して下さい。

TForm1.FormKeyPress(Sender: TObject; var Key: Char);
var s : string ;
begin
   s := Label2.Caption ;
   if Key = Copy(s,1,1) then
   begin
      Delete(s,1,1) ;
      Label2.Caption := s ;
   end ;
   
   if Lable2.Caption = '' then
   begin
      showmessage('タイピング完了!') ;
      Button1.OnClick(Sender) ;
   end ;
end ;
procedure TForm1.FormCreate(Sender: TObject);
begin
   //Delphi3.0以前の場合はForm1のOnCreateイベントに以下を記述
   KeyPreview := True ;
end ;

といっても何やら全然見たことないようなものが出て来ていますね? しかし、そんなに難しい事をしている訳ではありません。今覚えればいいだけの話です。 「KeyPreview」「Copy」「Delete」について話を進めて行きます。

KeyPreview

「キー・プレビュー」とそのまま読みます。キーボードで押されたものを判定したり、 キーを受け付けるようにします。これがないと、キーボードを押しても全く反応しません。 よくミスするので、キーボードからキー操作をする時には必ず、 「Delphi3」以前の場合は「Form」の「OnCreate」イベントに、「KeyPreview := True ;」を書いて下さい。 また、Delphi5の方は、書く必要がありません。Delphi5の方は、「オブジェクト インスペクタ」で 「KeyPreview」というのがそのままあります。ここを「True」にしてやって下さい。

Copy

「コピー」です。その名の通り、「文字列をコピーする」関数です。使い方は、

Copy(文字列, 文字列のコピーを始める位置, コピーする長さ)

です。例えば、

s := Copy('abcde',2,2) ;

としますと、文字列「abcde」の2バイト目から2バイトを取り出すというので、 変数s には「bc」という文字列が格納される事になります。また、今回の場合ですと、

Copy(s,1,1) ;

ですので、これは、変数s の先頭1バイトを取り出している事になります。 しかも、これはちょっと先程言いました文字列一文字の「Char」型です。 Keyも「Char」型なので型は一致しているので判定できる訳です。 ここでは、変数s には「Label2.Caption」の値が代入されているので、 「Label2.Caption」に書かれている先頭1文字と、押したキーが一致していれば… という判定を行っている事になります。

尚、日本語が混じった時には、日本語の全角は2バイトなので、

s := Copy('あいうえお',3,2) ;

とした場合、変数sには、「い」という文字列が代入される事になります。

Delete

「デリート」です。「文字列の指定個所を削除する」関数です。使い方は、同じです。

Delete(文字列, 文字列の削除し始める位置, 削除する長さ)

です。「Copy」との大きな違いは戻り値を返さないという点で大幅に異なります。 「Copy」関数は、戻り値を返しますが、「Delete」は戻り値を返しません。 例えば、2バイト目から2バイト削除したい時には、

s := Delete(s,2,2) ;

とする必要はなく、

Delete(s,2,2) ;

とします。これで、変数s の2バイト目から2バイト目までを削除して更に、 変数sを更新してくれます。ここが大きなポイントです。って難しいですね。 自分も最初なら絶対理解できてないと思います。 今は、「Copy」が「文字列をコピーする時に使う」、 「Delete」が「文字列を削除する時に使う」程度で構いません。 時期に慣れて来ると分かると思います。 Delete(s,1,1)は先頭の1文字を削るという意味になります。

しかし、やっぱり戻り値とか、 引数とかは分かってた方がいいと思いますので少々語りますと、Copy関数の場合は、 「文字列」「文字列のコピーする最初の位置」「コピーする長さ」という3つの引数を与える事によって、 戻り値と返します。しかし、重要なポイントがあります。

重要なポイント。Copyでは、'abcde'、 2(バイト目)、2(バイト)という情報を与えて、初めて'bc'という戻り値(結果)を返す訳です。 それに比べて、Deleteは、戻り値を返さずに、 指定された文字列を削るという動作を行うだけで、戻り値は返しません。次の2つの例を見て見ましょう。

procedure TForm1.Button1Click(Sender: TObject);
var s : string ;
begin
   s := 'abcde' ;
   Copy(s,3,2) ;
   showmessage(s) ;
end ;
procedure TForm1.Button1Click(Sender: TObject);
var s : string ;
begin
   s := 'abcde' ;
   Delete(s,3,2) ;
   showmessage(s) ;
end ;

結果は前者の方は、「'abcde'」と、 後者の方は「'abe'」という結果がメッセージで表示されているのが確認できたと思います。 つまり、Copy関数は戻り値をきちんと持つので、代入してあげなければダメな訳です。

s := Copy(s,3,2) ;

としてやらなければならない訳ですね。それでは製作に入りましょう!

文字列処理をふまえて実際に動かして見よう!

ではでは、実際に見て行く事にしましょう。 このプログラムを日本語に訳すとすれば次のようになります。

  1. キーボードを使って、キー判定ができるように「KeyPreview」を「True」に設定する!(Delphi5未満ですが、別にDelphi5でも書いても何ら問題は起こりません。)
  2. 変数s に現在の「Label2」の「Caption」の文字列を代入する!
  3. もし、押されたキーと、変数sの最初の文字とが一致したら、変数s の最初の1文字を削除して、それを「Label2」の「Caption」に再び反映させる!これにより、「Label2」の「Caption」は正しくキが押されていると、どんどん先頭から消えて行く!という仕組みになります。
  4. そして、文字列が空になったら、すべて打てた証拠なのでメッセージを出して、「Button1」をプログラムから「Click」させてあげる。

以上で基礎の解説は終わりで、実行して見て下さい。 「おお!」と感動した方もいらっしゃると思いますが、まずはこれを機に自分でプログラムを改造して見て下さい。 何秒かかったか?とか時間に関する話はちょっと今はまだ難しいのでもう少し慣れてからお話します。

これを応用させて、「タイピングミスした数をカウントする」プログラムも作れると思います。 ちょっと考えて見て下さい。さほど難しくはありません。 プログラムもどこかにちょこっと追加すれば実現できます。お分かりになった方は…理解できています。 でも、いきなり、難しい話をしてしまいましたが…ついてこれたでしょうか? もし、分からなければ「Delphi」付属の「ヘルプ」もちょっと最初からはレベルが高いですが、 「Copy」とかキーワードで検索掛けるとサンプルプログラムとか出てきますので眺めて見るのもいいと思います。

それと、説明していなかったのですが、 最後の行にある、「Button1.OnClick(Sender)」というのは、 実際に人間の手でボタンをクリックするという事をプログラム内で実行してしまうのです。 つまり、タイピングが打ち終わったら、メッセージを表示して、 また、「Label2」の「Caption」を元に戻すという事を行っております。

ミスタイプ数を計測するプログラムを考えて見ましょう。 更に、このフォーム上にいくつかコンポーネントを配置します。 「Label」を更に2つ追加します。次のようにしてみましょう。 ミスタイプ数を計測するのではなく、 きちんと打てたら1点、ミスったら1点減点するというアプリケーションを作って見ましょう!

更に、ちょっと別のコンポーネントも使って見ましょう。 今まではすべて「Standard」でしたが、「Additional」の所に、「TBevel」というコンポーネントがあります。 これはフォームデザイン用です。今までのはちょっと味気ないので立体感を出して見ましょう。 配置後は次のように自分はしてみました。

タイピング(改良版)

「TBevel」はデザイン用なのでイベントは何もありません。 「Label3.Caption」は'Score :'にして「Label4.Caption」は'0'に設定して下さい。 ここの「Label4」の値を判定によって得点を追加させたり減点させたりします。 それで、メインプログラムを書き換える訳ですが、ここでどういう風に書き換えたらいいか次を見て下さい。さほど難しくはない事が分かります。今までやってきた事を軽く使えば簡単に実現できますね。

procedure TForm1.Button1Click(Sender: TObject);
var s : String ;
begin
   Label2.Caption := 'abcdefghijklmnopqrstuvwxyz' ;
   Label4.Caption := '0' 
end ;
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
var s : String ;
begin
   //Form1のOnKeyPressイベントに書くプログラム(追加した部分は色を変えています)
   s := Label2.Caption ;
   if Key = Copy(s,1,1) then
   begin
      Delete(s,1,1) ;
      Label2.Caption := s ;
      Label4.Caption := IntToStr(StrToInt(Label4.Caption) + 1) ;
   end else Label4.Caption := IntToStr(StrToInt(Label4.Caption) - 1) ;

   if Label2.Caption = '' then
   begin
      showmessage('タイピング完了!あなたの得点は'+Label4.Caption+'点です!') ;
      Button1.OnClick(Sender) ;
   end ;
end;

変更した個所は、赤くしてあります。 タイプをミスしたら1点減点、タイプ成功したら1点プラスします。 つまり26点取れればミスなくタイプできたという事になります。

ここで、「Label4」の「Caption」を最初に「0」としておかなかった場合は、 整数値に変換できないのでエラーが起きますので注意して下さい。「else」文以下は、つまり、 表示されている問題の先頭1文字と、打ったキーが一致しなかった場合に処理される事になりますので、 これでうまくできる訳です。

それにしても、こんな簡単な数行のプログラムと、フォームのデザインだけで作れてしまいます。 「Delphi」の凄さ… 何となく分かりましたか?勿論、長年プログラムをやっている人は、 「内部ではどう動いているの?」とか聞いてくる人いますが、 「Delphi」にそのような質問は愚問というものです。

そんなに気になるようならば、例えば、「Delete」の部分にマウスカーソルを合わせて、 「Ctrl」キーを押しながらクリックして見て下さい。仕組みが分かると思います。 しかし、内部って何でしょう?APIレベルの事でしょうか?APIも基本的にはプログラミングの原点かも知れませんけど、 本当の原点は、それを言うならば、もっと内部、いわば半導体設計の部分から気にならないのが不思議です。内部を内部を… と突き詰めれば最後は半導体で電圧値が5Vに達したら1を、0V付近で0を判定しているなど、それが原点ではないでしょうか? それも理解した上でそういう事を言っているのでしたらいいのかも知れませんし、あまり理屈をこねるのは嫌いですが。 こだわりすぎるのは個人的に良くないと思います。

今後どういう風に開発していけば?

そういう事は段々慣れてから徐徐に知っていけば良いのです。 最初から難しい事をやろうとするからみんなプログラム言語というと引いてしまうのです。 その変が大学教育などでもプログラム言語を教えるという事に関してあまりよろしくない事と自分は感じております。

完成したプログラムは自由に配布しても構いません。 今回作ったものも単体で動くはずです。実行ファイルを友達に渡してやってもらっても動くはずです。 また、アプリケーションのアイコンや、いろいろ変更したい時には、 メニューの「プロジェクト」→「オプション」を選んで下さい。いろいろ設定できます。 ここで、ソフトのアイコンなどを設定して、再びコンパイルすればアイコンが変わっています。

少し、高度な話をしておく事にします。 今回のプログラムでは、小文字しか受け付けませんでした。 よって「Caps lock」とかが気付かないで入って大文字入力になっていた場合は受け付けてくれません。

そこで、大文字で打っても、小文字で打ってもきちんとタイプできるようにしてみます。 これで実行してみると分かりますが、「Shift」を押しながらキーを押したりしても動きません。 という訳で、プログラムを次のように書き換えます。

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
var s : String ;
begin
   //Form1のOnKeyPressイベントに書くプログラム(追加した部分は色を変えています)
   s := Label2.Caption ;
   if LowerCase(Key) = Copy(s,1,1) then
   begin
      Delete(s,1,1) ;
      Label2.Caption := s ;
      Label4.Caption := IntToStr(StrToInt(Label4.Caption) + 1) ;
   end else Label4.Caption := IntToStr(StrToInt(Label4.Caption) - 1) ;

   if Label2.Caption = '' then
   begin
      showmessage('タイピング完了!あなたの得点は'+Label4.Caption+'点です!') ;
      Button1.OnClick(Sender) ;
   end ;
end;

「LowerCase」というものを使います。 「LowerCase」とは和訳すると「小文字」です。これは「LowerCase()において、()内の文字列を小文字化」する関数です。 逆に大文字化する関数は「UpperCase」を使います。これで、打ったキーである変数Key を小文字化してしまう訳です。 これで大文字で入力してしまっても大丈夫な訳ですね。

今回の場合は、LowerCase()の()内は英語でしたが、 日本語が混じった場合(全角文字が混じった場合)はうまく小文字に変換できません。 その場合には、「AnsiLowerCase」を使って下さい。 また、逆に大文字化する時には「AnsiUpperCase」を使って下さい。

以上の解説で、「AからZまでをタイプするプログラム」を考えて来ましたが、 それでは、次は少し深く掘り下げて、テキストファイルを読み込んでそこから一行づつ問題を出す、というプログラムを考えて行きましょう! 次のテーマへ進んで下さい。