生成したTPaintBoxに線を描画するには?

解決


デル太  2006-03-18 07:45:44  No: 20560

フォームに配置したPaintBoxには、問題なく線を描けるのですが、
自分で生成したPaintBoxは、線を描くことができません。
何か、特別な処理が必要でしょうか?

下記にソースを添付します。
ご指導いただけると幸いです。

なお、背景は透明なまま、線を描きたいと思っています。
よろしくお願いします。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    PaintBox1: TPaintBox;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  //TPaintBoxを生成するための宣言
  MyPaintBox: TPaintBox;
  MyLabel: TLabel;
begin
  //フォームをオーナーにして生成する
  MyPaintBox := TPaintBox.Create(Form1);
  //通常生成したオブジェクトはParentを指定すると表示される
  MyPaintBox.Parent := Form1;
  //キャンバスに描画しても、線が表示されない
  MyPaintBox.Canvas.LineTo(100,100);

  //こちらは線が表示される
  PaintBox1.Canvas.LineTo(100,100);
end;

end.


Mr.XRAY  URL  2006-03-18 08:13:41  No: 20561

>MyPaintBox.Canvas.LineTo(100,100);

この後に   Update; を追加して下さい.
TImageのCanvasとTPaintBoxへの描画の違いです.詳しい説明は省略させて
頂きます.


デル太  2006-03-18 08:33:06  No: 20562

Mr.XRAYさん、アドバイスありがとうございます。
後ろでUpdateしたところ、描画されませんでしたが、助言のおかげで以前TShapeを生成してTextOutにトライした際に得た情報を思い出しました。

次のように線を描画する前にUpdateを実行したところ、無事描画されました。

  //キャンバスに描画しても、線が表示されない・・・(A)
  MyPaintBox.Update;                                    ←この行追加
  MyPaintBox.Canvas.LineTo(100,100);

Updateに関する説明などでは「描画後に更新」のように読み取れますが、実際には、描画の前でアップデートするんですね・・。不思議な感じがします。

次は、再描画(OnPaint)を扱えるようにチャレンジすることになります。
すばやいレスをいただいたおかげで先に進めます。
ホームページも、いつも活用させていただいています。
ありがとうございます。


Mr.XRAY  URL  2006-03-18 08:52:19  No: 20563

>後ろでUpdateしたところ、描画されませんでしたが

あははっ,すみません.
それと,TPaintBoxを特定のUnit内でCreateして使用する時は,OnPaiintを定
義して使用すると確実です.たとえば,

  MyPaintBox := TPaintBox.Create(Self);
  with MyPaintBox do begin
    Parent := Self;
    Visible:=True;
    OnPaint := PaintBoxOnPaint;
  end;
end;

procedure TForm1.PaintBoxOnPaint(Sender: TObject);
begin
    Canvas.Pen.Color:=clBlack;
    Canvas.Pen.Width:=5;
    Canvas.Pen.Style:=psSolid;
    Canvas.MoveTo(0,0);
    Canvas.LineTo(100,100);
end;


Mr.XRAY  URL  2006-03-18 08:56:24  No: 20564

>OnPaiintを定義して使用すると確実です.たとえば,

これは,なぜかと言いますと,TPaintBoxでは再描画が必要な時に,もし
OnPaintが定義されていると,それを自動的に呼出すからです.


花の命は  2006-03-18 09:46:47  No: 20565

>次のように線を描画する前にUpdateを実行したところ、無事描画されました。
>  //キャンバスに描画しても、線が表示されない・・・(A)
>  MyPaintBox.Update;                                    ←この行追加
>  MyPaintBox.Canvas.LineTo(100,100);
>Updateに関する説明などでは「描画後に更新」のように読み取れますが、実際には、描画の前でアップデートするんですね・・。不思議な感じがします。

「キャンバスに描画しても、線が表示されない」のではなくて、描画した直後に消されているのです。
動的に作成したPaintBoxはまだ一度もUpdateされていないので、描かれた線は背景(Form)のWM_ERASEBKGNDメッセージによって消されてしまいます。
Updateしておいてから描画すれば、描画後にWM_ERASEBKGNDメッセージがFormへ送られないから、消えないだけです。
OnPaintイベントで描画しなければ、「何かのコントロールのRepaintが起きた時に消される」ハカナイ命です。


デル太  2006-03-18 09:47:03  No: 20566

Mr.XRAYさん、またまたありがとうございます。
ちょうど、教えていただいた内容についてトライしていました。
欲張って、次のアプローチをしていたのですが、

  ・MyPaintBoxをクラスにする
  ・コンストラクタをオーバーライドしてそこでサイズなど初期化する
  ・Paintをオーバーライドして、対角線を描く

Paintの中で描画した対角線が、他のウィンドウに消えた後、再描画されない現象に陥り、試行錯誤していたところです。
今回教えていただいたコードで試したら、ちゃんと再描画されますね!!
コードを拝見していると、「独立したクラスの中で全部こなさなくてもいいんだなー」と感じます。
よく分からないと、つい大きなステップでトライしてしまいますが、いきなりクラス構築しないで、こういう形で動作を確認するのは大切なのでしょうね。
大変参考になります。教えていただいたコードを元に、トライしてみますね。

私のレベルですと、procedureの定義とか、リストの実行に若干ですが戸惑ったところがありました。
多くの方には既知のこととは思いますが、せっかくですので、全体を提示させていただきますね。

---
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    PaintBox1: TPaintBox;
    procedure Button1Click(Sender: TObject);
  private
    procedure PaintBoxOnPaint(Sender: TObject);
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  MyPaintBox: TPaintBox;
begin
  MyPaintBox := TPaintBox.Create(Self);
  with MyPaintBox do begin
    Parent := Self;
    Visible:=True;
    OnPaint := PaintBoxOnPaint;
  end;
end;

procedure TForm1.PaintBoxOnPaint(Sender: TObject);
begin
    Canvas.Pen.Color:=clBlack;
    Canvas.Pen.Width:=5;
    Canvas.Pen.Style:=psSolid;
    Canvas.MoveTo(0,0);
    Canvas.LineTo(100,100);
end;

end.


デル太  2006-03-18 10:09:40  No: 20567

「花の命は」さん、わかりやすいご説明をありがとうございます。
描画されたけど、消えていたんですね。一瞬見えることがあるのは気のせいじゃなかったみたいです。

自分なりに試していたクラス版は、まだ暗礁に乗り上げています。
それどころか、基本的な部分で勘違いしているのかもしれません。

というのは、フォームに貼り付けたPaintBoxのOnPaintイベントにコードを書いても、ウィンドウに隠れた後で再描画されなくなってしまいました。
Mr.XRAYさんのコードは問題なく再描画されます・・。

一番シンプルな形でコードを作ってみました。
何かいけないのでしょうか?(
これだけシンプルなコードでも自力解決できないなんて情けないなぁ・・。
クラスなんて、夢のまた夢のように感じてきました。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    procedure PaintBox1Paint(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  //フォームが表示された時点で線が描画されるが
  //他のウィンドウに隠れた後は、消えてしまう
  PaintBox1.Update; //←現象はUpdateを入れても変わらない
  Canvas.LineTo(100,100);
end;

end.


やれやれ  2006-03-18 11:19:15  No: 20568

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  PaintBox1.Canvas.MoveTo(0,0);
  PaintBox1.Canvas.LineTo(100,100);
end;


デル太  2006-03-18 21:11:48  No: 20569

「やれやれ」さん、ご指導ありがとうございます!
しっかり描画&再表示されました。期待通りの振る舞いになりました。

  a)描画対象の指定
  b)描画方法の問題(初回と再描画時とで始点が異なる)

が複合していたことがわかりました。
思い込みの部分が多く、ひとりで悩んでいてもきっと気づかなかったと思います。
みなさんにご指摘&ご指導いただけたおかげです。
特に実際に動くコードで確認できるので、スムーズに検討できました。
ありがとうございます。

以下に、まとめておきます。

----------
  //「やれやれ」さんのコード
  PaintBox1.Canvas.MoveTo(0,0);     //PaintBoxに描画される
  PaintBox1.Canvas.LineTo(100,100); //再描画もOK

  //PaintBoxを指定しない場合の動作
  Canvas.MoveTo(0,0);               //Formに描画される
  Canvas.LineTo(100,100);           //再描画もOK

  //「デル太」のコードの問題点を整理:1)PaintBoxを指定していなかった
                           ↓下の1行は不要だった
  PaintBox1.Update; //←現象はUpdateを入れても変わらない
  Canvas.MoveTo(0,0); //3)これがないと再描画される際に100,100にから描画される
  Canvas.LineTo(100,100); //  だから移動量がないので*表示されない*
                          //今回の根本的な問題は3)だった
----------

それでも少し疑問が残ってしまいました。お付き合いいただけますか?

今回のケースで対象を省略した場合、フォームが対象になることがわかりました
PaintBoxで発生したイベントなので、デフォルトでPaintBoxが対象になると勝手な想像をしていましたが、ここが間違いなんですね?

疑問1:
  対象を省略した場合、すべてフォームになるのでしょうか?

疑問2:
  もしかすると、イベントが発生した際に実行するコードというのは、イベントが起きたオブジェクトとは無関係(オブジェクトの外部にある)と考えるべきなのですか?

そういえば「複数のオブジェクトでイベントを共有する」こともできると聞きます。
違う種類のオブジェクトでの共有ではデフォルトなんて考えにくいですものね・・。


は?  2006-03-18 22:16:11  No: 20570

> 疑問1:
>  対象を省略した場合、すべてフォームになるのでしょうか?
省略した場合、Self.の扱いになる。
すなわち、そのメソッドを記述しているクラスが対象。

