喫茶XRAYトップページ

[▲前のスレッド]

[11062] procedure手続きを手続き名で検索できますか返信 削除
2017/4/10 (月) 19:06:04 taki
動的にコンポーネントを生成しているのですが、text等に記録した文字列から生成したいのです。
TopとかLeftとかCaptionとかは文字列から値を代入できます。
MyButton.ParentはGetComponentByName()という関数を作って文字列'Panel1'からTWinControlを
得ることができたのですが、MyEvent手続きを手続き名'MyEvent'から探す方法が分かりません。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    MyButton:TButton;
    procedure MyEvent(Sender: TObject);
    function GetComponentByName(AName:String):TComponent;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.GetComponentByName(AName:String):TComponent;
var
  i:integer;
begin
  result := self;     // 無かったらForm1に生成
  if Form1.ComponentCount = 0 then exit;
  for i := 0 to Form1.ComponentCount - 1 do begin
    if Form1.Components[i].Name = AName then begin
      result := Form1.Components[i];
      exit;
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := GetComponentByName('Panel1') as TWinControl;
//Panel1上にボタンを生成している
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';
  MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if MyButton <> nil then FreeAndNil(MyButton);
end;

procedure TForm1.MyEvent(Sender: TObject);
begin
  beep;
end;

end.


[11063] Re:procedure手続きを手続き名で検索できますか返信 削除
2017/4/10 (月) 19:42:08 taki
Form1.MethodAddress()が使えそうな…。
しかし、よくわからん。


[11064] Re:procedure手続きを手続き名で検索できますか返信 削除
2017/4/10 (月) 20:40:27 Mr.XRAY
▼ takiさん

私もよくは分からんのですが
RTTI (実行時型情報) を利用するとか...

[456_RTTI ( 実行時型情報 ) を使用したメソッドの実行]
http://mrxray.on.coocan.jp/Delphi/plSamples/456_RTTI_InvokeMethod.htm

[458_RTTI ( 実行時型情報 ) を使用したプロパティの列挙]
http://mrxray.on.coocan.jp/Delphi/plSamples/458_RTTI_Properties.htm


[11065] Re2:procedure手続きを手続き名で検索できますか返信 削除
2017/4/10 (月) 23:08:32 taki
▼ Mr.XRAYさん

> 私もよくは分からんのですが
> RTTI (実行時型情報) を利用するとか...


マスター ありがとうございます。

ttp://d.hatena.ne.jp/tales/20121205/1354636256 で
こんなコードを見つけました。

procedure TForm1.Button1Click(Sender: TObject);
var
  method: TMethod;  // 追加
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := GetComponentByName('Panel1') as TWinControl;
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';
// ---------- ここから
  method.Code := @TForm1.MyEvent;
  method.Data := nil;
  MyButton.OnClick := TNotifyEvent(method);
// ---------- ここまで
end;

それで、label1を追加して内容を表示してみました。

procedure TForm1.Button1Click(Sender: TObject);
var
  method: TMethod;
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := GetComponentByName('Panel1') as TWinControl;
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';
// ---------- ここから
  label1.Caption := inttostr(Integer(@TForm1.MyEvent));   // 5977080
  method.Code := Pointer(strtoint(label1.Caption));
  method.Data := nil;
  MyButton.OnClick := TNotifyEvent(method);
// ---------- ここまで
end;

コンパイルする度にlabel1の値は変わりますが、プログラムを単独で動かしても
値が変わることはありません。
なので、この値を保存しておけばいいのかもしれません。
なお、method.Dataについてはもう少し調べる必要があります。



[11066] Re3:procedure手続きを手続き名で検索できますか返信 削除
2017/4/10 (月) 23:24:01 Mr.XRAY
▼ takiさん

> var
>   method: TMethod;  // 追加


あっ,そうか.
実行字型情報では無理そうですね.クラス情報とっていますから.


[11071] Re4:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 10:27:51 taki
▼ Mr.XRAYさん
> ▼ takiさん
>
> > var
> >   method: TMethod;  // 追加
>
> あっ,そうか.
> 実行字型情報では無理そうですね.クラス情報とっていますから.


ttp://d.hatena.ne.jp/tales/20121205/1354636256 の内容を
朝、読み返すと私ごときが踏み込んではいけない領域だったみたい。

という事で撤収します。 逃げ恥〜!
お騒がせしました。m(_ _)m


[11072] Re5:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 11:05:01 igy
試してないですけど、

