他のオブジェクトの変更を知りたい

解決


あくあ  2006-06-22 06:40:50  No: 22247

TQuery - TDataSource - TDBGrid のように
TQueryが変更になるとTDBGridの表示が更新されるみたいな
コンポーネントが作りたいのですが
やり方がわかりません。
どのように作ればよいか教えてください。

自分なりに考えてメッセージを送ればよいのではと思い
サンプルをつくって見ましたがうまくいきません。
TQuery -> TSendHoge , TDBGrid -> TGetHoge
のイメージです。

方向性から間違えているでしょうか?

Const WM_SendMsg = WM_USER + 1234;

type TSendHoge = class(TControl)
  public
    data: String;  //値
    procedure Setdata(AData: String);  //メッセージ送信
end;

type TGetHoge = class(TLabel)
  private
    SendHoge: TSendHoge;
  public
    procedure View;  //値をCaptionに表示
    property hoge: TSendHoge read SendHoge write SendHoge;
  protected
    procedure MessegeGet(var Msg: TMsg); message WM_SendMsg;
end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    SendHoge: TSendHoge;
    GetHoge:  TGetHoge;
  public
    { Public 宣言 }
  protected
    procedure MessegeGet(var Msg: TMsg); message WM_SendMsg;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TSendHoge.Setdata(AData: String);
var
  i: Integer;
begin
  //値を変更
  data := AData;
  //TGetHogeへ更新依頼のつもり
    //反応なし
  PostMessage(HWND_BROADCAST, WM_SendMsg, 0, 0);
    //Form1が反応
  for i:=0 to Application.ComponentCount-1 do
  begin
    (Application.Components[i] as TControl).Perform(WM_SendMsg, 0, 0);
  end;
end;

procedure TGetHoge.View;
begin
  Caption := hoge.data;
end;

procedure TGetHoge.MessegeGet(var Msg: TMsg);
begin
  ShowMessage('GetHoge');
  View;  //Caption更新
end;

procedure TForm1.MessegeGet(var Msg: TMsg);
begin
  ShowMessage('Form1');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SendHoge := TSendHoge.Create(Self);
  GetHoge := TGetHoge.Create(Self);

  SendHoge.data := '1234';

  GetHoge.Parent := Form1;
  GetHoge.hoge := SendHoge;
  GetHoge.View;

end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SendHoge.Free;
  GetHoge.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SendHoge.Setdata('test');
end;


オレ負け犬デス  2006-06-22 07:46:40  No: 22248

> メッセージを送れば
これが通用するのは、ウインドウハンドルを持っているコンポーネント・・・
基本的に、TWinControlから継承されたコンポーネントだけです。

> TQueryが変更になるとTDBGridの表示が更新されるみたいな
> コンポーネントが作りたいのですが
抽象的すぎて意味不明。

(1)プロパティ値が変更になったとき、ソレを検出したいのか、
(2)TQueryを参照しているクラスが、別のTQueryに変更されたときを検出されたのかわからない。

で、ソースを見ると、基本的なところが理解不足と見受けられる。
ここらあたりで、クラス型のプロパティについて学習したほうが望ましいです。
https://www.petitmonte.com/bbs/answers?question_id=4034
https://www.petitmonte.com/bbs/answers?question_id=3939

> property hoge: TSendHoge read SendHoge write SendHoge;
これがいけません。

ちなみに、(1)を実現したいのであれば、以下のようにしたほうがいい。
(宣言等細かいコトは省略)

property hoge: TSendHoge read GetSendHoge write SetSendHoge;

function GetSendHoge:TSendHoge;
begin
  Result := SendHoge;
end;

procedure SetSendHoge(AValue: TSendHoge);
begin
  //通常、このメソッドを継承し、必要なプロパティをコピります。
  //procedure Assign(Source: TPersistent); override;
  SendHoge.Assign(AValue);
  //Assignを継承しない場合は、プロパティ個別に更新
  SendHoge.Data := AValue.Data;
end;

クラスは、IntegerやStringのように、単純な代入で中身がコピーされたりしません。

https://www.petitmonte.com/bbs/answers?question_id=4034
ここの最後のレス参照。

アクセス違反に悩まされて挫折する前に、基本たたき込むべし。


Fusa  2006-06-22 10:33:05  No: 22249

たしかに少し伝わりにくい質問ですね。
オレ負け犬デス さんの言うとおり
> これが通用するのは、ウインドウハンドルを持っているコンポーネント・・・
> 基本的に、TWinControlから継承されたコンポーネントだけです。