procedure TForm1.HogeHoge(
          ^^^^^^^この部分が、Selfとして扱われる。

> 疑問2:
>  もしかすると、イベントが発生した際に実行するコードというのは、
> イベントが起きたオブジェクトとは無関係(オブジェクトの外部にある)と考えるべきなのですか?
質問の意味不明。

Sender でHelp みたらわかるんじゃね?

> そういえば「複数のオブジェクトでイベントを共有する」こともできると聞きます。
> 違う種類のオブジェクトでの共有ではデフォルトなんて考えにくいですものね・・。

Button1.OnClick := Button1Click;
Button2.OnClick := Button1Click;
Button3.OnClick := Button1Click;
Button4.OnClick := Button1Click;
Button5.OnClick := Button1Click;

イベントの共有。

procedure TForm1.Button1Click(Sender:TObject);
begin
  if Sender = Button1 then begin

  end
  else if 〜
end;

なんか、感じ取れるか?


デル太  2006-03-19 09:05:16  No: 20571

「は?」さん、ありがとうございます。
すごくよく分かりました。

(1)Selfの扱いについて

> > 疑問1:
> >  対象を省略した場合、すべてフォームになるのでしょうか?
> 省略した場合、Self.の扱いになる。

この説明は、なんとなく目にしていましたが、

> すなわち、そのメソッドを記述しているクラスが対象。

この部分まで把握していませんでした。

> procedure TForm1.HogeHoge(
>           ^^^^^^^この部分が、Selfとして扱われる。

この具体例で、ばっちり分かりました。大変分かりやすい例示をありがとうございます。

念のためヘルプでSelfを調べてみましたが、TCollectionItemの例なのと、クラスメソッドに該当するか判断が必要だったりで、判断しきれませんでした。
「は?」さんの例は今回のポイントをストレートに解釈でき、本当に嬉しく拝見しました。

(2)イベントの共有について

> Button1.OnClick := Button1Click;
> Button2.OnClick := Button1Click;
> Button3.OnClick := Button1Click;
> Button4.OnClick := Button1Click;
> Button5.OnClick := Button1Click;

> イベントの共有。

> procedure TForm1.Button1Click(Sender:TObject);
> begin
>   if Sender = Button1 then begin

>   end
>   else if 〜
> end;


> なんか、感じ取れるか?

はい、こちらもありがとうございます!
このように使えることから、イベント発生時に実行されるコードを「割り当てる」感じがわかりました。
イベントハンドラで「実行したいコードが書かれているプロシージャを指定する」ことになりますね。
プロシージャですから、それがクラスの内部にあるか外部にあるかは関係ない・・・のだと理解しました。

提示いただいたサンプルコードも参考に、ヘルプも見て、実際に動かしてみました。
ヘルプでは、AboutBoxの部分がエラーになったので、ボタンを押した際のメッセージ表示を共有しています。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if Sender = Button1 then        
    ShowMessage('Button1Click!')
  else
    ShowMessage('Not Button1!');
end;

end.

皆さんには既知と思いますが、このコードには共有指定が見当たらなかったので他のファイルを調べてみたところUnit1.dfmファイルに該当箇所(らしい部分)がありました。

-----
  object Button1: TButton
    Left = 96
    Top = 48
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click    //←この部分と
  end
  object Button2: TButton
    Left = 96
    Top = 96
    Width = 75
    Height = 25
    Caption = 'Button2'
    TabOrder = 1
    OnClick = Button1Click    //←この部分
  end
-----

(3)意味不明な質問の部分について

> > 疑問2:
> >  もしかすると、イベントが発生した際に実行するコードというのは、
> > イベントが起きたオブジェクトとは無関係(オブジェクトの外部にある)と考えるべきなのですか?
> 質問の意味不明。

確かになんのことだかわからない内容になってますね。
ご指摘ありがとうございます。

(1)と(2)で自分なりに整理できました。

再描画するには「Paintで処理すればいい」という点から、最初は「クラスのPaintメソッドに線を引くコードを追加する」と考えたのです。
それを「クラスを継承して元のPaintを呼び出し、線を描画する処理を追加する」と考えました。
メソッド内に記述するコードなのでなので「クラス内部にある」と表現しました。
(ここでかなり勘違いしていたことが、今は、わかりました)

ところが、イベントが発生した際のコードは、次のような場合があります

  a)フォームに貼り付けたボタンの場合
    procedure TForm1.Button1Click(Sender: TObject);   //これは自動的に生成されます

  b)生成したボタンにイベントを実装する場合
    procedure TForm1.MyButtonClick(Sender: TObject);  //これは自分で記述しました

a)はフォームに貼り付けたボタンに属している(内部にある)と解釈しましたが、

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject); //この部分の実装内容なのでTForm1の内部と考えた
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

b)は自分でコードを書きましたし、MyButtonというクラスも作っていませんから、特定のクラスに属していません。
これを「外部にある」と表現しました。

・・・と書いてみて、自分の誤りに気づきました。
上記コードを見直すと、Button1Clickも、TForm1に属していて、Button2には属していないですね。

この2例からすると「イベント発生時の実行コードはオブジェクトの外部に持つ」となりますね。

また、クラスに新たに独自のイベントを実装するのと、イベントが発生した際に実行するコードを記述するのは、全く違うというこも(たぶん)わかりました。

後者の「イベントが発生した際に実行するコード」を書く場所を「イベントハンドラ」と言うのですよね?

このあたり、私の理解も混乱し、用語の使い方も混乱していました。
ご指摘とご説明をいただいて、かなり整理できてきたと思います。
ありがとうございます。


Mr.XRAY  URL  2006-03-19 10:06:20  No: 20572

>ご指摘とご説明をいただいて、かなり整理できてきたと思います。
とはあまり思えませんが...
このような発言というか議論というのはあまり好きではないのですが,
(なら書くなってか!?)
最初の内は,用語よりも,体で感じた方がいいと思いますよ.

>  TForm1 = class(TForm)
>    Button1: TButton;
>    Button2: TButton;
>    procedure Button1Click(Sender: TObject); 
>  private
>    { Private 宣言 }
>  public
>    { Public 宣言 }
>  end;
  
の場合,Button1,Button2はTButtonというクラスのオブジェクトです,そして
これらはTForm1というクラスのオブジェクトです.
(TForm1=class(TForm)からend;まではTForm1というクラスの定義です)
(class クラスと書いてありますよね)

>MyButtonというクラスも作っていませんから、特定のクラスに属していません。
MyButtonのイベントは,MyButtonのイベントで,TForm1のクラスのものです.
(上の説明参照)

>Button1Clickも、TForm1に属していて、Button2には属していないですね。
Button2はTForm1のオブジェクトで,Button1ClickはButton1のイベントです.
「Button1ClickはButton1に属している」と言い方は変です.
また「オブジェクトの外部」という用語は多分意味不明です.
(ヘルプのどこかに説明があったのでしょうか?)
(内部にあるとか外部にあるとかも私は初耳です)

>「イベントハンドラ」と言うのですよね?
イベント(のコード)そのものを言います.場所ではありません.
(ん? 場所でもわかるかな〜?)

>独自のイベントを実装するのと、イベントが発生した際に実行するコードを記述するのは
Delphi言語が自動的にイベントハンドラを作成するか,自分で作成するかの
違いです.

あまり理解していない内に(自作?)用語を乱用すると,文章を読む人が混乱します.


sadoyama さどやま  URL  2006-03-20 19:08:00  No: 20573

Mr.XRAYさんには大変お世話になっているさどやまです。

> あまり理解していない内に(自作?)用語を乱用すると,文章を読む人が混乱します.
 確かにそうなのですが、気にせず萎縮せずに質問するのも上達上重要だと思います。

  幕末から明治維新にかけて、西洋の文化・科学技術を導入する上で先達ちになったのはオランダ医学者でした。その最初の仕事は外国語を日本語に翻訳し、日本人の感覚で理解できるようにすることでした。
  そうした門外漢の人たちから大村益次郎のような軍事専門化・天才も生まれました。「捧げ銃」「匍匐(ほふく)前進」などという翻訳も彼が行い、読み書きできない農民でもすぐに高度な戦術形態をとることができました。

  コンピュータの世界では、先進国アメリカの用語を機械的に翻訳して始まっていますので、用語の意味はもちろん(「例外」は今でもしっくりこない)、用語の存在自体を知ることが困難です。
  質問する場合、どういう表現、どういう用語を使えばよいか検討がつきません。
  検索で調べるにしても、適切なキーワードを思いつくには経験とある程度の知識の蓄積を必要とします。
  プログラミングに限らず、パソコンが解りにくい一番の原因だと思います。

  Delphi のヘルプを見ていても、ソースにあたらないと定義が正確にわからない用語も説明がないまま使われていたりします。
  
  何が言いたいかというと、
1.初心者は何でも質問すること
    たとえば私の場合、データベースは初心者以前のレベルです。
2.適切な用語とその意味を解りやすく紹介し整理するのは重要な基礎的作業ではないかと。
3.文系・総務畑の私のような全くの素人でもプログラミングの世界に入ってこれることがこれからは大切ではないかと。
    すべての事業所・家庭にプログラマの配置を。


デル太  2006-03-21 03:27:39  No: 20574

Mr.XRAY様、さどやま様、書き込みありがとうございます。

スレッドが長くなりましたので、ここで私なりに背景を整理します。

私の当初の課題は「再描画させるためPaintイベントを使って描画するには?」でした。
皆様の情報のおかげで、局所的に考えた場合、上記課題は既に解決しています。

でも、皆様のご指導のおかげで、ようやく「私が混乱している点」を自覚できるようになりました。(混乱している内容は後述します)
インターネットや書籍、ヘルプを調べたものの、このスレッドで聞いて具体的に教えていただくまで解決に至らなかった原因は、私の実力不足に加えて、この内容について混乱し整理して理解できていないため、調べた資料の説明を読み取れなかった点も一因と思っています。
そこで、みなさまには既知の内容とは思いながらも、この混乱を整理して把握できたときに「解決」ボタンを押そうと考えています。

その混乱もMr.XRAY様および皆様のご指摘およびコードのご提供で「体で感じての解決」がかなり進んでいます。
実際に動かして、コードを印刷して持ち歩き、ちょっとした時間に何度も読んで慣れながら理解しようとしています。
このアプローチは「これやり方でいいのだろうか?」という不安がありません。
既に正しく動いていますから、自分の理解が至らないだけで、正しい方向に進んでいることに迷いはありません。
本当にありがとうございます!