Delphiのメソッドをメソッド名から実行する方法
http://www.gesource.jp/weblog/?p=642

とか、いかがですか?


[11074] Re6:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 12:59:43 taki
▼ igyさん
> 試してないですけど、
>
> Delphiのメソッドをメソッド名から実行する方法
> http://www.gesource.jp/weblog/?p=642
>
> とか、いかがですか?


ありがとうございます。

実はこのページの内容も参考にして色々と試してみました。(過去形)

procedure ExecMethod(OnObject: TObject; MethodName: string);
をコピペしました。

procedure TForm1.Button4Click(Sender: TObject);
begin
  ExecMethod(Form1,'MyEvent');      // Beep鳴らない
end;

それで新たにButton5を配置して、IDEからイベントを関連付けました。

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

procedure TForm1.Button4Click(Sender: TObject);
begin
//  ExecMethod(Form1,'MyEvent');      // Beep鳴らない
  ExecMethod(Form1,'Button5Click');   // Showmessage表示された
end;

procedure TForm1.MyEvent(Sender: TObject);
begin
  beep;
end;

IDEで関連付けたButton5Clickとかだったら動くようですが、
動的に生成したイベントの手続き、もしくは標準的なイベントでない?場合は
Form1.MethodAddress()がnilしか返してくれません。

それで、method.Code := @TForm1.MyEvent; にたどり着いたのですが…。

逃げるは恥だが仕方がない!


[11076] Re7:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 13:24:15 igy
▼ takiさん
> 動的に生成したイベントの手続き、もしくは標準的なイベントでない?場合は
> Form1.MethodAddress()がnilしか返してくれません。



ヘルプみたら、
> MethodAddress メソッドは,パブリッシュメソッドのアドレスを返します。
って書いてましたね。失礼しました。

なので、


  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure MyEvent(Sender: TObject);   // <===== ここに移動
  private
    { Private 宣言 }
    MyButton:TButton;
    function GetComponentByName(AName:String):TComponent;
  public
    { Public 宣言 }
  end;


だったら、 Beepが鳴るかもしれません。


[11077] Re8:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 13:58:53 taki
▼ igyさん
> ▼ takiさん
> > 動的に生成したイベントの手続き、もしくは標準的なイベントでない?場合は
> > Form1.MethodAddress()がnilしか返してくれません。
>
>
> ヘルプみたら、
> > MethodAddress メソッドは,パブリッシュメソッドのアドレスを返します。
> って書いてましたね。失礼しました。
>
> なので、
>
>
>   TForm1 = class(TForm)
>     Button1: TButton;
>     Panel1: TPanel;
>     Button2: TButton;
>     procedure Button1Click(Sender: TObject);
>     procedure Button2Click(Sender: TObject);
>     procedure MyEvent(Sender: TObject);   // <===== ここに移動
>   private
>     { Private 宣言 }
>     MyButton:TButton;
>     function GetComponentByName(AName:String):TComponent;
>   public
>     { Public 宣言 }
>   end;
>
>
> だったら、 Beepが鳴るかもしれません。


Beep鳴りました。
ありがとうございます。

ヘルプの
「名前を指定すると、クラス メソッドのアドレスを返します。」
がパブリッシュメソッドの事だったのでしょうか。

とにかく、先が開けました。


[11078] Re9:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 14:40:17 taki
> とにかく、先が開けました。

OnClickイベントの他にOnMouseUpイベントも動くようになりました。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Button2: TButton;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure MyEventClick(Sender: TObject);
    procedure MyEventMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
  private
    { Private 宣言 }
    MyButton:TButton;
    function GetComponentByName(AName:String):TComponent;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TForm1.GetComponentByName(AName:String):TComponent;
var
  i:integer;
begin
//  result := nil;
  result := self;     // 無かったらForm1
  if Form1.ComponentCount = 0 then exit;
  for i := 0 to Form1.ComponentCount - 1 do begin
    if Form1.Components[i].Name = AName then begin
      result := Form1.Components[i];
      exit;
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  method: TMethod;
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := GetComponentByName('Panel1') as TWinControl;
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';
// ----------
  method.Code := Form1.MethodAddress('MyEventClick');
  method.Data := Pointer(Form1);
  MyButton.OnClick := TNotifyEvent(method);

  method.Code := Form1.MethodAddress('MyEventMouseUp');
  method.Data := Pointer(Form1);
  MyButton.OnMouseUp := TMouseEvent(method);
// ----------
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if MyButton <> nil then FreeAndNil(MyButton);
end;

