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;
> メッセージを送れば
これが通用するのは、ウインドウハンドルを持っているコンポーネント・・・
基本的に、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
ここの最後のレス参照。
アクセス違反に悩まされて挫折する前に、基本たたき込むべし。
たしかに少し伝わりにくい質問ですね。
オレ負け犬デス さんの言うとおり
> これが通用するのは、ウインドウハンドルを持っているコンポーネント・・・
> 基本的に、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でオブジェクト指向を学ぶともっと素直に理解できるのに...
返答ありがとうございます。
>>(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一本なんです・・・
・・・そうっすかー、、、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という仕組みを使って実現できているはずです。
調べてみてください。
長文すいません。
こちらも長文失礼。
> そこで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していないという、基本的なとこでしょうね。
> (宣言等細かいコトは省略)
と明示しているのだから、そのまま使えるとはどこにも書いていないし。
だからクラスと通常の変数をきっちり分けて理解しておかないと、
エラーになった原因がわからなくなること請け合いです。
スレ最初にあった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;
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;
https://www.petitmonte.com/bbs/answers?question_id=2552
過去ログですが、これは参考になりませんか
みなさんありがとうございます。
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見たいに複数ある場合正常に処理しないですね。
> 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も一つの解決策でしょうね。
個人的には、メッセージは処理の流れが見えない分、不具合が出たときに解決が
難しくなりがちなので、きっちり設計し、保守しやすくしておきたいです。
三日経てば、自分のソースも他人のモノみたいに見える自分が言うのもアレですが。。。
頂いたものを少しいじっただけですが動くようになりました。
ありがとうございます。
色々わからないところがあり、調べるのに時間がかかってしまいました。
回答が遅くなり申し訳ありません。
> >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.
ツイート | ![]() |