そして、ここで、もう一歩がんばって、頭で考えた解決まで踏み込んでおきたいのです。
そのように考えながら、発言を続けて参りました。
そんな中「さどやま様」の「初心者は何でも質問する」以下のメッセージをいただき、さらに勇気も湧いてまいりました。
「さどやま様」にご指摘いただいていると思われる「わからないので説明できないから他に人はわからない」というジレンマ、今、痛感しています。
それでも、そのわからない質問に適切な回答を下さる皆様の発言を、本当に嬉しく拝見しています。

長年大きな壁と痛感しているところに、風穴ぐらいは開けられそうで楽しみなのです。
どうぞ、ご了承ならびに引き続きのご支援をよろしくお願いいたします。
年度末で忙しくしている面があり、実行してテストして整理しながらの書き込みのため、間が空くこともございます。
その点も、どうぞお許しください。

それでは、次の発言で、私が混乱している点をご説明いたします。


デル太  2006-03-21 03:28:32  No: 20575

私が混乱している点を整理します。

「PaintBoxで再描画が必要な処理は、OnPaintに描画する」について、次のようなケースが考えられると思います。

(1)開発環境(IDE)を使いフォームにPaintBoxを配置した場合
(2)PaintBoxを動的に生成した場合で、OnPaintに他のオブジェクトのメソッドを割り当てる場合
(3)PaintBoxを動的に生成した場合で、OnPaintに独自に入力したメソッドを割り当てる場合
(4)OnPaintで実行するコードを持たせたクラスを定義し、それを動的に生成する

私はまだ、
  ・それぞれどのように実装するのか?
  ・それぞれはどのように違うのか?

がわかっていないのだと思います。同時に、この点を悩んでいるということから、

  ・どのように使い分けるのか?

もわかっていないことになりますね。

他にも色々と調べて頭でっかちになっているのですが、そのひとつとしてDelphiでは次も可能とわかり、この点も混乱に拍車をかけていると感じています。

(5)クラスに新しいイベントを実装する

それでも、今までのご指導により、(1)〜(4)は類似しているが、(5)は違うものということはわかりました。

なお、この発言をまとめるために、上記(1)〜(3)を実際に動作確認しました。
その結果、自分としてはかなり理解できてきました。
次の発言で紹介いたします。


デル太  2006-03-21 03:30:02  No: 20576

直前の発言での(1)〜(3)を動作確認した内容を紹介します。
既知の方は多いと思いますが、参考になる方も中にはいらっしゃると思いますので、Unit1.pas全体を紹介します。

-----
//Paintで再描画するコードの実装方法を試しました
//
//実装例1:IDEで配置したPaintBoxを対象にIDEの機能でOnPaintのコードを入力
//実装例2:動的に生成したPaintBoxのPaintイベントに実装例1で生成したPaintBoxの
//          OnPaintを割り当てた
//実装例3:動的に生成したPaintBoxのPaintイベントに独自に入力したMyOnPaintを
//          割り当てた
//
//実行時はフォームにPaintBoxを1個、Buttonを2個配置しています

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);

    //イベントが発生した際に割り当てるコードをユーザが独自に実装する場合
    //この部分を自分で入力する
    //プロシージャの引数は、発生するイベントに合わせておく必要がある
    //この位置(TForm1の中)に書くべきか、他(TForm1の外)がいいのかは、
    //まだよく理解できていないので、今後の課題とする
    procedure MyOnPaint(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ここから下はPaintイベントで実行するコードを入力しています
 PaintBox内のコードを共有する場合と、独自コードを利用する場合があります}

//IDEで配置したPaintBoxのPaintメソッドで円を表示する
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  //ここで描画した円は最初から表示されています
  {
  //実装例1:一番シンプルな実装
  //Paintメソッド(イベント?)にコードを書くと必要に応じて再描画される
  //配置したPaintBox1だけに描画する場合はこのコードを実行してみよう
  //その際PaintBox1は円が表示されるがAPaintBoxを生成しても円は表示されなくなる
  with PaintBox1.Canvas do
  begin
    Ellipse(0,0,100, 100);   //隣接四角形(0,0)-(100,100)に内接する円を描く
  end;
  }
  //実装例2:複数のオブジェクトで共有できる実装例〜その1
  //「実装例1」を元にイベントが発生したオブジェクト(Sender)に描画するように修正した
  //SenderのままだとCanvasが使えないのでTPaintBoxに型キャストして利用している
  //この形なら動的に生成したPaintBoxから、このメソッドを共有できる
  //具体的な動作としては、APaintBoxを生成した段階でCanvasに円が表示される
  with TPaintBox(Sender).Canvas do
  begin
    Ellipse(0,0,100, 100);   //隣接四角形(0,0)-(100,100)に内接する円を描く
  end;
end;

//TPaintBox型のオブジェクト用:キャンバスに円を表示するだけのコード
procedure TForm1.MyOnPaint(Sender: TObject);
begin
  //実装例3:複数のオブジェクトで共有できる実装例〜その2
  //ユーザが独自に追加したメソッドをイベントに割り当てる形で実装している
  //以下の実行コードは実装例2と同じ命令で、描画サイズを変えています
  with TPaintBox(Sender).Canvas do
  begin
    Ellipse(0,0,50, 50);   //隣接四角形(0,0)-(50,50)に内接する円を描く
  end;
end;

{ここから下が動作テスト部分です}

//ボタンをクリックするとPaintBoxを動的に生成する
procedure TForm1.Button1Click(Sender: TObject);
var
  APaintBox: TPaintBox;
begin
  //PaintBoxを動的に生成する。引数(型はTComponent)には今回はSelfを指定した
  //この引数を省略するとSelfが指定されたことになる
  //Selfは自分自身が呼び出されたインスタンスオブジェクトTForm1を渡す←ヘルプで確認した
  //今回はForm1に配置したので、Form1が渡され、TPaintBoxのOwnerとしてセットされる
  APaintBox := TPaintBox.Create(Self);
  //動的に生成したオブジェクトはParentプロパティをセットすると画面に表示される
  APaintBox.Parent := Form1;
  //生成したAPaintBoxのPaintイベントに再描画のコードを割り当てる
  //ここではIDEで配置したオブジェクトのコードを割り当てている
  //割当先が実装例1のままだと、配置したオブジェクト(PaintBox1)に描画するだけで
  //使えないので実装例2のようにSenderオブジェクトを利用するよう修正した
  APaintBox.OnPaint := PaintBox1Paint;
end;

//ボタンをクリックするとPaintBoxを動的に生成する(その2)
//下記を除きButton1Clickと同じです。
//  ・Topを指定している
//  ・MyOnPaintを割り当てている
procedure TForm1.Button2Click(Sender: TObject);
var
  APaintBox: TPaintBox;
begin
  APaintBox := TPaintBox.Create(Self);
  APaintBox.Top := 170; //別の位置に表示するために指定している
  APaintBox.Parent := Form1;
  //生成したAPaintBoxのPaintイベントに再描画のコードを割り当てる
  //ここでは自分で記述したメソッドを割り当てている
  APaintBox.OnPaint := MyOnPaint;
end;

end.
-----

できれば、この発展で直前の発言の(4)まではトライしたいと思っています。
(4)に関しては、別のスレッドで、やはりMr.XRAY様や「は?」様から大変参考になりそうなコメントをいただいています。
まだ時間を取れずにいるのですが、それも参考に進めていくつもりです。

□スレッド:背景が透明な線を描画するには、どのようなアプローチが良いか?
https://www.petitmonte.com/bbs/answers?question_id=3775


デル太  2006-03-21 03:35:23  No: 20577

ここで、内部と外部について補足させてください

Mr.XRAY様にご指摘いただいた「外部」と「内部」は私が勝手に使っている用語です。
わかりにくくする原因となっていること、お詫びさせてください。

3月19日にMr.XRAY様にご説明いただいた内容、大変参考になりそうです。
このあと、別の発言で回答(質問)させてください。
その前に、私の用語の使い方を説明させていただきますね。

私なりに「クラス」を考えて、次のイメージで理解しています。

-----
クラスA{
  フィールドF1        ←クラスAの内部と表現
  メソッドM1宣言      ←クラスAの内部と表現
}

メソッドM1実装部{     ←クラスAの内部にある、と考えています
}
-----

問題になった点は、イベントの割り当てです。
1つ前の発言で紹介したコードの中で、動的に生成したオブジェクトについて、次のコードでイベントを割り当てています。

  APaintBox.OnPaint := PaintBox1Paint;
  APaintBox.OnPaint := MyOnPaint;

この点は、まだ「イベントはプロパティの一種との説明があったので代入できるのだろう」程度の理解ですが。

そして割り当てた結果のオブジェクトは、私のイメージでは次のように把握しています。

APaintBox{
  OnPaint→PaintBox1Paint  :→は呼び出すという意味
}

APaintBox{
  OnPaint→MyOnPaint
}

このうち、PaintBox1Paintは「PaintBox1の内部にありそれが呼び出される」と考えています。
また、MyOnPaintは「TForm1の内部にありそれが呼び出される」と考えています。

いずれも生成したオブジェクト(インスタンス?)の内部ではないので、「外部にある」と表現しました。
自分なりに分かりやすい把握方法を模索しながら勉強しているため、専門的に見ると誤りがあるのかもしれません。

さどやま様にご指摘いただいたように

>2.適切な用語とその意味を解りやすく紹介し整理するのは重要な基礎的作業ではないかと。

自分としては、分かりやすく整理している部分なのですが、わかりやすさを優先するため専門的な「正確さ」を犠牲にしている点は否めません。

(以下、余談が含まれていますが、お許しください)

それでも、大村益次郎の例は非常に勇気付けられました。ひとつの目標にできそうです。
そうですね。大村益次郎は元々医学者でしたよね?
今、思い出しましたが、司馬遼太郎の「花神」もNHKの大河ドラマも大好きでした。懐かしいなー。
少し前では「学際領域」や「ゼネラリスト」という表現、そして今は小学校で導入され(失敗の烙印を押されてしまいそうな?)「総合的な学習」などが彼のような人材を求め育てる動きなのでしょうね。