ですし、クラスの連携に
メッセージを使うなんてことは全く必要ないのですが

> (1)プロパティ値が変更になったとき、ソレを検出したいのか、
たぶん、これなのかな。

お望みの機能を実現する簡単なコードを書くために
TGetHogeにTSendHogeを所有させるのではなく
TSendHogeにTGetHogeを所有させてみました。
どうぞ。

type
  TGetHoge = class;

  TSendHoge = class(TControl)
  public
    FData: String;
    FGetHoge: TGetHoge;
    constructor Create;
    procedure Setdata(AData: String);  
    property Gethoge: TGetHoge read FGetHoge write FGetHoge;
  end;

  TGetHoge = class(TLabel)
  public
    procedure View(ACaption: String);  //値をCaptionに表示
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    SendHoge: TSendHoge;
    GetHoge:  TGetHoge;
  public
  end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

constructor TSendHoge.Create;
begin
  FGetHoge := nil;
end;

procedure TSendHoge.Setdata(AData: String);
var
  i: Integer;
begin
  //値を変更
  FData := AData;

  if Assigned(FGetHoge) then
  begin
    FGetHoge.View(FData);
  end;
end;

procedure TGetHoge.View(ACaption: String);
begin
  Self.Caption := ACaption;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SendHoge := TSendHoge.Create();
  GetHoge := TGetHoge.Create(Self);
  GetHoge.Parent := Form1;

  SendHoge.Gethoge := GetHoge;
  SendHoge.Setdata('1234');
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SendHoge.Free;
  GetHoge.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SendHoge.Setdata('test');
end;

余談ですが
なんでメッセージ投げて情報伝達しようとか
互いの所有部分のコードが逆だったりするのか理由がさっぱり。
VC++あたりで勉強したんでしょうか...

Delphiでオブジェクト指向を学ぶともっと素直に理解できるのに...


あくあ  2006-06-23 04:21:10  No: 22250

返答ありがとうございます。

>>(1)プロパティ値が変更になったとき、ソレを検出したいのか
これがやりたいことです。

データを管理しているクラス(TSendHoge)があり、そのデータを表示するクラス(TGetHoge)を作る。
TSendHogeで値の変更があれば、TGetHogeではそれを検知して表示を更新する。

たとえば、生徒の成績データを管理しているTSendHogeがあれば
グラフを表示するTGetHogeであったり
個人別のデータを表示するTGetHogeであったり
TSendHoge(1)-(*)TGetHogeの関係のクラスを作成したいと思っていました。

提示していただいたサンプルではTSendHoge側でTGetHogeを管理しないといけないため、
TGetHogeのようなものを追加するたびにTSendHogeを修正しなくてはいけないと思いました。

TSendHoge側はデータの管理のみで、表示については考えないようにはできないか
そこでTQueryを考えるとTDBGridで表示している内容をTQueryでは管理していないので
何らかの方法でTQueryの更新をTDBGridで検知している考えました。
で、その方法がメッセージを送っているのではないかと考え質問しました。

データの管理と表示を分けたクラスを作りたいのですが
どこら辺から間違っているのでしょうか(始めから?)
正直、メッセージで処理するという考えも、オブジェクト間の通信はメッセージというのを
どこかで見たから思いついただけですし、メッセージを使ったのも始めてです。

>>procedure SetSendHoge(AValue: TSendHoge);
>>begin
>>  //通常、このメソッドを継承し、必要なプロパティをコピります。
>>  //procedure Assign(Source: TPersistent); override;
>>  SendHoge.Assign(AValue);
>>  //Assignを継承しない場合は、プロパティ個別に更新
>>  SendHoge.Data := AValue.Data;
>>end;

ここの部分でエラーが出てうまくいきませんでした。
このメソッドを継承し、必要なプロパティをコピります。
は何を指すのでしょうか

>>Delphiでオブジェクト指向を学ぶともっと素直に理解できるのに...
じつは、Delphi一本なんです・・・


Fusa  2006-06-23 06:19:02  No: 22251

・・・そうっすかー、、、Delphi一本でしたか・・・

まずは提案から。
普通、所有している側から
通知するようにした方がいいとは思いますが、

TSendHoge側でTListやTObjectListを使ったパラメータを用意しておいて
それに対して、TGetHogeを追加してやってはどうでしょうか?
…伝わるでしょうか?

