CreationCollege-TOP
Now Printing..
▲ C.College TOP▲ D-Tech Top.情報局2号館

∇ 脚注
※1:文字通り加算しかできない電卓で、プログラムの初歩の初歩でしばし作ることがあります。


∇ 感想はこちらに
クリエイターズボード(掲示板)

- Article: 複数のグループオブジェクトを操作する

Update: 2000/6/18


Sender を有効に

 VisualBasicにはグループ配列なるものが存在します。それは、ほぼ同じように使われるオブジェクトに対して、配列を提供し、その配列番号を用いてメソッド等にアクセスできるという便利なものなのですが、この手のものはDelphiには貼り付けコンポーネントの形では用意されていません。では、グループオブジェクト的なものはDelphiではどのように処理するのか、というのがこの記事の主題です。


 たとえば左の加算電卓(※1)を考えてみましょう。0〜9まで、押したボタンの、その数値情報をどこかに記憶させるわけですが、この9個のボタンの処理のために、9個のOnClickプロシージャを用意するべきでしょうか?ほとんど同じ処理だから、一つのプロシージャで充分ですよね。よって、各ボタンのOnClickイベントの先を、Form1.Button1Click(); と割り当てる事にします。では共通処理はどうなるでしょう。

 Delphiはイベント通知の際、ほとんどにおいて「Sender: TObject」という引数を持っています。このSenderには、読みだし元のオブジェクトが記憶されています。Button?から押されたなら、Button?が入っている訳です。ここでやりたい事は、そのButton?の Caption プロパティを取り出して保存させたい、ということですよね。コードはなんとなくこんな感じになるはずです。

  procudure TForm1.Button1Click(Sender: TObject);
  begin
    Kiokustr:=Kiokustr+Sender.Caption // 全然ダメ
  end;
 これをコンパイルするとエラーになります。ここには一つ問題点がありまして、確かにSender にはButtonオブジェクトが入ってくるわけですが、コンパイラは本当にそれが Buttonオブジェクトなのか、このソースでは判断が付かないのです。 TButton.Caption というメソッドはありますが、 TEdit.Caption というメソッドはありません。Cの経験者にはお馴染みだと思いますが、ここでプログラマは、「これがTButton型なんだよ」とコンパイラに明示的に教えてあげる「キャスト」という処理を加えてあげる必要があります。書式は2種類ありまして、

  • TButton(Sender).Caption;
  • (Sender as TButton).Caption;

     こうすると、「Senderというのが、TButtonとして扱ってね」という事を伝えられ、先のプログラムも意図どおり、押したボタンに書いてある数値情報を読み取って記憶ができるというわけなのです。

    キャストでは不可能なオブジェクト変換

     これでグループオブジェクトの操作はOKかなと思ったら、実はもう一つ厄介な問題が残っています。次の図例を見てください。


     なんだかモノポリー的なソフトを作っているようです。さてイベントが発生し、ある対象の金額を300,000減らす事になりました。金額関係のコントロールは上から Edit1、Edit2、Edit3、Edit4、Edit5、Edit6と並んでいます。やりたい事は、指定されたTEditオブジェクトのEditコンポーネントの中身を取り出し、300,000減らして戻す、という処理です。ここではSender引数は存在しません。

     上からc番目のEditコンポーネントを操作したい場合、どうしましょう。一番原始的な解決策が、if文でそれぞれ次のように判定させる方法です。

      if c=1 then Edit1.Text:=InttoStr(StrtoInt(Edit1.Text)-300000);
      if c=2 then Edit2.Text:=InttoStr(StrtoInt(Edit2.Text)-300000);
      if c=3 then Edit3.Text:=InttoStr(StrtoInt(Edit3.Text)-300000);
      if c=4 then Edit4.Text:=InttoStr(StrtoInt(Edit4.Text)-300000);
      if c=5 then Edit5.Text:=InttoStr(StrtoInt(Edit5.Text)-300000);
      if c=6 then Edit6.Text:=InttoStr(StrtoInt(Edit6.Text)-300000);
    
     かかる処理がここだけでしたら良いのですが、そういった操作が何度も、いろんなパラメータに対して発生した場合、プレイヤー数がさらに多かった場合、とんでもないコードになります。鷹月の公開しているテキストSLG「ラープルール戦記」はこういう操作のオンパレードです。
     ここで悪戦苦闘する人は、オブジェクト名称をStringで保持できないかと考えます。つまり、

     for i:=1 to 6 do str[i]:='Edit'+InttoStr(i);
     と予め設定しておいた上で、
     TEdit(str[c]).Text:=InttoStr(StrtoInt(TEdit(str[c]).Text)-300000);

     このように、文字列をTEdit型にキャストしてアクセスしようと試みます。しかしその結果、コンパイルは通りますが、見事にアドレス違反でぽしゃります。オブジェクトを別型のオブジェクトとして扱う事は可能ですが、Stringの中身(オブジェクトですらない)をオブジェクトとして扱う事はメタ的に違反しています。

     では解法いきましょう。完全にテクニックなので、そういう物だと思ってください。

      // 変数宣言部
      var str: array[1..6] of TEdit;
    
      // 記憶部
      for i:=1 to 6 do str[i]:=TEdit(FindComponent('Edit'+InttoStr(i)));
    
      // アクセス部
     str[c].Text:=InttoStr(StrtoInt(str[c].Text)-300000);
    
     このソースのミソは「記憶部」にある、FindComponent()にあります。これはStringで指定した名称のコンポーネントがないか探し、そのオブジェクトを返すという便利な関数です。もちろんコンパイラは、戻り値がTObject型だと認識してしまうので、それを、TEdit型だと明示的にキャストしてやり、Teditオブジェクト配列 str[i] を形成するわけです。このようにすれば、期待通りの結果である、指定したコントロールの値を300,000減らす事ができるわけです。
     Delphiでグループオブジェクトを使うときが出てきたら、この事を思い出してくださいね。

    - 鷹月ぐみな


  • .
    Copyright- 鷹月ぐみな(gumina)たかつきCOMPANY 1997-2000.▲ C.College TOP