>3.文系・総務畑の私のような全くの素人でもプログラミングの世界に入ってこれることがこれからは大切ではないかと。

プロフィールを拝見し、全くの素人というのには意義を申し立てますが(笑)、私もこの点、同感で非常に強く感じています。
業務の知恵はスタッフ(ユーザ)にあり、技術のノウハウは開発者が持っています。そのふたつが融合するのが一番ですよね?

少なくともこのスレッドでは、私の初歩的な混乱に対してMr.XRAY様のような開発系の方とさどやま様のようなスタッフの方の助言をいただくことができています。
それはとても心強く有難い経験です。ありがとうございます。


デル太  2006-03-21 03:36:12  No: 20578

蛇足ながら、私同様入門レベルの方には混乱を招くかも?と気づき以下を補足します。

上記2例では、同じAPaintBoxという名前のオブジェクト(インスタンス)になっていますが、システム上は別物として扱われているようです。
これは動作させると、別々に表示されるのでわかります。
この点は、私なりに調べ、次のように理解しています。

  ・動的に生成したオブジェクトはCreate(AOwner)の引数(AOwner=今回はForm1)が保持する
    →だからButton1Clickのローカル変数なのに処理を抜けてもメモリから消えずに保持され続ける
  ・AOwnerが破棄される際に保持しているオブジェクトを破棄するからユーザは廃棄しなくて良い
  ・生成時の処理の中でAPaintBox.Name := 'xxx'; と設定しておけば、その後、その名前で使える(と書籍「Delphi3 Q&A 150選」で読みました)
  ・名前を付けない場合、フォームが保持するコントロールはForm1.Controls[0]のように利用することができる

誤りがございましたら、ご指摘いただけると幸いです。


デル太  2006-03-21 05:23:35  No: 20579

Mr.XRAY様、3月19日の発言に回答させていただきますね。

あれこれ悩んでしまって、ぜんぜん、まとまってないんです。
お詫びします。
それでも、最後に少し糸口が見えてきました。
そのまま発言いたしますが、皆様、どうぞ、お付き合いください。

> 最初の内は,用語よりも,体で感じた方がいいと思いますよ.

ご指摘ありがとうございます。
言葉から入って、実行が遅くなる傾向がありますので、気をつけるようにしますね。

> >  TForm1 = class(TForm)
> >    Button1: TButton;
> >    Button2: TButton;
> >    procedure Button1Click(Sender: TObject); 
> >  private
> >    { Private 宣言 }
> >  public
> >    { Public 宣言 }
> >  end;

まず私の理解をそのまま書いてみますね。
あたっているでしょうか?

一番大きいのがTForm1というクラス。これは、TFormを継承している。
TForm1はクラスだから、そのままでは使えない。どこかで変数を宣言しているはず。
TForm1というクラスにはButton1とButton2という2つのメンバがある。

Button1とButton2はTButton型として宣言されている。
どこで生成するのだろう・・?と気になる。(が目をつぶって使う)

TForm1というクラスにはButton1Clickというメソッドがある。
Button1ClickというメソッドはSenderという名前のTObject型の引数を要求する
Button1Clickはprocedureなので、戻り値はない

この次の行からprivateや{Private宣言}とあるが、ここまでの3行は何になるのだろう??

> の場合,Button1,Button2はTButtonというクラスのオブジェクトです

これは理解できているみたいです。
実装部で次のように使う場合の延長で解釈して問題なさそうですね。

var 
  AStringList: TStringList;

問題は、どこで生成しているのか???という点がわかっていないみたいです。
自作クラスの内部に(すいません、内部という表現をつかいますね)別のクラスを持たせる場合、自作クラスのコンストラクタ(Createメソッド←イベントかもしれません)で生成し、デストラクタでFreeする例を見かけたことがあるので、クラスのメンバとして宣言(用語としては「定義」が正解??)したオブジェクトは生成不要ということはなさそうな気がします・・。

>そしてこれらはTForm1というクラスのオブジェクトです.

この文面だけみると次のようなコードをイメージしてしまいます。

  Form1: TForm1;  //これらはTForm1というクラスのオブジェクトです
  Form2: TForm1;  //

TForm1というクラスのオブジェクト、という表現は、TForm1というクラスのメンバとなるオブジェクト、と解釈すればよいでしょうか?

上記でも「内部」という表現を使って逃げてしまったように、私は、この部分をキチンと理解できていないみたいです。

> >MyButtonというクラスも作っていませんから、特定のクラスに属していません。
> MyButtonのイベントは,MyButtonのイベントで,TForm1のクラスのものです.

これは私の次の発言を対象にして説明していただいています。

>   b)生成したボタンにイベントを実装する場合
>     procedure TForm1.MyButtonClick(Sender: TObject);  //これは自分で記述しました

最初に実際のコードで書かなかったことをお詫びします。
以下にMyButton1Clickの実装部を追加してみました。この形で進めていいでしょうか?

-----
> >  TForm1 = class(TForm)
> >    MyButton1: TMyButton;
> >    procedure MyButton1Click(Sender: TObject); 
> >  private
> >    { Private 宣言 }
> >  public
> >    { Public 宣言 }
> >  end;

procedure MyButton1Click(Sender: TObject);
begin

end;
-----

この場合、

  a)MyButtonのイベントは
  b)MyButtonのイベントで
  c)TForm1のクラスのものです

a)は、次の行を指す表現になりますか?

> >    procedure MyButton1Click(Sender: TObject); 

b)は「MyButton型のイベントで」と解釈してよいでしょうか?
やはり、私は、b)の表現を、ちゃんと理解できずにいるみたいです。
なので、この時点では、この点を整理して質問することが難しい。どうぞご了承ください。

たぶん、私が一番わからないでいるのは「MyButton1」の「イベント」という表現なんです。
MyButton1Clickを見ていて、そのイベントというと「Click」のことなのかな?と思ってしまうんですよ。

  d-1)MyButtonクラスはTButtonを継承しているのでClickというイベントを内部に(「内部」を使いますね)持っている
  d-2)そのオブジェクトMyButton1はClickイベントを持つ
  d-3)MyButton1でClickイベントが発生した場合にコードを実装する場合、IDEのオブジェクトインスペクタが使えるならIDEがMyButton1Clickというプロシージャを自動生成する
  d-4)同時にMyButton1Clickというプロシージャの実行部分が自動で開かれるのでそこにコードを書く

  ※ちなみに上記d-3とd-4はDelphiのIDEがコードを自動生成するルール(言語でなくIDEの仕様)なのかな?と思っています。

動的に生成した場合、オブジェクトインスペクタが使えないので、次のようになりますね

  d-3')オブジェクトインスペクタが使えないので、procedure TForm1.MyButtonClick(Sender: TObject);を書く
  d-4')クリックイベントで実行するコードを次のように割り当てる MyButton1.OnClick  := TForm1.MyButtonClick;

このように考えてしまうんですね・・。
一応、IDEの場合と動的生成の場合で、矛盾はしていないように思うのですが、しっかり把握できていないと感じています。

具体的には、

  procedure MyButton1Click(Sender: TObject); はイベントではないのかもしれない。

という気持ちが出てきてしまい、しっくりいかないんです。
Paintを使って再描画できるけど、再描画のコードはPaintBoxPaint内にない。
そこが納得できないから、というのもあります。

他にも、「イベント」とは別に「イベントハンドラ」という用語があるのではないか?と思ってしまうんです。

  ・イベントを実装する
  ・イベントハンドラにコードを記述する

というような使い分けがあるような気がしているんです。

Windowsでは「ウィンドウハンドル」というのにここで「ハンドラ」というのには何か意味がありそうだ・・・とか・・。

これって、さどやま様がおっしゃる「先進国アメリカの用語を機械的に翻訳して始まっていますので」にあたる部分なんですよね?きっと。
特にイベント、ハンドラ、ハンドルは翻訳さえもしていないですし。
ネイティブスピーカーなら理解できるのでしょうか・・???

まとめられなくて、ごめんなさい。そのまま進みますね。

ここから先の部分は一番目のコードの課題ですので、誤解をさけるため、もう一度引用しておきますね。

> >  TForm1 = class(TForm)
> >    Button1: TButton;
> >    Button2: TButton;
> >    procedure Button1Click(Sender: TObject); 
> >  private
> >    { Private 宣言 }
> >  public
> >    { Public 宣言 }

> >Button1Clickも、TForm1に属していて、Button2には属していないですね。
> Button2はTForm1のオブジェクトで,Button1ClickはButton1のイベントです.

はい、Button2はTForm1に配置されたオブジェクトですね。納得できます。
「TForm1のオブジェクト」という表現は、「Form1はTForm1型のオブジェクト」という文意での「TForm1のオブジェクト」という同じ表現ですが、「TForm1のメンバオブジェクト」と受け取ればいいですよね?

> 「Button1ClickはButton1に属している」と言い方は変です.

すいません。
Form1にButton1とButton2を置いた場合が上記のコードになりますよね。
そのとき、Button1とForm1の上下関係をどう表現したらよいのかわからなくて「属する」と表現していました。
Button1がForm1に「属する」と表現して適当な感じがしたので、procedure Button1ClickもForm1に「属する」と表現したんです。

このようなオブジェクトの上下関係をどのように表現するのがよいでしょうか?
クラスの上下関係というのもありそうで、混乱しそうですね。まずは、オブジェクトの上下関係(含有関係)が気になっています。

> また「オブジェクトの外部」という用語は多分意味不明です.
> (ヘルプのどこかに説明があったのでしょうか?)
> (内部にあるとか外部にあるとかも私は初耳です)

すいません。この点もお詫びいたします。