Gethogeがいくつあっても

SendHoge1.Add(GetHoge1);
SendHoge1.Add(GetHoge2);

として、所有クラスを沢山もたせておくようにして

procedure TSendHoge.Add(Value: TGetHoge);
begin
  FGetHogeList.Add(Value);
  Value.ConnectionSendHoge := Self;
end;

とでもしておいて、

>TSendHogeで値の変更があれば、TGetHogeではそれを検知して表示を更新する。
という場合には、SendHoge側でタイミングは関知できるので
そのときにViewメソッドを呼び出して
procedure TSendHoge.View;
begin
  for ループ
    TGetHoge(FGetHogeList.Item[i]).View;
end;

で、
procedure TGetHoge.View;
begin
  Caption := Self.ConnectionSendHoge.Data;
end;

とするといいのではないでしょうか?

> 提示していただいたサンプルではTSendHoge側でTGetHogeを管理しないといけないため、
> TGetHogeのようなものを追加するたびにTSendHogeを修正しなくてはいけないと思いました。

なるほど、だから
TQueryとTDBGridか...
まあ、VCLでもその仲介にTDataSourceを入れてるぐらいですから
あんまし簡単じゃないと思うのですが、
VCLを内部まで読んでませんが、たぶんTQueryはリンクしているTDataSourceに
値を放り投げるだけで、(ここまでは私のサンプルソースと似たような仕組み)
TDataSourceがTDBGridやTDBEditに値を渡している仕組みが
あくあさんの知りたい事のようですね。

TDataSource側(DataSource1とする)から
Form上やアプリ上に存在するコンポーネントを全てサーチして
(Form.Componentsプロパティで調べる方法があります)
その中にTDBHogeなクラスがあり、TDBHoge.DataSourceがDataSource1なら
更新処理を行う、、、とかやってるんじゃないかしら?
...誰か詳しく知ってたら教えてください。

"DBGrid等をFormに配置する"という時点で、
他のクラスから参照できる仕組みがあり
上記の『SendHoge1.Add(GetHoge1);』と同じ事を
やっているのと同じなので
VCLが便利な事ができると思ってしまいますが

> データを管理しているクラス(TSendHoge)があり、そのデータを表示するクラス(TGetHoge)を作る。
> TSendHogeで値の変更があれば、TGetHogeではそれを検知して表示を更新する。
というのは、どうやってもTSendHoge側で、どこにTGetHogeがあるのか、
を関知する仕組みが必要ですね。

…って書いてきて、だいぶ私も整理できてきましたが、
あくあさんの最初の投稿の
procedure TSendHoge.Setdata(AData: String);
…省略…
  for i:=0 to Application.ComponentCount-1 do
  begin
    (Application.Components[i] as TControl).Perform(WM_SendMsg, 0, 0);
  end;
と、書いてありますね。

ここでメッセージを投げるのではなくて
  Application.Components[i]  に
    hogeプロパティがあるかどうかを調べて
  その場合には、hogeプロパティがTSendHogeであるSelfと一致するかどうか調べて
  一致すれば
  TGetHoge(Application.Components[i]).Viewを呼び出す
とすればいいんじゃないでしょうか?

あるクラスにあるプロパティが存在するかどうか
ということは、DelphiはRTTIという仕組みを使って実現できているはずです。
調べてみてください。

長文すいません。


オレ負け犬デス  2006-06-23 10:26:02  No: 22252

こちらも長文失礼。

> そこでTQueryを考えるとTDBGridで表示している内容をTQueryでは管理していないので
> 何らかの方法でTQueryの更新をTDBGridで検知している考えました。

うろ覚えですが、TDataLink、TFieldDataLinkあたりのあまり表に出てこないクラスが、
各列のTFieldをリストだったか動的配列だったかで保持していて、イベントやら
なにやらで通知を受け取っています。

お互いを取り持つ、中間管理職クラスががんばっておるのですよ。

> データを管理しているクラス(TSendHoge)があり、そのデータを表示するクラス
(TGetHoge)を作る。
> TSendHogeで値の変更があれば、TGetHogeではそれを検知して表示を更新する。
> (略)
> TSendHoge(1)-(*)TGetHogeの関係のクラスを作成したいと思っていました。

こうなると、TSendHogeとTGetHogeは、お互い結びつきが強いクラス関係ですね。