// OnClickの場合
procedure TForm1.MyEventClick(Sender: TObject);
begin
  beep;
end;

// OnMouseUpの場合
// 引数を TForm1.Button?MouseUp に真似て作成
procedure TForm1.MyEventMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  showmessage('座標 ' + inttostr(X) + ' ' + inttostr(Y));
end;

end.


[11079] Re8:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 14:47:21 Mr.XRAY
▼ igyさん

>   TForm1 = class(TForm)
>     Button1: TButton;
>     Panel1: TPanel;
>     Button2: TButton;
>     procedure Button1Click(Sender: TObject);
>     procedure Button2Click(Sender: TObject);
>     procedure MyEvent(Sender: TObject);   // <===== ここに移動
>   private


なるほど,これなら RTTI のテーブルにのりますね.
確認しました.
RTTI にのれば何でもやり放題 !!


[11073] Re:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 12:30:34 snail
▼ takiさん

>   MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい


この意味が理解手せきません。おせーて

[11075] Re2:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 13:08:56 taki
▼ snailさん
> ▼ takiさん
>
> >   MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい
>
>
> この意味が理解手せきません。おせーて


分かりにくくてすみません。

「MyButton.OnClick := MyEvent;」

はOnClickイベントにMyEvent手続きを関連付け(登録?)することで、
たぶん、手続きのポインタが渡されるんだろうなと想像してました。

「// ← ここの値を'MyEvent'から得たい」

は'MyEvent'文字列でポインタを探す方法がないかなと言う意味です。