Form1にPanel1とPanel2を置いたとします。
Panel1にButton1を、Punel2にButton2を置きます。
教えていただいたので、Button2のClickイベントにButton1のClickイベントを割り当てることができるようになりました。
この場合、Button1のClickイベントはButton2の外部にある・・、とか、そういう関係を表現したいのですが、どのように表現するとよいでしょうか・・?
そもそも、オブジェクトの内部とか外部とか意識しない方がよいのでしょうか?

> あまり理解していない内に(自作?)用語を乱用すると,文章を読む人が混乱します.

ごめんなさい。
もう少し理解してから、もう一度整理してみますね。
今回の発言は、ぜんぜんまとまらずに申し訳ありません。

それでも、疑問を文章にする中で、コード上の表現の差には厳密に意味があるのかも?と感じ始めました。
次のような感じです。

A)クリック時に実行するコードを記述する場所:用語は「イベントハンドラ」?
  Button1Click
  begin
    xxxx;
  end;

B)クリックイベントの割り当て:イベントは実はプロパティなので代入している?

  OnClick := xxx;

C)イベント(実装場所がどこかにある)
  OnClick
  begin
    xxxx;
  end;

ますます混乱しているのですが、極端ながら直接的な例を思いついたのでコードを実行してみました。

フォームにButton1とButton2を置き、Button1のClickに適当な処理を記述します。
Button2のClickにはオブジェクトインスペクタでButton1Clickを割り当てます。
これで、Button2のClickイベントでButton1Clickプロシージャが実行されますね。

-----
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  showmessage('aaa');
end;

end.
-----

この中には、

  Procedure Button2Click(Sender: TObject);

がないですね。
これがあって、その中で

  Button1Click(TButton(Sender));    ・・・コードA

とでもなっていると、なんとなく納得できるのですが、実際は違うんですね・・・。

ちなみに、上記コードAで期待通りに動作しました。
-----
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject); //Button2をダブルクリックしてコードを入力したら生成された
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  showmessage('aaa');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Button1Click(TButton(TObject)); //・・・コードAの実装
end;

end.
-----

コードAの実装部分は

  Button1Click;

だけや

  Button1Click(Sender);

ではエラーが起きるのでは?と感じていた予想通り、コードAにしないとNGでした。
このことから、なんとなく、方向性は当たっているように思います。

やはり「dfmファイル」の中の次のコードが理解できれば・・、という感じがします。
---
  object Button2: TButton
    Left = 0
    Top = 288
    Width = 185
    Height = 25
    Caption = #...
    TabOrder = 1
    OnClick = Button2Click //このコードが理解できれば・・?
  end
---

どうぞ、お力添えをよろしくお願いします。


デル太  2006-03-21 05:44:42  No: 20580

直前の私の発言を読み返してわかりにくいので補足いたします。

> 具体的には、

>   procedure MyButton1Click(Sender: TObject); はイベントではないのかもしれない。

> という気持ちが出てきてしまい、しっくりいかないんです。
> Paintを使って再描画できるけど、再描画のコードはPaintBoxPaint内にない。
> そこが納得できないから、というのもあります。

この中で次の部分を読み替えてください。

> Paintを使って再描画できるけど、再描画のコードはPaintBoxPaint内にない。

  ↓

例えば、TLabelを動的に生成して、ALabelPaintプロシージャ内に描画コードを書けば再描画できる。
でも、Captionを再描画するコードはPaintBoxPaint内にない。
それでもCaptionは再描画される。


Mr.XRAY  URL  2006-03-21 09:22:30  No: 20581

>でも、Captionを再描画するコードはPaintBoxPaint内にない。
>それでもCaptionは再描画される。

Delphi言語というのは,クラスという単位でその言語体系を構築しています.
例えば,TLableの派生元はTCustomLableです,

TLabel <--- TCustomLabel 
このTCustomLabelの中に
procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
というメッセージ処理があります.その中身は以下の様になっています.
この中でCaptionの再描画を行っているわけです.

procedure TCustomLabel.CMTextChanged(var Message: TMessage);
begin
  Invalidate;  
  AdjustBounds;
end;

Windowsのというのはメッセージの嵐です,その中のいくつを捕らえて
利用しやすいようにしたのがイベントです.オブジェクト・インスペク
タに表示されているそれです.他のWindowsのメッセージは作成しなけ
れば捕らえることができません.上に示したCaptionを表示するための
メッセージの処理は,TCustomLableで行われています.
つまり,TLableのCaptionはDelphiの利用者が意識しなくても再描画
(メッセージ)処理をしてくれます.

この例の様に,Delphiの動作を詳しく知るには,コンポーネント(クラス
)のソースコードを追うことで理解することができます.

もっと理屈を言えば,コンポーネントのTextまたはCaptionが変更されると
Delphiは自分自身にCM_TEXTCHANGEDというメッセージを送ります.
(その様にDelphiが作られています.中も外も関係ありませんし,考える必
要もありません.その様にDelphiができているのです)
このメッセージの処理がCMTextChangedです.

[参考]
http://homepage2.nifty.com/Mr_XRAY/Halbow/Chap00.html#sanka

>でも、Captionを再描画するコードはPaintBoxPaint内にない。
>それでもCaptionは再描画される。
どうしても,この疑問を納得するまで解決したいのであれば,Halbowさん
の解説のコードを自分で打ち込んで実行してみることをお勧めします.
まずは,理屈よりも実践です.理屈は後からついてきます.
神武天皇も明治天皇も関係ありません!!
文法から入って勉強する某国の英語教育の真似をする必要はありません.


ちらつき防止隊員  2006-03-21 09:31:44  No: 20582

>文法から入って勉強する某国の英語教育の真似をする必要はありません.
日本ですね。
そして日本人です。Classというのはわかりにくいですね。
メッセージ一覧なんて便利なものないっすかねぇ。日本語で。解説つきで。
PS.Mr.XRAYさんのHPにてレジストリからPDFを読みこむ方法などかなり参考にさせていただきました。ありがとうございます。


さどやま  URL  2006-03-21 10:42:45  No: 20583

1.TLabel.Captionが変更されると、
    自分でCM_TEXTCHANGEDというメッセージを発し、(TControl.SetTextBuf)
    それを改めて受けて自身を再描画する。(XRayさんが示したTCustomLabel.CMTextChangedで行う)

    こういう面倒な手続きはCaptionに限ったことで、理由は定かでありません。
    「その様にDelphiが作られています.」としか私も言いようがありません。
    ソースを見て初めて解ることですが、ソースを見たからといって解るというものでもありません。
    習わないと解らないことです。

2.再描画はどうやって行うかというと、TCustomLabel.CMTextChangedがInvalidate を実行します。
    これは,Windowsに再描画を依頼する行為です。暇ができると
    親コントロールに再描画を命令してくれます。
    この間にもその伝達のためのメッセージがたくさん飛び交いますが省略します。

    それ以後はデル太さんも少し解りかけてきたと思います。
3.実際の描画は、paintがあればそれを実行します。
    そのpaintはTCustomLabelにあります。

4.具体的作業はTCustomLabel内のDoDrawTextが行います。
    
5.DoDrawTextはTCustomLabelのヘルプにも書かれているGetLabelTextでCaptionの文字列を得、Canvas.DrawTextを使って描画します。

    従って、我々がそのコードをいちいち書く必要はどこにもありません。

  ただし、起動時にLabelが初めてCreateされて以後Caption property が設計者により変更されるまでのCaptionの描画はもう少し手がかかっています。(Name property をコピーする)


デル太  2006-03-21 11:14:58  No: 20584

> この例の様に,Delphiの動作を詳しく知るには,コンポーネント(クラス
> )のソースコードを追うことで理解することができます.

ご紹介いただいて無性にソースコードを追いたくなりました。
宣言部でCTRL+クリックで実装部分を見られることもしらなかったんです。
なんとかWEBで見つけることができ、TCustomLabelのAdjustBoundsについて、コードを参照できました。

ソースコードが20行前後で、何をやっているかも大体はわかる。
Delphiってすごいですねー!改めて感じました。
かなり、心強くなりました。
これからは、ソースコードを積極的に見たいと思います。

□IDEの格言:操作方法をここで見つけました
http://kakinotane.s7.xrea.com/delphi/faq/f003.html

> (その様にDelphiが作られています.中も外も関係ありませんし,考える必
> 要もありません.その様にDelphiができているのです)

ありがとうございます。でも、ごめんなさい。
もし、考える必要がないとしたら、私は理解できなくて使うことができない場合が多いんです。
考えることは多めに見てください。その結果分からなくて覚えることになっても文句は言いませんので。

考えずに覚える「公式」「英単語」「漢字」など大変苦手なんです。
「手順」も理由がないとなかなか覚えられなくて・・・。
例えば2次方程式の解の公式「y=-2aぶんのなんとか」というやつ、あれを単に覚えることができなくてテストのたびに毎回導いてから使ってました。
さすがに理科の法則をその場で導くのは無理でしたけど、数学はそのやり方でも結構成績は良かったんですよ。(笑)

そういう「気質」の人もいるということで、どうぞ、許してくださいね。
どうぞ、これからもご指導ならびにお付き合いのほど、よろしくお願いします。

> どうしても,この疑問を納得するまで解決したいのであれば,Halbowさん
> の解説のコードを自分で打ち込んで実行してみることをお勧めします.
> まずは,理屈よりも実践です.理屈は後からついてきます.

はい、ソースコードの見つけ方がわかったので、徐々にトライができそうです。
理屈より実践、それがとても重要だなって、今回の調査や実験ですごく感じましたのでよく分かります。
そして、実践した後には自分の理解を整理して理屈で考えられるようにするつもりでいます。

>神武天皇も明治天皇も関係ありません!!

私が解の公式や、確立の計算式をテストの際に自分で導いていたのは、主に高校時代でした。
そのころはクラス(教室)に天皇陛下を崇拝する友達と、毛沢東氏の崇拝者がいたりして、一緒に勉強してました。
彼らに、それぞれの得意分野を聞くのがとても楽しかったことを思い出しました。
主義や思想は違っていても、真剣に学んだ人の話は大変参考になりますものね。
ここの掲示板は良く拝見していましたが、今回初めて質問させていただいて、それに似た「楽しさ」を感じています。
真剣に学んだ方々のアドバイスは、大変参考になり、そして楽しいものですね。