> 提示していただいたサンプルではTSendHoge側でTGetHogeを管理しないといけないため、
> TGetHogeのようなものを追加するたびにTSendHogeを修正しなくてはいけないと思いました。
オブジェクト指向で設計することで、このようなことはほとんどなくなると思います。

Fusaさんのレスにもありますが、TSendHoge側でTListやTObjectListにより、複数の
TGetHogeを管理できるようにしておきます。

共通の親クラスTGetHogeから、
(1)グラフを表示するTGetHogeA
(2)個人別のデータを表示するTGetHogeB
を派生

TGetHoge.Update; virtual; abstract;
TGetHogeA.Update; override;
TGetHogeB.Update; override;

仮に、TListを使ってTGetHogeAやTGetHogeBを管理するとしても、
TGetHoge(List[i]).Update; と記述するだけで、派生したクラスがどんな動作をする
クラスであっても、
その正しい動作をしてくれます。
TSendHogeが、TGetHogeAやTGetHogeBの中身を知る必要はありません。

(1)では、グラフの表示を変更するような動作を行い、
(2)では、データ表示しているGridやEditの内容を更新する動作を行う。

TQueryやTDBGridのような、中間管理職クラスをつくったとしても、変更を通知するため、
中間管理職クラスが、TGetHogeAやTGetHogeBを管理する必要があるでしょう。
TSendHoge(1)-(*)TGetHogeの関係のクラスである以上、誰か1のTSendHogeの変更を検出し
多のTGetHogeへ通知する必要があるんですから。
それなら、「検出する」ことと「通知する」ことは、TSendHogeにまかせてしまったほうが
楽でしょう?

> >>procedure SetSendHoge(AValue: TSendHoge);
> >>begin
> >>  //通常、このメソッドを継承し、必要なプロパティをコピります。
> >>  //procedure Assign(Source: TPersistent); override;
> >>  SendHoge.Assign(AValue);
> >>  //Assignを継承しない場合は、プロパティ個別に更新
> >>  SendHoge.Data := AValue.Data;
> >>end;

> ここの部分でエラーが出てうまくいきませんでした。
> このメソッドを継承し、必要なプロパティをコピります。
> は何を指すのでしょうか

くどくど説明してもあかんから、サンプル
自分が持っている変数をコピーすることね。

TSendHoge=class(TPersistent)  //class(TObject)やclassのみでなければ、なんでもよし
protected
  FData: String;
public
  procedure Assign(Source: TPersistent); override;
end;

procedure TSendHoge.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  //Source が TSendHogeか、継承先のクラスか判定
  if Source is TSendHoge then begin
    //TSendHogeが持っている情報のみを複写
    FData := TSendHoge(Source).FData;
  end;
end;

TSendHogeNext=class(TSendHoge)
protected
  FDataOne: Integer;
  FDataTwo: Boolean;
public
  procedure Assign(Source: TPersistent); override;
end;

procedure TSendHogeNext.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  //Source が TSendHogeNextか、継承先のクラスか判定
  if Source is TSendHogeNext then begin
    //TSendHogeNextが持っている情報のみを複写
    FDataOne := TSendHogeNext(Source).FDataOne;
    FDataTwo := TSendHogeNext(Source).FDataTwo;
  end;
end;

こんなのを用意しておけば、Assignメソッド一発で済むって話。

procedure T〜〜.Copy(aXXX: TSendHoge);
var
  sendHoge: TSendHoge;
begin

  sendHoge := aXXX;  //NG
  //コトある毎に、値を複写するためのコードを書かないといけない。
  semdHoge.FData := aXXX.FData;

end;

ちなみに、エラーになったのは、「SendHoge」のインスタンスを
Createしていないという、基本的なとこでしょうね。
> (宣言等細かいコトは省略)
と明示しているのだから、そのまま使えるとはどこにも書いていないし。

だからクラスと通常の変数をきっちり分けて理解しておかないと、
エラーになった原因がわからなくなること請け合いです。


ん?  2006-06-23 17:31:46  No: 22253

スレ最初にあったTGetHogeで、TSendHogeを保持する(部分のみ)例
ようするに、参照するのみの変数を確保する。
当然、TSendHogeの変更は検知できません。

ま、参考程度にどうぞ。

type
  TGetHoge = class(TLabel)
  private
    FSendHoge: TSendHoge;
  protected
    //コンポーネントの生成・破棄の通知を受け取る
    procedure Notification(AComponent: TComponent;
      Operation: TOperation); override;
    procedure SetSendHoge(aSendHoge: TSendHoge);
  public
    property Hoge: TSendHoge read FSendHoge write SetSendHoge;
  end;

