複数データ対象のDDEのOnChangeでの動作がうまくいきません。


おも  2005-12-29 21:22:55  No: 19434

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;

なぜこのようなことが起こるのでしょうか。回避する方法はありますでしょうか。
良いアドバイスがあれば、よろしくお願いします。

なお、上記の動作では、サーバー側→クライアント側の順番で起動する必要があり、サーバー側を再起動すると、クライアント側もその後に再起動が必要になります。


やまね  2006-03-14 06:46:14  No: 19435

自分も同じような現象で悩んでいます。(−−;
無理やりやるならタイマーを使って、こちらからリクエストすることで
変化を読み取れると思います。
でもそれではせっかく無駄なことをせずにChangeイベントで
処理できるはずなのに・・・・と悔しくおもってます。
ちなみに自分はDELPHI5、XPの環境です。


やまね  2006-03-14 09:46:10  No: 19436

自分と同じ悩みを持つ人がいると知って、再度調べてみました。
どうやら相手が早い(頻繁に更新される)と、1つしか
認識しない、仕様?のようです。
頻繁にといっても数秒間隔あっても駄目みたいです。
つかえねー。


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

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






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