Delphi6 Personal、Win2000の環境です。
DDEを使用して、複数のデータを取り込もうとしています。その際、サーバー側のデータが変わった場合に自動的にクライアント側のデータの表示も変更されるようにOnChangeイベントを利用しました。
いろいろ試行錯誤した経緯はあるのですが、自分のしたいことが実現できない根本的な原因の現象を以下の通り把握しました。
テスト用のDDEサーバーとして、3つのアイテムを持ち、タイマーで自動的に乱数を発生させて数字を変化させるものを用意しました。
ddeserver.exeとして作成
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DdeMan, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Topic1: TDdeServerConv;
item1: TDdeServerItem;
item2: TDdeServerItem;
item3: TDdeServerItem;
Timer1: TTimer;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure Timer1Timer(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Timer1Timer(Sender: TObject);
begin
item1.Text:=IntToStr(Random(100));
Label1.Caption:=item1.Text;
item2.Text:=IntToStr(Random(100));
Label2.Caption:=item2.Text;
item3.Text:=IntToStr(Random(100));
Label3.Caption:=item3.Text;
end;
end.
これに対して、クライアント側として、以下のようなものを作成しました。こちらでは、DDEに関するコンポーネントは動的に作成しています。OnChangeイベントとしては、どのデータが変化しても、変化したデータだけではなく、そのときの3つ、すべてのデータをラベルに表示させるようにしています。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, DdeMan, StdCtrls,Contnrs, ExtCtrls,Menus;
type
//動的に生成するTDdeClientItemのItem
TDdeClientItemItem = class (TDdeClientItem);
//TDdeClientItemを管理するクラス
//TComponentListはTListから派生したクラスで,便利な機能がある
TDdeClientItemList = class(TComponentList);
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Memo1: TMemo;
MainMenu1: TMainMenu;
Exit1: TMenuItem;
Exit2: TMenuItem;
procedure FormShow(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure DdeClientItemChange(Sender: TObject);
procedure Exit2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
ADdeClientItemList : TDdeClientItemList;
DdeClientItem:TDdeClientItem;
DdeClientConv:TDdeClientConv;
end;
var
Form1: TForm1;
aryDdeItem :array [0..2] of String=
('item1','item2','item3');
implementation
{$R *.dfm}
var
Changing:Integer;
//DDeClientConv,DdeClietnItemListの作成
procedure TForm1.FormShow(Sender: TObject);
var
i:Integer;
lbl:TComponent;
begin
Changing:=0;
DdeClientConv:=TDdeClientConv.Create(self);
//PanelItemを管理するTComponentListを作成
//生成Itemオブジェクトを自動破棄するには引数をTureにする
ADdeClientItemList:=TDdeClientItemList.Create(True);
DDeClientConv.ServiceApplication:='ddeserver';
DDeClientConv.ConnectMode:=ddeManual;
DdeClientConv.SetLink('ddeserver','topic1');
DdeClientConv.OpenLink;
for i:=0 to 2 do begin
DdeClientItem:=TDdeClientItemItem.Create(self);
DdeClientItem.DdeConv:=DdeClientConv;
DdeClientItem.DdeItem:=aryDdeItem[i];
DdeClientItem.OnChange:=DdeClientItemChange;
ADdeClientItemList.Add(DdeClientItem);
lbl:=FindComponent('Label'+IntToStr(i+1));
if (DdeClientItem.Text<>' ') and (DdeClientItem.Text<>'') then begin
(lbl as TLabel).Caption:=DdeClientItem.Text;
end else begin
(lbl as TLabel).Caption:='';
end;
end;
end;
//変化した場合にLabel表示に反映
procedure TForm1.DdeClientItemChange(Sender: TObject);
var
i:Integer;
lbl:TComponent;
begin
if Changing=1 then Exit;
Changing:=1;
//変化した部分だけの更新にしようと思うのだが、頻繁に変化する部分ばかりの動きで
//まったく反映されない部分もある。なので、どの部分が変化しても、すべてのLabelを
//一括して更新することにしてみる。
for i:=0 to 2 do begin
lbl:=FindComponent('Label'+IntToStr(i+1));
if ((ADdeClientItemList[i] as TDdeClientItem).Text<>' ') and ((ADdeClientItemList[i] as TDdeClientItem).Text<>' ') then begin
(lbl as TLabel).Caption:=(ADdeClientItemList[i] as TDdeClientItem).Text;
//状況確認のため
Memo1.lines.add(IntToStr(i)+(ADdeClientItemList[i] as TDdeClientItem).Text);
end else begin
(lbl as TLabel).Caption:='';
end;
end;
Changing:=0;
end;
//Formがなくなる前にADdeClientItemListを解放.
procedure TForm1.FormDestroy(Sender: TObject);
begin
//DdeClientConv.Freeの実行によりDdeClientItemの
//onChangeイベントが発生し、そちらが一瞬実行されエラーとなるのを回避
Changing:=1;
//順番重要 -逆だとエラー-
DdeClientConv.Free;
FreeandNil(ADdeClientItemList);
end;
procedure TForm1.Exit2Click(Sender: TObject);
begin
Close;
end;
end.
で、この両方を起動してみると、私の環境では、クライアント側での動的作成時のサーバー側の数字が、とりあえず入りますが、その後のサーバー側でのタイマーによる変化では、3つあるうちの最初のデータ(item1)に対応するラベルしかクライアント側では変化しません。
これは、例えば、サーバー側のTimer1Timerで、乱数を発生させる順番を以下のように、item3で最初に発生させるようにすると、今度は、クライアント側ではitem3しか変化しないようになります。
procedure TForm1.Timer1Timer(Sender: TObject);
begin
item3.Text:=IntToStr(Random(100));
Label3.Caption:=item3.Text;
item2.Text:=IntToStr(Random(100));
Label2.Caption:=item2.Text;
item1.Text:=IntToStr(Random(100));
Label1.Caption:=item1.Text;
end;
なぜこのようなことが起こるのでしょうか。回避する方法はありますでしょうか。
良いアドバイスがあれば、よろしくお願いします。
なお、上記の動作では、サーバー側→クライアント側の順番で起動する必要があり、サーバー側を再起動すると、クライアント側もその後に再起動が必要になります。
自分も同じような現象で悩んでいます。(−−;
無理やりやるならタイマーを使って、こちらからリクエストすることで
変化を読み取れると思います。
でもそれではせっかく無駄なことをせずにChangeイベントで
処理できるはずなのに・・・・と悔しくおもってます。
ちなみに自分はDELPHI5、XPの環境です。
自分と同じ悩みを持つ人がいると知って、再度調べてみました。
どうやら相手が早い(頻繁に更新される)と、1つしか
認識しない、仕様?のようです。
頻繁にといっても数秒間隔あっても駄目みたいです。
つかえねー。
ツイート | ![]() |