procedure TGetHoge.Notification(AComponent: TComponent;
                            Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation = opRemove then begin
  //コンポーネントが破棄される
  //TComponentから継承されたクラスでないと発生しませんし、
  //「THoge.Create(nil)」のように親を指定していない場合、発生しない(はず)
    if FSendHoge = AComponent then begin
      //参照解除しておかないと、存在しないインスタンスにアクセスし、
      //アクセス違反になります。
      FSendHoge := nil;
    end;
  end
  else begin
  //コンポーネントが生成される
    //あまり使ったことないです
  end;
end;
procedure TGetHoge.SetSendHoge(aSendHoge: TSendHoge);
begin
  //これだけの場合
  //property (略) write FSendHoge; で記述してもOK
  //メソッドを用意する必要がないので
  FSendHoge := aSendHoge;
  //SendHoge が変更されることにより、表示が変わったりする場合
  //このタイミングで更新するといい。
end;


ん?  2006-06-23 18:30:16  No: 22254

TSendHogeで管理するクラスの例
Fusaさんや、オレ負け犬デス さんのレスを参考に・・・
とりあえずコンパイルは通りますが、実用的にするには、改善が必要

type
  //forward宣言
  TSendHoge=class;

  TGetHoge = class(TLabel)
  private
    FSendHoge: TSendHoge;  //管理者
  public
    constructor Create(aOwner: TComponent;
       aOwnerSendHoge: TSendHoge); reintroduce; virtual;

    destructor Destroy; override;

    //これがミソですか?
    procedure Assign(aPersistent: TPersistent); override;

    //実装は下位クラスで
    procedure View; virtual; abstract;

  protected
    //TSendHogeが変更されたときに呼び出される
    //通知受け取りメソッド
    procedure UpdateSendHoge; virtual;

  public
    //外部からは、基本的には参照のみでしょう
    property Sendhoge: TSendHoge read FSendHoge;
  end;

  TSendHoge = class(TControl)
  private
    FHogeList: TList;

  protected
    FData: String;  //値

  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;

  protected
    //TGetHogeの追加削除
    function AddGetHoge(aGetHoge: TGetHoge): Integer;
    function RemoveGetHoge(aGetHoge: TGetHoge): Boolean;

    //更新通知
    procedure UpdateData;

    //管理アイテム
    function GetGetHoge(Index: Integer): TGetHoge;
    procedure SetGetHoge(Index: Integer; aHoge: TGetHoge);

    //プロパティ変更
    procedure SetData(aData: String);

  public
    property Data: String read FData write SetData;
    property Items[Index: Integer]: TGetHoge read GetGetHoge write SetGetHoge;
  end;

//implementation 部

{ TGetHoge }
constructor TGetHoge.Create(aOwner: TComponent; aOwnerSendHoge: TSendHoge);
begin
  inherited Create(aOwner);
  FSendHoge := aOwnerSendHoge;
  //自身のインスタンスをオーナーTSendHogeに登録
  FSendHoge.AddGetHoge(Self);
  //表示も更新するなら、ここでやるべきかも
  //View;
end;

destructor TGetHoge.Destroy;
begin
  //親から参照を解除する
  if FSendHoge <> nil then FSendHoge.RemoveGetHoge(Self);
  inherited Destroy;
end;

//これがミソですか?
procedure TGetHoge.Assign(aPersistent: TPersistent);
begin
  inherited Assign(aPersistent);
  if aPersistent is  TGetHoge then begin
    //特になしだから、メソッドもいらないですね(^^ゞ
  end;
end;

//TSendHogeが変更されたときに呼び出される
//通知受け取りメソッド
procedure TGetHoge.UpdateSendHoge;
begin
  //表示更新のみであれば、これだけでいいのでしょう
  View;
end;

{ TSendHoge }
constructor TSendHoge.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  FHogeList := TList.Create;
end;
destructor TSendHoge.Destroy;
begin
  FreeAndNil(FHogeList);
  inherited Destroy;
end;

//TGetHogeの追加削除
function TSendHoge.AddGetHoge(aGetHoge: TGetHoge): Integer;
begin
  //同一インスタンスの複数追加は行わない
  Result := FHogeList.IndexOf(aGetHoge);
  if Result < 0 then begin
    FHogeList.Add(aGetHoge);
    Result := FHogeList.Count -1;
  end;
end;
function TSendHoge.RemoveGetHoge(aGetHoge: TGetHoge): Boolean;
var
  iii: Integer;
begin
  iii := FHogeList.IndexOf(aGetHoge);
  if iii >= 0 then begin
    FHogeList.Delete(iii);
    Result := True;
  end
  else begin
    Result := False;
  end;
end;

//更新通知
procedure TSendHoge.UpdateData;
var
  iii: Integer;
begin
  for iii := 0 to FHogeList.Count -1 do begin
    TGetHoge(FHogeList[iii]).UpdateSendHoge;
  end;
end;

//管理アイテム
function TSendHoge.GetGetHoge(Index: Integer): TGetHoge;
begin
  //Index外の例外発生がいやなら、ここでチェックする
  Result := TGetHoge(FHogeList[Index]);
end;
procedure TSendHoge.SetGetHoge(Index: Integer; aHoge: TGetHoge);
var
  getHoge: TGetHoge;
begin
  //エラーチェックは必須ですよ
  getHoge := GetGetHoge(Index);
  getHoge.Assign(aHoge);
end;

//プロパティ変更
procedure TSendHoge.SetData(aData: String);
begin
  if FData <> aData then begin
    {変数値更新}
    FData := aData;
    {更新通知}
    UpdateData;
  end;
end;


nnn  2006-06-23 19:22:38  No: 22255

https://www.petitmonte.com/bbs/answers?question_id=2552
過去ログですが、これは参考になりませんか


あくあ  2006-06-24 12:07:19  No: 22256

みなさんありがとうございます。
TList等で考えて見たいと思います。
情報量が多くてまだ読み解いておりません。
報告については、また後日させていただきます。

ただ、すっきりしないところがあります。
Form1
 - SendHoge1
 - GetHoge1
Form2
 - GetHoge2
があったとすると、From2.pasはForm1.pasに対してusesするのはわかります。
TSendHogeで表示を管理する場合、Form1.pasはForm2.pasをusesしなくてはいけません。
TQueryなどの場合Form1->Form2の参照はありません。

TSendHogeで表示を管理する場合どうしてもTGetHogeが増えるたびにTSendHogeの独立性が失われていきます。
データと表示を分離したかったのでイメージしたものと違いました。

>>基本的に、TWinControlから継承されたコンポーネントだけです。
というのを見て昨日Helpを見ていてTWinControl.Broadcastを見つけました。
  (Application.Components[i] as TControl).Perform(WM_SendMsg, 0, 0);

  Msg :TMessege;
  Msg.Msg = WM_SendMsg;
  (Application.Components[i] as TWinControl).Broadcast(Msg);
見たいに変更したらTGetHogeで変更を検知できました。(Delphiが今使えないので間違っているかもしれません)
けど、SendHoge1,SendHoge2見たいに複数ある場合正常に処理しないですね。


勝ち組  2006-06-24 20:41:08  No: 22257

> TSendHogeで表示を管理する場合どうしてもTGetHogeが増えるたびにTSendHogeの独立性が失われていきます。
> データと表示を分離したかったのでイメージしたものと違いました。

あまりに読み飛ばしすぎ。

TSendHogeは、TGetHogeを実態を知らなくても動作するサンプルが書いてある。
これこそオブジェクト指向の真骨頂。

<Unit1>
お互いに密接に関係する、TSendHogeとTGetHoge
TGetHogeは、抽象クラス。

<Unit2>
uses Unit1
TGetHogeを継承した実際の動作を記述するTGetHogeAとTGetHogeB

Unit1のTSendHogeは、TGetHogeAとTGetHogeBを知る必要はない書き方ができる。
まさに表示とデータの分離に他ならないし、独立性も高い。
しかもTSendHogeは、表示管理はしていない。
表示機能を持ったTGetHogeを管理しているだけ。
したがって、Unit1は、Unit2をusesする必要はない。

> ただ、すっきりしないところがあります。
> Form1
>  - SendHoge1
>  - GetHoge1
> Form2
>  - GetHoge2
> があったとすると、From2.pasはForm1.pasに対してusesするのはわかります。
> TSendHogeで表示を管理する場合、Form1.pasはForm2.pasをusesしなくてはいけません。
> TQueryなどの場合Form1->Form2の参照はありません。
TQueryはDBTablesですから、必要ありません。

「SendHoge1が、GetHoge2を知っている」言い換えるなら
「SendHoge1が、GetHoge2のときに条件分岐する記述がある」から、
「Form1.pasはForm2.pasをuses」なのです。

> というのを見て昨日Helpを見ていてTWinControl.Broadcastを見つけました。
>   (Application.Components[i] as TControl).Perform(WM_SendMsg, 0, 0);
> を
>   Msg :TMessege;
>   Msg.Msg = WM_SendMsg;
>   (Application.Components[i] as TWinControl).Broadcast(Msg);
> 見たいに変更したらTGetHogeで変更を検知できました。(Delphiが今使えないので間違っているかもしれません)

あながち間違いではありませんが、Application.Components[i] = TComponent であり、
必ずしも TWinControlではありません。

ちなみに、Performは、指定したコントロールに送るものであり、その配下の
コントロールにはメッセージを送りません。
なお、Application.Components には、メインフォームと、自動生成される
フォームしかなく、フォーム上にあるコンポーネントは含まれていません。

逆に、Broadcastは、Broadcastした相手の配下のコンポーネント全てにも
メッセージを送ります。
Application.Componentsにメインフォームが含まれていることから、
メインフォームの全てのコンポーネントにメッセージが通知されるのです。

また、Windows を介したメッセージ処理ではないので、TWinControlのような
ウインドウハンドルを保持していないコントロール(TLabel等)でもメッセージ
処理可能なわけです。
TPaintBoxとかTImageとかで、MouseMoveイベントが発生するのも、メッセージ
処理機構をTControlの段階で実装してあるからでしょう。

SendMessage を使うのは、Windowsを介したメッセージ処理であるため、
ウインドウハンドルを持たないコンポーネントは処理してくれません。

ただ、動けばよしということであれば、Broadcastも一つの解決策でしょうね。

個人的には、メッセージは処理の流れが見えない分、不具合が出たときに解決が
難しくなりがちなので、きっちり設計し、保守しやすくしておきたいです。
三日経てば、自分のソースも他人のモノみたいに見える自分が言うのもアレですが。。。


あくあ  2006-07-09 08:53:35  No: 22258

頂いたものを少しいじっただけですが動くようになりました。
ありがとうございます。
色々わからないところがあり、調べるのに時間がかかってしまいました。
回答が遅くなり申し訳ありません。

> >TQueryはDBTablesですから、必要ありません。
なぜ、DBTablesは必要ないのか教えていただけないでしょうか。

unit Unit1;

interface

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

type
  //forward宣言
  TSendHoge=class;

  TGetHoge = class(TLabel)
  private
    FSendHoge: TSendHoge; //管理者
  public
    constructor Create(aOwner: TComponent;
     aOwnerSendHoge: TSendHoge); reintroduce; virtual;

    destructor Destroy; override;

    //これがミソですか?
    procedure Assign(aPersistent: TPersistent); override;

    //実装は下位クラスで
    procedure View; virtual; abstract;

  protected
    //TSendHogeが変更されたときに呼び出される
    //通知受け取りメソッド
    procedure UpdateSendHoge; virtual;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;

  public
    //外部からは、基本的には参照のみでしょう
    property Sendhoge: TSendHoge read FSendHoge;
  end;

  TSendHoge = class(TControl)
  private
    FHogeList: TList;

  protected
    FData: String;  //値

  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;

  protected
    //TGetHogeの追加削除
    function AddGetHoge(aGetHoge: TGetHoge): Integer;
    function RemoveGetHoge(aGetHoge: TGetHoge): Boolean;

    //更新通知
    procedure UpdateData;

    //管理アイテム
    function GetGetHoge(Index: Integer): TGetHoge;
    procedure SetGetHoge(Index: Integer; aHoge: TGetHoge);

    //プロパティ変更
    procedure SetData(aData: String);

  public
    property Data: String read FData write SetData;
    property Items[Index: Integer]: TGetHoge read GetGetHoge write SetGetHoge;
  end;

type TGetHogeA = class(TGetHoge)
  public
    procedure View; override;
end;

type TGetHogeB = class(TGetHoge)
  public
    procedure View; override;
end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FSetHoge: TSendHoge;
    FGetHoge1: TGetHogeA;
    FGetHoge2: TGetHogeB;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TGetHoge }
