
【Delphi / C++Builder Starter チュートリアルシリーズ】
第5回 ‟イベントに合わせて動かしてみよう„
10月24日より始まりました 「Delphi / C++Builder Starter チュートリアルシリーズ」。全10回、12月26日まで、毎週17時より30分間で、無料でダウンロード&利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、ゲームを作るまで一通り、セミナーを実施してまいります。
このブログでは第5回のサマリーと参考情報など掲載いたします。
なお、前回および初回分の内容に関するブログ記事は以下のリンクからお読み頂けます。
<< 第4回 ‟UI アニメーションの設定„
<< 第1回 ‟無料で始めよう アプリ作成„
[アジェンダ]
- イベントとイベントハンドラについて知る
- イベントの種類について知る
[開発環境インストール]
[Delphi / C++Builder Starter チュートリアルシリーズ] 第1回 ‟無料で始めよう アプリ作成„をご参考になり、開発環境をインストールしてください。
[第5回のサンプルコードのダウンロード]
以下のURLよりzip形式でダウンロード頂けます。演習と応用すべてのコードがDelphi/C++の両方とも含まれています。
https://github.com/kazinoue/2016_StarterTutorial_S1-5/archive/master.zip
[演習1] マウスのイベントに処理を書く。
この演習では OnClick, OnDblClick, OnMouseDown, OnMouseUp というイベントに対してイベントハンドラを割り当てました。以下では OnClick に対するコードを例に Delphi と C++ の実装の違いをご紹介しています。
Delphi では文字列はシングルクオートで挟みます。
// procedure TForm1.Button1Click(Sender: TObject); begin // Lines.Insert は指定の場所に行を差し込む処理です。 // 0 は 0 行目に差し込む = 先頭行に差し込むことを意味します。 Memo1.Lines.Insert(0,'シングルクリック'); end;
C++ ではダブルクオートで挟みます。また最初のシングルクオートの前に L と記述していることにも注意が必要です。
また、Memo1.Lines.Insert ではなく、Memo1->Lines->Insert と記述している点も大きな違いです。
// void __fastcall TForm2::Button1Click(TObject *Sender) { // 文字列を記述するときは L"" と表現します。 // Delphi では Memo1.Lines.Insert と記述しましたが、 // C++ の場合は . のかわりに -> を用いている点にご注意ください。 // Lines->Insert は指定の場所に行を差し込む処理です。 // 0 は 0 行目に差し込む = 先頭行に差し込むことを意味します。 Memo1->Lines->Insert(0,L"シングルクリック"); }
[演習2] キー操作のイベントに処理を書く。
この演習では OnKeyDown のイベントで「入力可能文字のキー入力」と「Enter や Shift などの特殊キー」で値の取得が異なることを学びました。
入力可能な文字は KeyChar で、特殊キーの情報は Key で渡されます。このときに Key は数値型であるため、これを文字列型に変換せねばなりません。このときの書き方が Delphi と C++ では異なります。
Delphi
// procedure TForm1.Memo1KeyDown(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin Edit1.Text := KeyChar; // Key は Word型 = 数字型なので、Edit2 で表示するためには // 文字列型に変換せねばならない。 Edit2.Text := Key.toString; end;
C++
// void __fastcall TForm2::Memo1KeyDown(TObject *Sender, WORD &Key, System::WideChar &KeyChar, TShiftState Shift) { Edit1->Text = KeyChar; // Key は WORD型 = 数字型なので、Edit2 で表示するためには // 文字列型に変換せねばならない。 Edit2->Text = IntToStr(Key); }
[演習3] 一定時間ごとに発生するタイマーイベントを使う。
この演習では現在の日時を Memo に表示する周期を interval で変えました。現在の日時は Now() という関数で取得できますが、それを文字列型に変換するために DateTimetoStr() という関数を組み合わせています。
これについては Delphi と C++ で大きな違いはありません。
// procedure TForm1.Button3Click(Sender: TObject); begin Memo1.Lines.Insert(0,'インターバルを 500 に変更します'); Timer1.interval := 500; end; procedure TForm1.Timer1Timer(Sender: TObject); begin // 現在の日付時刻は Now() で取得できます。 // 取得した値は TDateTime 型であり、これはそのまま表示できないため、 // DateTimetoStr() という関数で文字列型に変換しています。 Memo1.Lines.Insert(0,DateTimetoStr(Now())); end;
// void __fastcall TForm2::Button2Click(TObject *Sender) { Memo1->Lines->Insert(0,L"インターバルを 2000 に変更します"); Timer1->Interval = 2000; } //--------------------------------------------------------------------------- void __fastcall TForm2::Timer1Timer(TObject *Sender) { // 現在の日付時刻は Now() で取得できます。 // 取得した値は TDateTime 型であり、これはそのまま表示できないため、 // DateTimetoStr() という関数で文字列型に変換しています。 Memo1->Lines->Insert(0,DateTimeToStr(Now())); }
[演習4] アプリケーション起動や終了時のイベントを使う。
この演習では onFormCreate や onFormDestroy でアプリの起動時や終了時のイベントを扱ってみました。このような処理はアプリの終了時に状態を保存したり、アプリの起動時に保存済みの状態を復元するなどの実装に使えます。
自体は基本的にこれまでの演習の内容と大差ありません。
// procedure TForm1.FormCreate(Sender: TObject); begin ShowMessage('起動時のメッセージ'); end; procedure TForm1.FormDestroy(Sender: TObject); begin ShowMessage('終了時のメッセージ'); end;
// void __fastcall TForm2::FormCreate(TObject *Sender) { ShowMessage(L"起動時のメッセージ"); } //--------------------------------------------------------------------------- void __fastcall TForm2::FormDestroy(TObject *Sender) { ShowMessage(L"終了時のメッセージ"); }
[応用] アナログ時計を作ってみる
これまでに紹介した内容と、いくつかのシェイプのコンポーネントを使うと、アナログ時計が作れます。
時計の針の角度は、シェイプのコンポーネントの RotationAngle プロパティを変えるだけで描画できますから、特に難しいことはなく、単に針の角度の計算を正しく行えばよいのです。実際の実装は以下の通りですが、実質的なコードは20行もなく、それぞれの針の角度を計算して RoundRect.RotationAngle に設定することと、Edit1 への現在時刻の表示しかしていません。
なお、このサンプルコードでは時刻を取り扱うために「変数」を用いています。変数については次回の第6回でご説明します。
// procedure TForm1.Timer1Timer(Sender: TObject); var // 日付時刻型です。現在の日時を取り扱います。 timestamp: TDateTime; // 時、分、秒は小数点が扱える数値型にします。 // これは後述する角度の計算で小数点を含む計算が必要となるからです。 Hour: Double; Minutes: Double; Second: Double; begin timestamp := Now(); // 秒を取得する処理 Second := SecondOf(timestamp); // 秒針の角度を変える。 // 1秒の角度は 360 / 60 = 6 なので、 // それを用いてコンポーネントの表示角度を変えている。 // なお、回転の中心位置は予め RotationCenter のプロパティで設定済み。 RoundRectSecond.RotationAngle := Second * 6; // 分を取得する処理 Minutes := MinuteOf(timestamp); // 分針の角度を変える。 // 1分の角度は 360 / 60 = 6 なので、Minutes * 6 で一応は角度が計算できる。 // しかし1分の動きが6度ということは10秒で1度動くわけで、この動きを無視することはできない。 // よって使用する分の値に小数点以下で秒の値を混ぜ込むようことで滑らかに動かすようにする。 RoundRectMinute.RotationAngle := ( (Minutes + (Second/60) ) * 6); // 時を取得する処理 Hour := HourOf(timestamp); // 時針の角度を変える。 // 1時間の角度は 360 / 12 = 30 だから Hour * 12 でもよいのだが、 // これでは時針の動きが雑すぎるので分単位の動きは必須。 // だから分単位の値を小数点以下に混ぜ込んで使う。 // ただし秒単位の処理は省略してよい。なぜなら時針は2分間で1度しか動かないのに // 秒単位の動きを正確に再現しても認識できないから。 RoundRectHour.RotationAngle := ( Hour + (Minutes/60) ) * 30; // しかし、どうしても秒単位の動きにも正確に追従させたいならこちらの計算式を使う。 // RoundRectHour.RotationAngle := ( Hour + ( (Minutes+(Second/60)) /60) ) * 30; // 現在時刻のデジタル表示を TMemo に出力する。 Edit1.Text := TimeToStr(timestamp); end;
C++
// void __fastcall TForm1::Timer1Timer(TObject *Sender) { // 日付時刻型です。現在の日時を取り扱います。 TDateTime timestamp; // 時、分、秒は小数点が扱える数値型にします。 // これは後述する角度の計算で小数点を含む計算が必要となるからです。 Double Hour; Double Minutes; Double Second; // 現在の日時を取得します。 timestamp = Now(); // 秒を取得する処理 Second = SecondOf(timestamp); // 秒針の角度を変える。 // 1秒の角度は 360 / 60 = 6 なので、 // それを用いてコンポーネントの表示角度を変えている。 // なお、回転の中心位置は予め RotationCenter のプロパティで設定済み。 RoundRectSecond->RotationAngle = Second * 6; // 分を取得する処理 Minutes = MinuteOf(timestamp); // 分針の角度を変える。 // 1分の角度は 360 / 60 = 6 なので、Minutes * 6 で一応は角度が計算できる。 // しかし1分の動きが6度ということは10秒で1度動くわけで、この動きを無視することはできない。 // よって使用する分の値に小数点以下で秒の値を混ぜ込むようことで滑らかに動かすようにする。 RoundRectMinute->RotationAngle = ( (Minutes + (Second/60) ) * 6); // 時を取得する処理 Hour = HourOf(timestamp); // 時針の角度を変える。 // 1時間の角度は 360 / 12 = 30 だから Hour * 12 でもよいのだが、 // これでは時針の動きが雑すぎるので分単位の動きは必須。 // だから分単位の値を小数点以下に混ぜ込んで使う。 // ただし秒単位の処理は省略してよい。なぜなら時針は2分間で1度しか動かないのに // 秒単位の動きを正確に再現しても認識できないから。 RoundRectHour->RotationAngle = ( Hour + (Minutes/60) ) * 30; // しかし、どうしても秒単位の動きにも正確に追従させたいならこちらの計算式を使う。 // RoundRectHour->RotationAngle = ( Hour + ( (Minutes+(Second/60)) /60) ) * 30; // 現在時刻のデジタル表示を TMemo に出力する。 Edit1->Text = timestamp.FormatString(L"hh:nn:ss"); }
なお、時計を1秒単位で表示するための TTimer では、OnTimer イベントの Interval を 500ms 以下に設定するほうがよいでしょう。たとえば、1秒単位の TTimer イベントで 17:15:00 という時刻を取り扱う場合に、もしかするとミリ秒単位の時刻は 17:15:00.000 かもしれませんし、あるいは 17:15:00.999 かもしれません。つまり1秒単位でのイベント処理では現実の時刻に対して表示上の時刻が1秒ずれる恐れがあります。しかしイベントの発生周期を500ms単位にすれば、現実の時刻とのズレは0.5秒以内に収まります。(github で公開中のサンプルコードでは100ms単位で設定しています)
■次回は11月28日(月)17:00より “ミニゲームを作ってみよう„ をお送りします。
それでは、また来週!
<< 第4回 ‟UI アニメーションの設定„
>> 第6回 ‟ミニゲームを作ってみよう„