> >文法から入って勉強する某国の英語教育の真似をする必要はありません.
> 日本ですね。

最近、英語漬け(DS)をほぼ毎日楽しくやっています。
文法主体でなくて、楽しいですね。
でも、つづりなど、昔、丸暗記したときのストックがベースにになっていると感じることが多いですね。
単に覚えるのは苦手と言いながら、かなり助けられていることがわかります。
理屈と実践のバランスも大切なんでしょうね。

> Classというのはわかりにくいですね。

えぇ、概念としてわからないと、頭の中でシミュレーションができませんものね。
Classっていうのは、日本語だと普通は「教室」ですものね。
「クラス」という表現を使うなら、「教室」を題材に説明できるとよさそうです。

  ・教室にメンバがいる    ←わたしはこっちの方が聞く気になりますね。
  ・クラスにメンバがある

このあたりについては、さどやまさんのご指摘が的を得ていると感じます。

>「捧げ銃」「匍匐(ほふく)前進」などという翻訳も彼が行い、読み書きできない農民でもすぐに高度な戦術形態をとることができました。

もっと「その最初の仕事は外国語を日本語に翻訳し、日本人の感覚で理解できるようにすることでした。」のように「日本人の感覚で理解できる」ための努力を重ねたいと感じます。

概念を実物に近い「もの」のように扱って考えられると、若年者でもわかりやすいし、年をとっても忘れにくい。
そのためにも、日本語でわかりやすい「名前」を付けたり考え方を説明するのは大切だと思います。
その際、むずかしいけど単に日本語でなく、昔から日本にある言葉が一番いいですね。

経験的には「理解する」よりも「わかる」の方が伝わりやすいみたいです。
日本古来の「ことば」は大切にしたいですね。(「言葉」と書くよりわかりやすいみたいです)

簡潔&厳密な表現は英語が適しているのは否めないので、日本語でプログラミングするっていうのは一概に賛成しかねますが、わかりやすい資料はほしいですよねー。

> メッセージ一覧なんて便利なものないっすかねぇ。日本語で。解説つきで。

最近「Win32APIオフィシャルリファレンス」を購入しましたが、こういうものをイメージなさっているのかな?
書籍に関しては、だんだん良書がなくなってきているみたいですね・・。

□Win32APIオフィシャルリファレンス
http://www.amazon.co.jp/exec/obidos/ASIN/4756137490


sadoyama さどやま  URL  2006-03-21 11:20:59  No: 20585

> Classというのはわかりにくいですね。
  同感です。
  ただし、どう解りにくいかで一致しているかどうかは解りませんが。
  「一族」「先祖」?生物学的には「類」?

> メッセージ一覧なんて便利なものないっすかねぇ。日本語で。解説つきで。
  Windows に関しては無いわけではないのですが。
  「Windows95 API バイブル2  メッセージ編」
  文字は日本文字を使って説明されていますが、文章としては日本語と言えるかどうか。内容的にも無いよりはマシというところか。

  Delphi 独自のメッセージにいたっては・・・・
  中村拓男氏著「Delphi コンポーネント設計&開発  完全解説」あたりか。
  

追伸
    
  別のスレッドだったかどうか、
  OnClick = MyClic が解らない、という個所があったが。

  property はメソッドポインタ(アドレス)だということです。
  OnClick := MyClick;
の後でdfmを開けば
  OnClick = MyClick になります。

  メソッドポインタの説明が更に必要なら検索してみてください。
  クラス := Create() も同様で、
  インスタンスをメモリ上に作り、最後にそのアドレスを代入します。


デル太  2006-03-21 11:33:41  No: 20586

あ!さどやまさん、ご説明いただいていたんですね。ありがとうございます。
気づかずに先の回答をしていました。お詫びいたします。

う、さすがに難しい・・。ちょっと二の足を踏みますね。(苦笑)

>     従って、我々がそのコードをいちいち書く必要はどこにもありません。

はい、この部分がオブジェクト指向のライブラリが提供されているメリットですね。

Mr.XRAY様、さどやま様、もうひとつご教授いただいてもよろしいでしょうか?
TCustomLabelのソースを見て、話題の中心となっているCaptionを見つけました。
でも、すっきりしない点があるんです。

   property Caption;

read も writeもないので、Captionにアクセスしたときの振る舞いが思い浮かばないのです。

ヘルプを参照すると次のように書かれています

>property propertyName[indexes]: type index integerConstant specifiers;
>specifiers には,read,write,stored,default(または nodefault),implements の各指定子を列挙します。プロパティ宣言では,最低限 read か write のいずれかのアクセス指定子を指定しなければなりません。

最低限必要な指定子がない・・。
この情報から先、どうやるとお二方のような情報にたどり着けるのでしょう???

きっと、コードを書かないかわりに、覚える(教わる?学ぶ?体験する?)べきことがあるんですね。

隠蔽は手続きを簡単にすると同時に理解を難しくしますよね?
恥ずかしながら、Delphi3.1のころから使っていながら、コードとしてのフォームの意味や、オブジェクトを変数として渡す考え方などは、業務でjavaをかじったことでようやく理解できました。
Delphiが隠蔽してくれている部分をjavaは自分で書く必要がありますので、そこの理解がDelphiに転移できたようです。

でも、皆様が活用しているように、かならず道筋はあると思います。
私は、その道筋を知りたいと感じます。


デル太  2006-03-21 11:55:22  No: 20587

さどやまさん、再びの書き込みありがとうございます。

別スレッドで「えーと」さんが、大変示唆に富んだ発言をしてくださいました。

>コピペで動作しないのは、初歩の初歩です。イベントハンドラは、TForm 派生クラスのメソッドにすぎませんが、特別なことがあります。それは、オブジェクトインスペクタを通じて、イベントとハンドラが結びつけられていることです。この内容は *.dfm ファイルに記録されています。ですから、単なるコピペでは、この結びつきがないので、イベントが起きてもハンドラが呼び出されません。イベントハンドラをコピペするときは、まず、オブジェクトインスペクタで空のハンドラを生成し、それをコピペで上書きするといいです。

わかったこと:

  (1)イベントハンドラはTForm派生クラスのメソッドである
  (2)オブジェクトインスペクタを通じて、イベントとハンドラが結び付けられる
  (3)この内容は *.dfm ファイルに記録されている
  (4)単なるコピペでは、この結びつきがないので、イベントが起きてもハンドラが呼び出されない

この点を意識して、さどやまさんのご説明を実験してみました。

-----
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure MyClick(Sender: TObject);  //TForm派生クラスのメソッド→オブジェクトインスペクタに表示された
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.MyClick(Sender: TObject);
begin
  ShowMessage('MyClick Done');
end;

end.
-----
  object Button1: TButton
    Left = 40
    Top = 40
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = MyClick  //さどやま様ご指摘のように設定されている
  end
-----

この実験から、かなり分かってきました・・。

>  (1)イベントハンドラはTForm派生クラスのメソッドである

とあるので、TForm派生クラス内に宣言を書きました。
すると、オブジェクトインスペクタにMyClickメソッドが表示されるではありませんか!
それを選択してOnClickに関連付けて、保存すると、OnClick = MyClick が記録されています。

「イベント」と「イベントハンドラ」が別にあるのか?という疑問についても

  「イベントが起きてもハンドラが呼び出されない」

という表現から、別にあるのだろうと推察できますね。
この点は、いずれ当たり前のようにわかってくるのかもしれません。


えーと  2006-03-21 12:07:35  No: 20588

> 「イベント」と「イベントハンドラ」が別にあるのか?という疑問についても

別にあるのです。イベントとは、イベントハンドラを設定できるプロパティー
なのです。ヘルプで TButton の OnClick イベントをみてください。

property OnClick: TNotifyEvent;

となっているでしょう。で、この TNotifyEvent 型をさらにヘルプで見てみると

type TNotifyEvent = procedure (Sender: TObject) of object;

なっているでしょう。イベントはメソッド型の変数を代入できるプロパティー
なのです。上のコードで MyClick をオブジェクトインスペクタで代入してますが、コードでも代入できます。 From1Create などで

Button1.OnClick := MyClick;

とするといいのです。これは文法的には OnClick プロパティーに TNotifyEvent 型の変数を代入しているのです。TButton の中では、Click イベントが起こるとこのプロパティーが設定されているかどうか見て、設定されているとそれを呼び出します。このように、イベントとイベントハンドラは、プロパティーを通じて結びつけられます。


デル太  2006-03-21 12:49:49  No: 20589

えーとさん、丁寧な噛み砕いたご説明をありがとうございます。
私の「ことば」や「表現」「考える方向」をあえて使ってくださっているのでしょうか?
非常に分かりやすいです。

> > 「イベント」と「イベントハンドラ」が別にあるのか?という疑問についても

> 別にあるのです。

あぁ、別にあるのですね!
やっと、すっきりしました。

> イベントとは、イベントハンドラを設定できるプロパティー
> なのです。ヘルプで TButton の OnClick イベントをみてください。

> property OnClick: TNotifyEvent;

ありました!

> となっているでしょう。で、この TNotifyEvent 型をさらにヘルプで見てみると

> type TNotifyEvent = procedure (Sender: TObject) of object;

> なっているでしょう。イベントはメソッド型の変数を代入できるプロパティー
> なのです。

はい、確かに、コードを見ると代入してますね。
そして、代入されているのはprocedureですね。
これはメソッド型なんですね。

> 上のコードで MyClick をオブジェクトインスペクタで代入してますが、コードでも代入できます。 From1Create などで

> Button1.OnClick := MyClick;

> とするといいのです。

あ、これは「初期値の設定」の一環として、イベントをイベントハンドラに関連付けしているんですね。

ここまででイベントハンドラがどこに記述されているかがわかりました。

-----
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure MethodName(Sender: TObject);  //これがイベントハンドラ(メソッドのひとつ)
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

procedure TForm1.MethodName(Sender: TObject);
begin
  //実行内容