constructor TGetHoge.Create(aOwner: TComponent; aOwnerSendHoge: TSendHoge);
begin
  inherited Create(aOwner);
  FSendHoge := aOwnerSendHoge;
  //自身のインスタンスをオーナーTSendHogeに登録
  FSendHoge.AddGetHoge(Self);
  //表示も更新するなら、ここでやるべきかも
  View;
end;

destructor TGetHoge.Destroy;
begin
  //親から参照を解除する
  if FSendHoge <> nil then FSendHoge.RemoveGetHoge(Self);
  inherited Destroy;
end;

//これがミソですか?
procedure TGetHoge.Assign(aPersistent: TPersistent);
begin
  inherited Assign(aPersistent);
  if aPersistent is  TGetHoge then begin
    //特になしだから、メソッドもいらないですね(^^ゞ
  end;
end;

//TSendHogeが変更されたときに呼び出される
//通知受け取りメソッド
procedure TGetHoge.UpdateSendHoge;
begin
  //表示更新のみであれば、これだけでいいのでしょう
  View;
end;

procedure TGetHoge.Notification(AComponent: TComponent;
                            Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation = opRemove then begin
  //コンポーネントが破棄される
  //TComponentから継承されたクラスでないと発生しませんし、
  //「THoge.Create(nil)」のように親を指定していない場合、発生しない(はず)
    if FSendHoge = AComponent then begin
      //参照解除しておかないと、存在しないインスタンスにアクセスし、
      //アクセス違反になります。
      FSendHoge := nil;
    end;
  end
  else begin
  //コンポーネントが生成される
    //あまり使ったことないです
  end;
end;

{ TSendHoge }
constructor TSendHoge.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  FHogeList := TList.Create;
end;
destructor TSendHoge.Destroy;
begin
  FreeAndNil(FHogeList);
  inherited Destroy;
end;

//TGetHogeの追加削除
function TSendHoge.AddGetHoge(aGetHoge: TGetHoge): Integer;
begin
  //同一インスタンスの複数追加は行わない
  //IndexOfでAGetHogeの添字取得なければ -1
  Result := FHogeList.IndexOf(aGetHoge);
  if Result < 0 then begin
    FHogeList.Add(aGetHoge);
    Result := FHogeList.Count -1;
    aGetHoge.FreeNotification(Self);
  end;
end;
function TSendHoge.RemoveGetHoge(aGetHoge: TGetHoge): Boolean;
var
  iii: Integer;
begin
  iii := FHogeList.IndexOf(aGetHoge);
  if iii >= 0 then begin
    FHogeList.Delete(iii);
    Result := True;
  end
  else begin
    Result := False;
  end;
end;

//更新通知
procedure TSendHoge.UpdateData;
var
  iii: Integer;
begin
  for iii := 0 to FHogeList.Count -1 do begin
    TGetHoge(FHogeList[iii]).UpdateSendHoge;
  end;
end;

//管理アイテム
function TSendHoge.GetGetHoge(Index: Integer): TGetHoge;
begin
  //Index外の例外発生がいやなら、ここでチェックする
  Result := TGetHoge(FHogeList[Index]);
end;
procedure TSendHoge.SetGetHoge(Index: Integer; aHoge: TGetHoge);
var
  getHoge: TGetHoge;
begin
  //エラーチェックは必須ですよ
  getHoge := GetGetHoge(Index);
  getHoge.Assign(aHoge);
end;

//プロパティ変更
procedure TSendHoge.SetData(aData: String);
begin
  if FData <> aData then begin
    {変数値更新}
    FData := aData;
    {更新通知}
    UpdateData;
  end;
end;

procedure TGetHogeA.View;
begin
  if Assigned(FSendHoge) then
  begin
    Caption := 'A= ' + FSendHoge.Data;
  end;
end;

procedure TGetHogeB.View;
begin
  if Assigned(FSendHoge) then
  begin
    Caption := 'B= ' + FSendHoge.Data;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FSetHoge := TSendHoge.Create(Self);
  FSetHoge.SetData('aa');
  FGetHoge1 := TGetHogeA.Create(Self,FSetHoge);
  FGetHoge2 := TGetHogeB.Create(Self,FSetHoge);
  FGetHoge1.Parent := Self;
  FGetHoge1.Top := 10;
  FGetHoge1.Left := 10;
  FGetHoge2.Parent := Self;
  FGetHoge2.Top := 10;
  FGetHoge2.Left := 100;
  FSetHoge.AddGetHoge(FGetHoge1);
  FSetHoge.AddGetHoge(FGetHoge2);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FGetHoge2.Free;
  FGetHoge1.Free;
  FSetHoge.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FSetHoge.SetData(Edit1.Text);
end;

end.


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

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






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