えっと…、基本的な間違いがありますか? (^_^;


[11080] Re3:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 14:56:15 taki
▼ snailさん

 >   MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい

これって、変ですよね。
たまたまOnClickだったからコンパイラーが許してくれたのかも。


[11083] Re4:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 16:09:08 taki
>  >   MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい
>
> これって、変ですよね。
> たまたまOnClickだったからコンパイラーが許してくれたのかも。


すみません。

MyButton.OnClick := TNotifyEvent(method);

と勘違いしました。


[11084] Re5:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 16:36:23 snail
インターネットで検索するとメソッドアドレス取り出し方法として
MethodAddress が利用できる山本さんやStackOverflowなどで
紹介されていたので実際に試してみました。

// サンプルソース抜粋
function TForm9.MethodFromName(const Name: string): TNotifyEvent;
begin
  TMethod(Result).Data := Self;
  TMethod(Result).Code := Self.MethodAddress(Name);
  if TMethod(Result).Code=nil then
    Memo1.Lines.Add('Not find ' + Name )
   else
    Memo1.Lines.Add('Find ' + Name );
end;

結果、メソッドが定義されている場所が private や public だと駄目でした。
なぜ駄目かは?

[11085] Re6:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 18:23:22 igy
▼ snailさん
> 結果、メソッドが定義されている場所が private や public だと駄目でした。
> なぜ駄目かは?


上のほうでも、書きましたが、

Delphi7のヘルプでは、

> MethodAddress メソッドは,パブリッシュメソッドのアドレスを返します。

とありますので、publish以外では、ダメみたいですね。


[11087] Re7:procedure手続きを手続き名で検索できますか返信 削除
2017/4/11 (火) 20:32:28 snail
▼ igyさん

> > MethodAddress メソッドは,パブリッシュメソッドのアドレスを返します。
>
> とありますので、publish以外では、ダメみたいですね。


ヘルプを改めてみると
> published メンバについては、コンパイラにより実行時型情報が生成されます。
> published プロパティは、設計時に[オブジェクト インスペクタ]に表示されます。


snailは1つ賢くなった。が、2つ忘れた。



[11086] TWinControlを検索返信 削除
2017/4/11 (火) 19:51:16 Mr.XRAY
>   MyButton.OnClick := MyEvent;  // ← ここの値を'MyEvent'から得たい

ができるようになったところで,

  MyButton.Parent := GetComponentByName('Panel1') as TWinControl;

なんですが,以下のようにできる気がします.
親コントロール(コンテナ)になれるのは TWindControl だけですから,
FindControl は TWinControl だけを検索します.

  MyButton.Parent := FindControl(Panel2.Handle);

http://docwiki.embarcadero.com/Libraries/Berlin/ja/Vcl.Controls.FindControl

[追記]
つまり,こういう感じです.

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

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  LMethod : TMethod;
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := FindControl(Panel1.Handle);
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';

  LMethod.Data := Self;
  LMethod.Code := Self.MethodAddress('MyEvent');
  if Assigned(LMethod.Code) then begin
    MyButton.OnClick := TNotifyEvent(LMethod);
  end;
end;

procedure TForm1.MyEvent(Sender: TObject);
begin
  ShowMessage('Clicked !!');
end;



[11089] Re:TWinControlを検索返信 削除
2017/4/12 (水) 00:18:46 Mr.XRAY
> なんですが,以下のようにできる気がします.
> 親コントロール(コンテナ)になれるのは TWindControl だけですから,
> FindControl は TWinControl だけを検索します.


あっ,ゴメンナさい.もしかして,これも名前で調べたいのですね.
失礼しました m(_ _)m


[11092] publishedに記述する意味返信 削除
2017/4/13 (木) 17:18:22 taki
▼ Mr.XRAYさん
 
> [追記]
> つまり,こういう感じです.
>
> type
>   TForm1 = class(TForm)
>     Button1: TButton;
>     Panel1: TPanel;
>     procedure Button1Click(Sender: TObject);
>   private
>     { Private 宣言 }
>     MyButton : TButton;
>   public
>     { Public 宣言 }
>   published
>     procedure MyEvent(Sender: TObject);
>   end;


http://mrxray.on.coocan.jp/Delphi/plSamples/122_PluralForms.htm
「09_他のフォームのユニットの変数やメソッド類を使う」の最後の方に

type
  TMyClass = class(TObject)
    //ここには書かないこと(Delphiが使用する特別な領域)
  private
    //ここに書いた変数やメソッド,関数類は同じユニット内だけでアクセス可能
    //他のユニットからはアクセスできない
  protected
    //ここに書いた変数やメソッド,関数類は同じユニット内だけでアクセス可能
    //他のユニットからはアクセスできない
  public
    //ここに書いた変数やメソッド,関数類はどのユニットからもアクセス可能
  published
    //ここに書いた変数やメソッド,関数類はどのユニットからもアクセス可能
  end;

というのがありました。
なので、マスターのコードではpubulicの下にpublishedを追加してそこにMyEventを
記述してあるのですね。

「11_public 部と published 部の違い」の説明中に
「また,published 部のメンバーは,クラスのインスタンス (TButton ならば Button1 等) を
ファイルに保存すると,その設定値もファイルに保存されます.public 部の値は保存されません.」
とあるのですが、ファイルに保存の意味が分かりません。
TButton.SaveToFile()みたいなのがあるのかと思いましたが無いし。


[11093] コンポーネントの保存返信 削除
2017/4/13 (木) 18:35:46 Mr.XRAY
▼ takiさん

> 「11_public 部と published 部の違い」の説明中に

これなんですが,まず,TForm は特別でして,published に書いても
オブジェクトインスペクタには表示されません.で,

> ファイルに保存すると,その設定値もファイルに保存されます.public 部の値は保存されません.」

これなんですが,コンポーネントの保存は,ストリームとして保存します.
上のサンプルの Button1 クリックを以下のように変更し,
TButton を 1 つ追加して,この Button2 のクリックイベントを以下のようにします.

procedure TForm1.Button1Click(Sender: TObject);
var
  LMethod : TMethod;
  LFileStream : TFileStream;
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := FindControl(Panel1.Handle);
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';

  LMethod.Data := Self;
  LMethod.Code := Self.MethodAddress('MyEvent');
  if Assigned(LMethod.Code) then begin
    MyButton.OnClick := TNotifyEvent(LMethod);
  end;

  //コンポーネントの保存
  LFileStream := TFileStream.Create('AAA.bin', fmCreate);
  try
    LFileStream.WriteComponent(MyButton);
  finally
    FreeAndNil(LFileStream);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LFileStream : TFileStream;
  LMyButton : TButton;
begin
  //同名のコンポーネントは作成できないので別の名前にする
  //Button1クリックの直後に実行することも考慮
  LMyButton := TButton.Create(Self);

  //コンポーネントの読み込み
  //イベント用のメソッドはTForm1のメンバなので保存はされていない
  //イベントを保存するには,TButtonの継承クラスを作成して,そのイベントを作成する
  LFileStream := TFileStream.Create('AAA.bin', fmOpenRead);
  try
    LFileStream.ReadComponent(LMyButton);
    //親(コンテナ)を設定しないと表示できないでエラー
    LMyButton.Parent := Panel1;
    LMyButton.Update;

    Sleep(1000);
    LMyButton.Top := 100;
  finally
    FreeAndNil(LFileStream);
  end;
end;

そして,以下の手順でコンポーネントが保存されたことを確認できます.

(1) EXE を起動して Button1 をクリック
    AAA.bin というファイルができる
(2) EXE を閉じる
(3) 再度 EXE を起動して Button2 をクリック

動作確認は Windows 7 U64(SP1) + Delphi XE(UP1) Pro でござんす.


下の図は Buttpm2 をクリックして 1 秒後の状態

[11094] Re:コンポーネントの保存返信 削除
2017/4/13 (木) 21:03:06 taki
▼ Mr.XRAYさん

マスター ありがとうございます。

コンポーネントそのものに保存する機能はない。
TFileStream,TMemoryStreamのWriteComponent,ReadComponentメソッドを使う。

その時に保存される内容はAAA.binをメモ帳で見るとクラス名、位置、大きさ、
キャプション、タブオーダーまでで、イベントの設定の情報は無い。

オブジェクトインスペクタに表示されるButton1,Button2などは「設計時」の情報として
Unit1.dfmファイルに「OnClick = Button1Click」の記述がある。

'Button1Click'は多分Self.MethodAddress()で使われるのだろうと想像します。
ここまでは理解できます。

「//イベントを保存するには,TButtonの継承クラスを作成して,
そのイベントを作成する」
とのことですが、継承クラスを作成してキャストしなくてもMethodが代入できる?
みたいです。

追加した部分のコードで何か根本的な勘違いがありますでしょうか?


unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

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

{
type
  KeiButton = class(TButton);    // 追加 継承クラス用
}

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  LMethod : TMethod;
  LFileStream : TFileStream;
begin
  MyButton := TButton.Create(Self);
  MyButton.Parent := FindControl(Panel1.Handle);
  MyButton.Top := 20;
  MyButton.Left := 20;
  MyButton.Caption := '私のボタン';

  LMethod.Data := Self;
  LMethod.Code := Self.MethodAddress('MyEvent');
  if Assigned(LMethod.Code) then begin
    MyButton.OnClick := TNotifyEvent(LMethod);
  end;

  //コンポーネントの保存
  LFileStream := TFileStream.Create('AAA.bin', fmCreate);
  try
    LFileStream.WriteComponent(MyButton);
  finally
    FreeAndNil(LFileStream);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LMethod : TMethod;
  LFileStream : TFileStream;
  LMyButton : TButton;
begin
   //同名のコンポーネントは作成できないので別の名前にする
  LMyButton := TButton.Create(Self);

   //コンポーネントの読み込み
  //イベント用のメソッドはTForm1のメンバなので保存はされていない
  LFileStream := TFileStream.Create('AAA.bin', fmOpenRead);
  try
    LFileStream.ReadComponent(LMyButton);
    //親(コンテナ)を設定しないと表示できないでエラー
    //イベントを保存するには,TButtonの継承クラスを作成して,そのイベントを作成する
    LMyButton.Parent := Panel1;
//-------------  イベントを作成
    LMethod.Data := Self;
    LMethod.Code := Self.MethodAddress('MyEvent');
    if Assigned(LMethod.Code) then begin
//      KeiButton(LMyButton).OnClick := TNotifyEvent(LMethod);  // KeiButtonでキャスト
      LMyButton.OnClick := TNotifyEvent(LMethod);  // 直接代入できちゃう?
    end;
//-------------
    LMyButton.Update;   // .Top := 20; を表示するため

    Sleep(1000);
    LMyButton.Top := 100;
  finally
    FreeAndNil(LFileStream);
  end;
end;

procedure TForm1.MyEvent(Sender: TObject);
 begin
   ShowMessage('Clicked !!');
 end;

end.


[11095] Re2:コンポーネントの保存返信 削除
2017/4/13 (木) 21:31:16 Mr.XRAY
▼ takiさん

> とのことですが、継承クラスを作成してキャストしなくてもMethodが代入できる?
> みたいです。


できやす.
もし,継承クラスを作成して,イベントを published なプロパティにしておけば,
コードで設定しなくても動作するというわけでござんす.
>
> 追加した部分のコードで何か根本的な勘違いがありますでしょうか?


ないと思いますが...
プログラムは「勝てば官軍,動けば正義」です.ハイ.


[11096] Re3:コンポーネントの保存返信 削除
2017/4/13 (木) 22:26:34 taki
▼ Mr.XRAYさん

> もし,継承クラスを作成して,イベントを published なプロパティにしておけば,
> コードで設定しなくても動作するというわけでござんす.


しつこくてすみません。

イベント作成の部分を以下のようにコメントアウトすると

{
//-------------  イベントを作成
    LMethod.Data := Self;
    LMethod.Code := Self.MethodAddress('MyEvent');
    if Assigned(LMethod.Code) then begin
//      KeiButton(LMyButton).OnClick := TNotifyEvent(LMethod);  // KeiButtonでキャスト
     LMyButton.OnClick := TNotifyEvent(LMethod);  // 直接代入できちゃう?
   end;
//-------------
}

Button1で生成したMyButtonをクリックするとShowmessageが表示されますが、
Button2で生成したLMyButtonをクリックしてもShowmessageは表示されません。
つまり、コードを書かないと動かないということです。

イベントは published に記述されています。

「継承クラスを作成して…」の部分がどうもよくわかりません。

どのように書けば、コードを書かなくても動作するのでしょうか。



[11098] Re4:コンポーネントの保存返信 削除
2017/4/13 (木) 23:02:22 Mr.XRAY
▼ takiさん

> 「継承クラスを作成して…」の部分がどうもよくわかりません。
>
> どのように書けば、コードを書かなくても動作するのでしょうか。


ゴメンナさい.よく考えてたら,継承クラスを作成しても,
元々あるイベントが保存されてしまいますね.nil のままで.
つまり,やっぱり,実行時に設定しないと,文字列の名前で別のイベントは
定義できないですね.ハハハッ.


[11099] Re5:コンポーネントの保存返信 削除
2017/4/14 (金) 08:22:37 taki
▼ Mr.XRAYさん

> ゴメンナさい.よく考えてたら,継承クラスを作成しても,
> 元々あるイベントが保存されてしまいますね.nil のままで.
> つまり,やっぱり,実行時に設定しないと,文字列の名前で別のイベントは
> 定義できないですね.ハハハッ.


了解しましたっ!
ありがとうございました。

それと、AAA.binの内容はバイナリーの情報である事を付け加えておきます。
つまり、.dfmファイルとは異なります。



[11111] Re6:コンポーネントの保存返信 削除
2017/4/17 (月) 00:00:39 Konnoya
▼ Mr.XRAYさん▼ takiさん

これ、私も過去にチャレンジしてた事があります。
辿り付いた結果が TFileStream の ReadComponent & WriteComponentプロパティ
でしたが、試しにOnClickイベント有り/無しでファイルを作ってみた所、
保存されたファイルのバイナリに変化は無かったと記憶してます。

複数のリッチエディットのテキストをまとめて1つのファイルにするテクニックを使って、
コンポーネントとメソッドをどうにかまとめて保存できないかと試行錯誤しましたが、
どうやらそんな問題でも無いらしく、良く判らないままで終わりました(笑)

プロジェクトを読み込む際の*.dfmファイルみたいに、
コンポーネントのテキスト情報を読み込む「何か」があるんじゃないか?と
思ってましたが…。

[11112] Re7:コンポーネントの保存返信 削除
2017/4/17 (月) 07:51:25 Mr.XRAY
▼ Konnoyaさん

> 保存されたファイルのバイナリに変化は無かったと記憶してます。

ですね.
テキスト形式で保存するとなると,RTTI から取得してとか,
↓ な方法とか... どちらも実際やってみないとどこまで可能かは不明ですが...

[ Delphi: REST.JSON を利用したオブジェクトの直列化]
http://d.hatena.ne.jp/igapyon/20151203
[ユーザー オブジェクトのシリアル化]
http://docwiki.embarcadero.com/RADStudio/Seattle/ja/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC_%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E5%8C%96


type
  TMyApp = class(TObject)
  private
    FName: string;
    FAge: Integer;
    FPhoto: TBytes;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
    property Photo: TBytes read FPhoto write FPhoto;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Image1: TImage;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  REST.JSON;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'JSON を利用したシリアライズ';
  Edit1.Text := '私は誰';
  Edit2.Text := '100';
end;

//=============================================================================
//  Delphi: REST.JSON を利用したオブジェクトの直列化
//
//  [いがぴょんの日記@はてな]より
//  http://d.hatena.ne.jp/igapyon/20151203
//
//  usesにREST.JSONが必要
//  動作確認 Window 7 U64(SP1) + DelphiXE5(UP2) Pro VCL-32
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMyApp  : TMyApp;
  LStream : TMemoryStream;
begin
  LMyApp  := TMyApp.Create;
  LStream := TMemoryStream.Create;

  try
    LMyApp.name := Edit1.Text;
    LMyApp.Age  := StrToInt(Edit2.Text);
    Image1.Picture.Bitmap.SaveToStream(LStream);

    SetLength(LMyApp.FPhoto, LStream.Size);
    LStream.Position := 0;
    LStream.Read(LMyApp.Photo[0], LStream.Size);

    Memo1.Lines.Text := TJson.ObjectToJsonString(LMyApp);
  finally
    FreeAndNil(LMyApp);
    FreeAndNil(LStream);
  end;
end;



[11114] Re8:コンポーネントの保存返信 削除
2017/4/17 (月) 15:22:43 Mr.XRAY
> テキスト形式で保存するとなると,RTTI から取得してとか,

もし,朝鮮してみようという方がいましたら,福士さんのが参考になります.
もちろん「他に急ぐ仕事がない」人に限るかも知れませんが ( <-- しつこい !)

「Delphi言語再入門」〜拡張されたRTTIを試してみる
https://community.embarcadero.com/images/42116/T8_Part2.pdf


[11113] Re6:コンポーネントの保存返信 削除
2017/4/17 (月) 15:05:47 taki
> それと、AAA.binの内容はバイナリーの情報である事を付け加えておきます。
> つまり、.dfmファイルとは異なります。


ReadComponentメソッドを検索していたら、こんなページに出くわしました。
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TStream_ReadComponent.html

その中の Delphi Examples に超超ステキな手続きがありました。

コードを実行してみたら、.dfmファイルに出力される物と同じ?と思われる
文字列がMemoに得られました。

それを参考にして以下のようなコードを作成して試しました。

Button3でStringGridを動的に生成します。
ExamplesではFormCreateで行われる内容です。

Button1でそれをテキスト化します。Memoに文字列が表示されます。
MemoはScrollBarsをssBothにしておいてください。

Button2でStringGridをFreeした後、Memoのテキストから
コンポーネントを生成します。

----------------------------------
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Grids;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  published
    procedure MyEvent(Sender: TObject);
  end;

var
  Form1: TForm1;
  StringGrid1 : TStringGrid;
  LMethod : TMethod;

implementation

{$R *.dfm}

function ComponentToStringProc(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

function StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= BinStream.ReadComponent(nil);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

// コンポーネントを文字列化
procedure TForm1.Button1Click(Sender: TObject);
begin
  if Assigned(StringGrid1) then Memo1.Text := ComponentToStringProc(StringGrid1);
end;

// 文字列からコンポーネント生成
procedure TForm1.Button2Click(Sender: TObject);
begin
  if Length(Memo1.Text) <= 10 then exit;
  if Assigned(StringGrid1) then StringGrid1.Free;
  StringGrid1 := (StringToComponentProc(Memo1.Text) as TStringGrid);
  StringGrid1.Parent := Form1;
  StringGrid1.Visible := true;
  StringGrid1.Repaint;

  sleep(1000);
  StringGrid1.Top := 200;

  //------ やはりここでイベントを登録しないとダメみたい
{
  LMethod.Data := Self;
  LMethod.Code := Self.MethodAddress('MyEvent');
  if Assigned(LMethod.Code) then begin
    StringGrid1.OnClick := TNotifyEvent(LMethod);
  end;
}
  //--------------------------------------
end;

// テスト対象のStringGridを動的に生成
procedure TForm1.Button3Click(Sender: TObject);
begin
  if Assigned(StringGrid1) then begin
    showmessage('既に存在しています。');
    exit;
  end;
  StringGrid1 := TStringGrid.Create(Form1);
  StringGrid1.Parent := Form1;
  StringGrid1.Name := 'Ojisan';   // 名前は英数のみ有効
  StringGrid1.Top := 70;
  StringGrid1.Left := 330;
  StringGrid1.Options := StringGrid1.Options + [goRowSizing,goColSizing,goEditing];
  StringGrid1.Cells[2,2] := 'データ';
  StringGrid1.ColWidths[2] := 100;
//------- イベントの登録
  LMethod.Data := Self;
  LMethod.Code := Self.MethodAddress('MyEvent');
  if Assigned(LMethod.Code) then begin
    StringGrid1.OnClick := TNotifyEvent(LMethod);
  end;
end;

// 実行されるイベント
procedure TForm1.MyEvent(Sender: TObject);
begin
  ShowMessage('Clicked !!');
end;


initialization

  RegisterClass(TStringGrid);
// 文字入力を行うとInplaceEditを使うのでここに登録する
// 登録しないと 'クラスTInplaceEditが見つかりません'のエラーが出る
  RegisterClass(TInplaceEdit);

end.

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

結果は形状(セルの幅とか高さ)、OptionsなどはText化されますが、
セル内のデータとイベントは保存されないようです。

でも.dfmファイルには[OnClick = Button1Click]のようなイベントに
関連した文字列があるので、どこかにあるんでしょうね。


[11115] Re7:コンポーネントの保存返信 削除
2017/4/17 (月) 15:29:18 Mr.XRAY
▼ takiさん

> ReadComponentメソッドを検索していたら、こんなページに出くわしました。
> http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Classes_TStream_ReadComponent.html


おおっ !!
そうか, ObjectBinaryToText なんてのを忘れていました.
ハハハッ.
しかし,taki さんってぱ.
精通していないからと言って逃げない人なんですね.( <-- しつこい ! )
いや〜,いえ,いやじゃないけど.このスレッド役に立ちますね.


[11116] Re8:コンポーネントの保存返信 削除
2017/4/17 (月) 15:42:16 Mr.XRAY
> そうか, ObjectBinaryToText なんてのを忘れていました.

サイト内で一か所だけですが,使っていました.
保存はバイナリですが.

[130] [03_TMemoryStream を使用したコンポーネントの保存と読み出し]
http://mrxray.on.coocan.jp/Delphi/plSamples/130_SaveComponent.htm#03


[11117] Re7:コンポーネントの保存返信 削除
2017/4/17 (月) 18:18:32 Mr.XRAY
▼ takiさん

> 結果は形状(セルの幅とか高さ)、OptionsなどはText化されますが、
> セル内のデータとイベントは保存されないようです。


ですね.保存されるのは published なプロパティだけですから.

というわけで,またまた人様のフンドシでなのですが,
記事を追加されていただきました.スミマセンね.
でも,きっと他の方にも役に立つと思いますのでご容赦願います.

[130] [04_コンポーネントをテキスト形式で保存]
http://mrxray.on.coocan.jp/Delphi/plSamples/130_SaveComponent.htm#04


[11118] Re:コンポーネントの保存返信 削除
2017/4/19 (水) 09:05:35 おかぽん
▼ Mr.XRAYさん

Published でないプロパティの保存と読み込み
http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Published_%E3%81%A7%E3%81%AA%E3%81%84%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E4%BF%9D%E5%AD%98%E3%81%A8%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF

やりたいことは、こういうことでは・・・ない・・・?


MemoryStream.WriteComponentで、書き出しされているところまでは確認しました。

type
  TStringGrid=class(Grids.TStringGrid)
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure ReadValues(Reader : TReader);
    procedure WriteValues(Writer : TWriter);
  end;

procedure TStringGrid.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('CellString', ReadValues, WriteValues, True);
end;
procedure TStringGrid.ReadValues(Reader : TReader);
begin
  Reader.ReadListBegin;
  Cells[1,1] := Reader.ReadString;
  Cells[1,2] := Reader.ReadString;
  Reader.ReadListEnd;
end;
procedure TStringGrid.WriteValues(Writer : TWriter);
begin
  Writer.WriteListBegin;
  Writer.WriteString(Cells[1,1]);
  Writer.WriteString(Cells[1,2]);
  Writer.WriteListEnd;
end;



[11119] Re2:コンポーネントの保存返信 削除
2017/4/19 (水) 10:34:37 Mr.XRAY
▼ おかぽんさん.どうもです.

> Published でないプロパティの保存と読み込み
> http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Published_%E3%81%A7%E3%81%AA%E3%81%84%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E4%BF%9D%E5%AD%98%E3%81%A8%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF
>
> やりたいことは、こういうことでは・・・ない・・・?


おおっ,TWriter, TReader ですね.
↓ には
『published 以外のプロパティの保存には,特別な方法が必要です
(拙作のコンポーネントでは使用しています).』
としか書いてないんです.

[130] [01_オブジェクトの保存と読み出し]
http://mrxray.on.coocan.jp/Delphi/plSamples/130_SaveComponent.htm#01

Halbow 資料館には TWriter, TReader 使ったコードがあるんですが,
折角サンプルコードを頂いたので,これも掲載しちゃおうかな.
タイトルは「published 以外のプロパティの保存方法」とか...


[▼次のスレッド]
INCM/CMT
Cyclamen v3.82