end;
-----

次はイベントです。OnClickがイベントですが、
OnClickはクラス(オブジェクト)のプロパティです。
TButtonのソースを見ると、プロパティとして存在しています。

    property OnClick;

ただ、この先はたどれませんでした。
もしかしてたどらなくていいのかな?
別スレッドで疑問としてあげた、write read がなくて許されているのは、これがイベントだから?

そうでないとしたら、IDEはどうやってイベントとわかるのだろう・・?
名前にOnがついているから、だったりして。
そうでなければ、どこかで定義されているはずで・・、それがproperty OnClick; かな?
あぁ、どうどうめぐりしてますね。これは置いておこう。(苦笑)

そして、イベントハンドラとイベントの関連付けは、dfmファイルの中です。

    OnClick = MethodName

これは、オブジェクトインスペクタで代入した場合ですね。
コードでも代入可能ということですね。例は「えーと」さんのご説明どおりです。

> これは文法的には OnClick プロパティーに TNotifyEvent 型の変数を代入しているのです。TButton の中では、Click イベントが起こるとこのプロパティーが設定されているかどうか見て、設定されているとそれを呼び出します。

なるほど、OnClickはプロパティなんですね。
やはり、プロパティの中にイベントがあるから、プロパティがイベントか判断して、そうである場合はイベントとして扱う、という流れのような気がする。

> このように、イベントとイベントハンドラは、プロパティーを通じて結びつけられます。

ありがとうございます。

たぶん、これで、ようやく、わかりました。

  ・別物だということ。
  ・その結び付け方

がわかりました。
みなさま、ありがとうございます!

OnClickの記述場所というか、IDEはなぜイベントとわかるか?という点で疑問が残ってしまっている気がします。
みなさまに説明していただいているのに、理解が悪いのだと思います。
もう少し、時間を置いてみますが、私が疑問に感じていた部分が、ほとんど追跡できたように思います。

そして、見えてきた新たな課題は、上記でえーとさんがご説明くださったイベントの仕組みを、自分で実装することとなるのでしょうね。
もしかすると、これをトレースすることで「プロパティOnClickがイベント」を理解できるのかもしれません・・。

#別スレッドで話題がクロスしてしまっていますが、どうぞご了承ください。


匿名?  2006-03-21 17:53:13  No: 20590

> OnClickの記述場所というか、IDEはなぜイベントとわかるか?
> という点で疑問が残ってしまっている気がします。
えーとさんもおっしゃるとおり、「TNotifyEvent 型」だからです。
Integer型、String型(正確には違うけど)・・・いろいろありますが、
「型」という共通のカタチが同じだからです。

もう一つは、そのイベント(イベントメソッド)宣言の記述場所。

(デル太さんのレスからコピペして編集しました)
  TForm1 = class(TForm)
    Button1: TButton;
    procedure MethodName1(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure MethodName2(Sender: TObject);
  end;

二つのTNotifyEvent型のメソッドがありますが、IDEで拾ってくれるのは、
上のメソッドだけ。
もしかしたら、どこかで解説があるのかもしれませんが、仕様と思います。


  URL  2006-03-21 22:08:51  No: 20591

今読み返してみて、おかしな事を書いているなと思いました。

> property はメソッドポインタ(アドレス)だということです。
  「イベントプロパティはメソッドポインタ」の誤りです。

> 「y=-2aぶんのなんとか」
  ax^2+bx+c=0 のことですかね。
  もしそうなら、デル太さんはかなり頭が悪いですね。
  他の人がすぐ解ることを理解するのに随分と時間がかかるでしょう。
  そうでないと、これだけしつこく食い下がって聞かれないでしょうし。
  私も同じです。そのかわり、今でも紙と鉛筆と少しの時間をもらえればこの公式が正しくいえます。
  そういう人種が一流になる最大の秘訣は「長生き」することだと思っています。
  ズーと落ちこぼれの時代が続くが、ある日悟ったとき、他の人を一気に抜くことができる、と私は思ってやっています。

  こういうことに私が出しゃばるのは、勉強になるからです。
  解っているつもりのこともいざ自分の言葉で語ってみろと言われると実はよく解っていなかったこと、間違って理解していたことに気づきます。
  恥もかきますが、その分、身にしみて覚えます。

  この間、デル太さんはいろいろなことを試されましたが、私自身「ヘーそうだったのか」と思わされ、随分勉強になりました。
  一手に引き受けて下さっていたXRAYさんは時間的に大変だったと思いますが、援軍も揃ってきたようなので遠慮なく徹底して究明し、整理したものをいつかサイトで発表していただけませんか。イベントの問題をここまで深く論じたサイトは他に知りません。
  それに、公開しようと文章にまとめると、更に疑問が出てきてより深まります。
  1つのことを断定するのに1週間かかったりします。
  頭の悪い人にしかできないことだと思いますし、そいうサイトができれば、これから始める人はすぐに上級になれるでしょう。
  ただし、その問題に関してだけは。だから、そういう人が一人でも多く必要なのです。もちろん私にとってですが。

> read も writeもないので、Captionにアクセスしたときの振る舞いが思い浮かばないのです。  
  他のプロパティなら通常、
    property Caption: TCaption read GetCaption write SetCaption;
のようにしますが、それが無いということでしょうか。
  そうであれば、失礼しました。
  TControl で GetText, SetText が定義されています。
  そのSetTextが私が「1.」として紹介したSetTextBufを呼び出しすべてが始まります。


Mr.XRAY  URL  2006-03-21 23:39:55  No: 20592

>整理したものをいつかサイトで発表していただけませんか。
あれれっ,いや〜,困ったな.私自身は,イベントについて,デル太さんへの
レスの形で,当面以下の様に理解していれば,と考えていますので,以下の文章で
勘弁して下さいませ m(_ _)m .ごめんなさい.

------------------------------------------------------------------------

>なるほど、OnClickはプロパティなんですね。
>OnClickの記述場所というか、IDEはなぜイベントとわかるか?という点で疑問が
OnClickというプロパティを設定すると,Delphiは自動的にそれをイベントとして
処理してくれるわけです.
ですから,「なぜわかるか」ではなく,
「そうなるようにDelphiが設計されている」のです.

Edit1.Text:='Delphi';
とした時,デル太さんは,なぜIDE,DelphiはそれがTextだとわかるかは理解
しているわけですか.たとえ十分理解していなくても使えてますよね.それ
と同じです.

プロパティといっても,String型,Integer型,TColor等々,そしてイベント
の場合もいろいろありますが,この場合はTNotifyEvent型です.
これは,他の方のレスにも説明があります.

>ただ、この先はたどれませんでした。
たどれます.「さ」さんがCaptionについて説明されていますが,それと同じ
ように.

TObject
   |
TComponent
   |
TPersistent
   |
TControl
   |
TWinControl
   |
TButtonControl
   |
TButton

TControlで以下の様になっています.
FOnClick: TNotifyEvent;
つまり,TControlのOnClickを継承しているんですね.継承元のプロパティなりが,
継承先で利用できるという,Delphiのすばらしい機能の一つです.

そして,ButtonのOnClickイベントを作成して実行すると,それを実行して
くれます.もし,この動作を,Windowsのメッセージを自分で処理するとなると,

(1) Windowsのメッセージを常に監視し,何かの操作が発生したかを検査する
(2) それががマウスの左ボタンのクリックかを調べる
(3) わかったら,そのボタンが押された時に実行するコードに実行を移動する
(4) そのコードを実行する
(5) そのコード自身は,自分がいつどこから呼出されたか関係なく実行する

これを1からプログラマーがやっていたら大変です.Delphiではイベントの
コードを書いておくことで,全てこれを自動で実行してくれます.
つまり,プログラマーは,イベントハンドラを書くことに専念すればいい
ことになります.

これを「イベント駆動型プログラミング」と言っています.

なお,TButtonの場合,オブジェクトインスペクタでダブルクリックして
作成されるOnClickのイベントの名前は,Button1Clickとなりますが,
(ButtonのNameプロパティがButton1の場合)
空白状態でダブルクリックしないで,オブジェクトインスペクタの右の欄に,
自分で好きな名前を入力(タイプして)つけることもできます.

例えば ABCとタイプして,ダブルクリックすると,

procedure TForm1.ABC(Sender: TObject);
begin

end;

というのができます.もちろん,ちゃんと動作します.

-----------------------------------------------------------------
デル太さんの次なる疑問は
(1) Windowsのメッセージはどのようにして,どこで発生するのですか?
(2) それがマウスの左ボタンとどうしてわかるのですか?
(3) 等々... 
ということになるのでしょうか.これらの疑問にはお付き合いできません
のでご了承下さい(笑).


デル太  2006-03-22 01:54:16  No: 20593

匿名?様、さ(どやま)様、Mr.XRAY様、メッセージありがとうございます。
今日は時間がないため、いただいたメッセージに回答ができませんが、どうぞご了承ください。

今日は、最後に残った課題「独自のイベント実装」にトライしていました。

調べた結果「独自イベントは自分で起こす」ことと知りながら、すっかり気づかず最後の最後ではまりましたが何とか完成できました。
独自イベントが発生して、ダイアログが表示されたときはうれしかったです。
みなさま、本当にありがとうございます。

作成したコードについて、お気づきの点などございましたら、ご指導をいただけると幸いです。

-----
//独自イベント実装を実験します 20060321 15:09
//祝WBC Japan優勝!  優勝直後、独自イベント発生を確認できました
//Paintメソッドでイベントを発生させるメソッドを呼び出すことに気づかず
//しばらくはまっていました。その点がわかりにくいように感じました
//その点を注意してコードをご覧になってみてください。

//コードの合間に説明を添えています
//コードの内容や、説明に誤りなどございましたら、
//ご指摘、ご指導いただけると幸いです
//よろしくお願いします
//掲示板のやりとりの中で、疑問と感じて調査、解決できた内容について
//一区切りできる内容にできたと感じています。
//多くの皆様にご指導、ご指摘いただきました。
//ご指導いただいた皆様に感謝いたします
//                                                          by デル太

//独自の描画をする目的で質問をはじめ、いくつかの実現方法が考えられました
//その最後の方法として、独自イベントを実装する例を検討しました。
//ただし、特に必要がなければ独自イベントまで使う必要はないと感じています
//イベント実装例の簡単な例として、技術的なことを追確認できると思います。

    //イベント実装の説明
    //イベントはプロパティとして実装されている
    //そのプロパティには、イベントが発生する際に実行するメソッドを保持する
    //そのメソッドをイベントハンドラのと呼ぶ
    //イベントハンドラはTForm型から派生したクラスのメソッドのこと
    //具体的にはForm1のメソッドのひとつとして実装される
    //イベントハンドラはオブジェクトインスペクタでイベントに関連付けることができる
    //その関連情報は、*.dfmファイルに保存される
    //独自イベントを実装する場合も、この方法で実装する

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,
  ExtCtrls, StdCtrls; //TPaintBoxに必要

type
  //独自イベントを持ったクラスを準備する
  TMyPaintBox = class(TPaintBox)
  private
    //ここでイベントハンドラを保持する変数を準備する
    //その変数はイベント型とする
    //イベントの発生元(Sender)だけをパラメータに持つイベントは
    //TNotifyEvent型の変数を準備する
    //独自パラメータを持つイベントは次の形で宣言する
    //
    //  自作イベント型=Procedure(パラメータ1,パラメータ2,...) of Object;
    //
    //この例を見ると、メソッド(プロシージャ)が代入されていることがわかる
    //これは具体的には、メソッドのポインタを代入する
    //このようにメソッド(イベントを含む)を代入した変数を、メソッドとして
    //実行することができる
    //このサンプルで、その実例を見ることができる
    FOnMyEvent: TNotifyEvent; //イベント型として宣言する
  protected
    //独自のイベントは自作コードの中のどこかで発生するように作りこむ
    //そのためにイベントを発生させたい場所で、このメソッドを呼び出す
    //その結果、イベントが起きることになる
    //この記述内容からだけでは、このメソッドがイベントとはわからない(と思う)
    procedure MyEvent(Sender: TObject);
    //Paintメソッド内で独自イベントを発生させる
    //そのため、オリジナルのメソッドに機能を追加する
    procedure Paint; override;
  public
  published
    //イベントはプロパティとして実装するので、ここでプロパティで定義する
    //定義の仕方は通常のプロパティと同じ
    //やはりこの記述内容からだけでは、このプロパティがイベントとはわからない
    property OnMyEvent: TNotifyEvent read FOnMyEvent write FOnMyEvent;
  end;

  TForm1 = class(TForm)
    procedure Button1Click(Sender: TObject);
    //TFomr型派生クラスのメソッドとして実装する
    //するとイベントハンドラとして扱われる
    procedure MyEventHandler(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyPaint }

//イベントを発生させたいときにこのメソッドを呼び出す
procedure TMyPaintBox.MyEvent(Sender: TObject);
begin
  //Assigned関数でポインタ変数または手続き型変数が割り当てられているかテストする
  //FOnMyEventは変数だが、プロシージャを保持している
  //それを呼び出せば、保持された関数が実行される
  //それではいつ変数にプロシージャを代入するのだろうか?
  //設計時に配置したオブジェクトの場合、オブジェクトインスペクタで
  //イベントハンドラを関連付けるが、そのときに代入される
  //今回のように動的に生成したオブジェクトはIDEで代入しないため、
  //どこかでコードで代入することになる
  //たとえばForm.Createなどで次のように代入できる
  //  Button1.OnClick := MyClick;
  if Assigned(FOnMyEvent) then FOnMyEvent(Self);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
end;

//独自イベントが発生した際に呼び出されるイベントハンドラ
//これは、通常のイベントハンドラと同様に扱うことができる
//たとえばButton1Clickのイベントハンドラと同じ作業で準備できる
procedure TForm1.MyEventHandler(Sender: TObject);
begin
  TMyPaintBox(Sender).Canvas.Ellipse(0, 0, 30, 30); //小さい円を描く
  ShowMessage('独自イベントが発生しました');
end;

//FormCreateメソッドで、独自イベントを実装したオブジェクトを生成し、
//生成したオブジェクトが持つ独自イベントとイベントハンドラを関連付けている
procedure TForm1.FormCreate(Sender: TObject);
var
  AMyPaintBox: TMyPaintBox; //独自イベントを持つ変数を準備
begin
  AMyPaintBox := TMyPaintBox.Create(Self);
  AMyPaintBox.OnMyEvent := MyEventHandler; //イベントにイベントハンドラを関連付ける
  AMyPaintBox.Parent := Form1; //表示する
end;

//このメソッドの中に独自イベントを実装する
procedure TMyPaintBox.Paint;
begin
  inherited;
  MyEvent(Self); //ここで独自イベントが発生することになる
end;

end.


デル太  2006-03-22 01:56:14  No: 20594

さどやま様、頭の悪いデル太です。(笑)
暖かい応援メッセージありがとうございます。大変うれしく拝見しました。
私も何かを考えるとき、紙と鉛筆が必須です。
今日もみなさまのメッセージをすべて印刷して、そこに書き込みをしながら考えていました。

サイトにまとめる件、私へのご提案のようにも拝見いたしました。
私自身、別のサイトで見つけられずに、すがる思いでこの掲示板に書き込みした経緯がございます。
今回の内容は、自分としては本当に欲しかった情報で、私の中にはなかったものです。
決して私だけのものではないですし、Delphiを使う上で、多くの方にとっては既知の情報と思います。それでも、私にとってわかりやすい形

  ・概念説明
  ・各論説明(具体的な題材を実装する時の注意)
  ・コード例
  ・コードの細部の説明

を揃えられたように思います。
マーケティングの世界では、弱者に有効な商品は一般にも受け入れられるといいます。
わかるのに時間がかかる人間との自覚がございますので、私なりのまとめかたも有効と感じていただけるかもしれませんよね?

みなさまのお力添えに、何かお返しをしたいと感じていますので、
さどやまさんのご提案を受け、前向きに検討していきたいと思います。
どうぞ、これからもご指導いただけると幸いです。


デル太  2006-03-22 09:10:48  No: 20595

みつけました。継承元のTControlにClickがあり、コードが記述されていました
TControlのメンバにFOnClickもありました。
  FOnClick: TNotifyEvent;
対応するプロパティも見つけることができました。
  property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
ちゃんとreadとwriteもありました。継承元のクラスまで探せば見つかるんですね。
ヘルプによるとstoredは、フォームに設定を保存するかどうか?の指定子とのこと。
残念ながら、IsOnClickStoredが見つけられませんでしたが、この行での設定結果としてdfmファイルに設定が保存されるみたいです。


デル太  2006-03-22 18:19:43  No: 20596

独自イベントを実装したコードのコメント文を訂正します

>   published
>     //イベントはプロパティとして実装するので、ここでプロパティで定義する
>     //定義の仕方は通常のプロパティと同じ
>     //やはりこの記述内容からだけでは、このプロパティがイベントとはわからない
>     property OnMyEvent: TNotifyEvent read FOnMyEvent write FOnMyEvent;

      //ここでTNotyfyEventと宣言しているため、このプロパティがイベントとわかる
      property OnMyEvent: TNotifyEvent read FOnMyEvent write FOnMyEvent;

> そして、見えてきた新たな課題は、上記でえーとさんがご説明くださったイベントの仕組みを、自分で実装することとなるのでしょうね。
> もしかすると、これをトレースすることで「プロパティOnClickがイベント」を理解できるのかもしれません・・。

この疑問が解決されたことになります。


デル太  2006-03-22 18:23:18  No: 20597

さらにイベント実装の説明を訂正します。

     //イベント実装の説明
     //イベントはプロパティとして実装されている
     //プロパティの宣言時にイベント型として宣言する
     //そのプロパティには、イベントが発生する際に実行するメソッドを保持する  ←追加

     //×そのメソッドをイベントハンドラと呼ぶ  ←これは間違いなので削除

     //イベントが発生した際に実行されるコードは、イベントハンドラに記述する  ←追加

     //イベントハンドラはTForm型から派生したクラスのメソッドのこと
     //具体的にはForm1のメソッドのひとつとして実装される
     //イベントハンドラはオブジェクトインスペクタでイベントに関連付けることができる
     //その関連情報は、*.dfmファイルに保存される
     //独自イベントを実装する場合も、この方法で実装する


デル太  2006-04-07 08:34:34  No: 20598

最後の疑問が、解決しました!

>残念ながら、IsOnClickStoredが見つけられませんでしたが、この行での設定結果としてdfmファイルに設定が保存されるみたいです

IsOnClickStored もTControlのメソッドでした。
さきほど別件で、言語ガイドのインデックス指定子を勉強していて、隣のページの格納指定子をじっくり読み直し、stored指定子の後にBooleanが来るとわかりました。
そこでようやく、IsOnClickが関数だと気づき、探し出せました。

-----
TControl = class(TComponent)
private
  function IsOnClickStored: Boolean;
protected
  property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
   :
function TControl.IsOnClickStored: Boolean;
begin
  Result := (ActionLink = nil) or not ActionLink.IsOnExecuteLinked;
end;
-----

-----
格納指定子
オプションの指令である stored,default,nodefault の 3 つの指令は格納指定子と呼ばれます。格納指定子はプログラムの動作に影響を及ぼすものではなく,実行時型情報(RTTI)を管理する方法を制御するものです。具体的には,格納指定子により,パブリッシュプロパティの値をフォームファイルに保存するかどうかが決まります。

stored 指令の後には,True,False,Boolean 型のフィールドの名前,論理値を返す,パラメータをとらないメソッドの名前のいずれかを指定しなければなりません。
-----

しっかり読み取れれば、ちゃんと書いてあるんですね。
うーん、情けない・・。
みなさん、ほんとうにご迷惑をおかけしていて、申し訳ありません。
少しですが、Delphiの動きのイメージがつかめたと思います。
ご指導、ありがとうございました


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加