Main Content

イベントとリスナーを使用するための手法

例の概要

この例は、2 つのクラスを定義します。

  • fcneval — 評価機能をもつクラスは、MATLAB® 式を含み、指定した範囲でこの式を評価します。

  • fcnview — 表示機能をもつクラスは、fcneval オブジェクトを含み、fcneval に含まれるデータを使用して評価した式の表面グラフを表示します。

このクラスは、2 つのイベントを定義します。

  • 新しい値が MATLAB 関数に対して指定されるときに起こる、クラス定義されたイベント

  • 範囲を含むプロパティが変更されるときに発生するプロパティ イベント

次の図は、2 つのオブジェクトの関係を示します。fcnview オブジェクトは、fcneval オブジェクトを含み、このオブジェクトのデータからグラフを作成します。fcnview は、fcneval オブジェクトのデータに変化があると、グラフを変更するために、リスナーを作成します。

Events and listeners defined for fcneval and fcenview

この例で示した手法

  • クラス定義内でのイベントの名前付け

  • notify を呼び出すことによるイベントのトリガー

  • SetObservable 属性によるプロパティ イベントの有効化

  • クラス定義イベントとプロパティ PostSet イベントに対するリスナーの作成

  • 追加の引数を受け取るリスナー コールバック関数の定義付け

  • リスナーの有効化と無効化

fcneval クラスのまとめ

fcneval クラスは、2 変数の指定範囲で MATLAB 式を評価します。fcnevalfcnview クラスのオブジェクトが表面としてグラフ化するデータのソースです。fcneval はこの例で使用されているイベントのソースです。クラス定義のリストは、@fcneval/fcneval.m クラスのコードを参照してください。

プロパティ目的
FofXY関数ハンドルMATLAB 式 (2 変数の関数)
Lm2 要素ベクトル関数が評価される両変数の範囲。SetObservable 属性を true に設定すると、プロパティ イベント リスナーが有効になります。
Datax、y、および z 行列をもつ構造体関数を評価した結果のデータ。表面グラフに使用されます。Dependent 属性が true に設定されると、クエリされたときに保存されたデータがない場合に、get.Data メソッドが呼び出されてプロパティ値を決定します。

イベントトリガーされるとき
UpdateGraphFofXY プロパティ関数 set (set.FofXY) は、このクラスのオブジェクトに上の MATLAB 式に対して新しい値が指定されると、notify メソッドを呼び出します。

メソッド目的
fcnevalクラス コンストラクター。入力は、関数ハンドルと、関数を評価する範囲を指定する 2 要素ベクトルです。
set.FofXYFofXY プロパティの関数 set。オブジェクトの作成中など、プロパティ値の設定時に常に呼び出されます。
set.LmLm プロパティの関数 set。有効な範囲の判定に使用されます。
get.DataData プロパティの関数 get。このメソッドは、クラス メンバーあるいは外部からデータがクエリされると常に Data プロパティの値を計算します。
gridデータの計算に使用される静的メソッド (Static 属性が true)。

fcnview クラスのまとめ

fcnview クラスのオブジェクトは、関数表示で作成される 4 つの表面グラフのデータ ソースとして、fcneval オブジェクトを含んでいます。fcnview は、fcneval オブジェクトに含まれるデータの変更に応答するリスナーとコールバック関数を作成します。クラス定義のリストは、@fcnview/fcnview.m クラスのコードを参照してください。

プロパティ目的
FcnObjectfcneval オブジェクトこのオブジェクトは、関数グラフの作成に使用するデータを含みます。
HAxesAxes のハンドルfcnview オブジェクトの各インスタンスは、サブプロットを含む Axes のハンドルを保存します。
HLUpdateGraphUpdateGraph イベントに対する event.listener オブジェクトevent.listener オブジェクトの Enabled プロパティを true に設定すると、リスナーが有効になり、false にするとリスナーが無効になります。
HLLmLm プロパティ イベントに対する event.listener オブジェクトevent.listener オブジェクトの Enabled プロパティを true に設定すると、リスナーが有効になり、false にするとリスナーが無効になります。
HEnableCmuimenu ハンドルコンテキスト メニュー上にあるリスナーを有効にするための項目 (確認した動作の処理に使用します)。
HDisableCmuimenu ハンドルコンテキスト メニュー上にあるリスナーを無効にするための項目 (確認した動作の管理に使用します)。
HSurfacesurface ハンドル表面データを更新するイベント コールバックが使用します。

メソッド目的
fcnviewクラス コンストラクター。入力は fcneval オブジェクトです。
createLisnaddlistener を呼び出し、UpdateGraph および Lm プロパティの PostSet リスナーを作成します。
limsAxes の範囲を fcneval オブジェクトの Lm プロパティの現在値に設定します。イベント ハンドラーによって使用されます。
updateSurfaceData新しいオブジェクトを作成せずに表面データを更新します。イベント ハンドラーによって使用されます。
listenUpdateGraphUpdateGraph イベントに対するコールバック。
listenLmLm プロパティの PostSet イベントに対するコールバック。
deletefcnview クラスの delete メソッド。
createViews各サブプロットに対して fcnview クラスのインスタンスを作成する静的メソッド。リスナーを有効化/無効化するコンテキスト メニューを定義し、サブプロットを作成します。

ハンドル クラスから継承されるメソッド

fcnevalfcnview クラスは、handle クラスからメソッドを継承します。次の表は、この例で使用された継承されたメソッドの一覧です。

ハンドル クラスのメソッドは、handle クラスをサブクラス化するときに継承される、すべてのメソッドを一覧表示します。

ハンドル クラスから継承されるメソッド目的
addlistener特定のイベントに対してリスナーを登録し、イベントが定義するオブジェクトにリスナーを追加します。
notifyイベントをトリガーし、すべての登録されたリスナーに通知します。

fcneval と fcnview クラスの使用

この節では、クラスの使用方法について説明します。

  • 2 変数関数の MATLAB 式とその関数を評価する範囲を含む、fcneval クラスのインスタンスを作成します。

  • fcnview クラスの静的関数 createViews を使用して、関数を表示します。

  • MATLAB 式または fcneval オブジェクトが含む範囲、および生成されたイベントに応答するすべての fcnview オブジェクトを変更します。

2 つの引数 (無名関数と 2 要素の単調増加するベクトル) と共にそのコンストラクターを呼び出すことによって、fcneval オブジェクトを作成します。以下に例を示します。

feobject = fcneval(@(x,y) x.*exp(-x.^2-y.^2),[-2 2]);

createViews 静的メソッドを使用して、関数のグラフを作成します。クラスの名前を使用して静的関数を呼び出します。

fcnview.createViews(feobject);

createView メソッドは、fcneval オブジェクトに含まれる関数の 4 つの表示を作成します。

Four graphs of the function defined by fcneval

各サブプロットは、そのグラフに関連するリスナーを有効にしたり無効にできる、コンテキスト メニューを定義します。たとえば、サブプロット 221 (左上) 上のリスナーを無効にし、fcneval オブジェクトに含まれている MATLAB 式を変更する場合、UpdateGraph イベントがトリガーされると、残りの 3 つのサブプロットだけが更新されます。

feobject.FofXY = @(x,y) x.*exp(-x.^.5-y.^.5);

Updates to three graphs of the function defined by fcneval

同様に、feobject.Lm プロパティに値を割り当てて範囲を変更する場合、feobjectPostSet プロパティ イベントをトリガーすると、リスナー コールバックがグラフを更新します。

feobject.Lm = [-8 3];

Graphs with updated limits

この図では、サブプロット 221 のコンテキスト メニューが、リスナーを再び有効にしています。PostSet プロパティのイベントのリスナー コールバックは、表面データの更新も行うので、すべての表示が同期します。

UpdateGraph イベントおよびリスナーの実装

UpdateGraph イベントは、fcneval オブジェクトに含まれる数学関数の MATLAB 式が変更されるときに発生します。表面グラフを含む fcnview オブジェクトは、このイベントをリッスンしています。したがって、これらはグラフを更新して新しい関数を表示することができます。

UpdateGraph イベントの定義とトリガー

UpdateGraph イベントは、クラス定義のイベントです。fcneval クラスは、イベントに名前を付け、イベントが発生すると notify を呼び出します。

Diagram of UpdateGraph event and listener

fcnview クラスは、このイベントに対してリスナーを定義します。fcneval がイベントをトリガーすると、fcnview のリスナーは、以下のアクションを実行するコールバック関数を実行します。

  • fcnview オブジェクトによって保存された surface オブジェクトのハンドルがまだ有効であるかどうかを判定します。すなわち、オブジェクトはまだ存在します。

  • fcneval オブジェクトの Data プロパティをクエリすることによって、表面の XDataYDataZData を更新します。

fcneval クラスは、event ブロックでイベントの名前を定義します。

events
   UpdateGraph 
end

イベントのトリガー タイミングの決定

fcneval クラスは FofXY プロパティの set メソッドを定義します。FofXY は、数学関数で使用する MATLAB 式を保存するプロパティです。この式は、2 変数関数の有効な MATLAB 式でなければなりません。

set.FofXY メソッドには、次の機能があります。

  • 式の適性を判別します。

  • 式が適している場合は、次のことを行います。

    • 式を FofXY プロパティに割り当てます。

    • UpdateGraph イベントをトリガーします。

fcneval.isSuitableMException オブジェクトを返さなかった場合は、set.FofXY メソッドは、プロパティに値を割り当てて、UpdateGraph イベントをトリガーします。

function set.FofXY(obj,func)
% Determine if function is suitable to create a surface
   me = fcneval.isSuitable(func);
   if ~isempty(me)
      throw(me)
   end
% Assign property value
   obj.FofXY = func; 
% Trigger UpdateGraph event
   notify(obj,'UpdateGraph');
end

式の適性の判定

set.FofXY メソッドは、指定された式の適性を判別するために、静的メソッド (fcneval.isSuitable) を呼び出します。式が適していない場合は、fcneval.isSuitableMException オブジェクトを返します。fcneval.isSuitable は、ユーザーにとって参考になるエラー メッセージを作成するために、MException コンストラクターを直接呼び出します。

set.FofXY は、throw メソッドを使用して例外を発行します。例外が発行されると、set.FofXY の実行が停止し、メソッドによるプロパティの割り当てや UpdateGraph イベントのトリガーが行われるのを防ぎます。

次に、fcneval.isSuitable メソッドを示します。

function isOk = isSuitable(funcH)
   v = [1 1;1 1];
   % Can the expression except 2 numeric inputs
   try
      funcH(v,v);
   catch
      me = MException('DocExample:fcneval',...
         ['The function ',func2str(funcH),' Is not a suitable F(x,y)']);
      isOk = me;
      return
   end
   % Does the expression return non-scalar data
   if isscalar(funcH(v,v));
      me = MException('DocExample:fcneval',...
         ['The function ',func2str(funcH),'' Returns a scalar when evaluated']);
      isOk = me;
      return
   end
   isOk = [];
end

fcneval.isSuitable メソッドでは、FofXY プロパティに割り当てられた式が、クラスの設計で必要とされている条件を満たしているかどうかをテストすることもできます。

その他の方法

FofXY プロパティの set イベントをクラスに実装することもできます。この場合は、notify を呼び出す必要がなくなります (プロパティ値変更のリスニングを参照)。この場合は、クラスのイベントを定義すると、イベントのトリガーを細かく制御できるようになるので、柔軟性が向上します。

たとえば、新しいデータが前のデータと異なるときだけ、グラフが更新されるようにするとします。新しい式で許容誤差内のデータが生成された場合は、set.FofXY メソッドはイベントをトリガーできないので、グラフは更新されません。ただし、プロパティは新しい値に設定されます。

UpdateGraph イベントのリスナーおよびコールバック

fcnview クラスは、addlistener メソッドを使用して、UpdateGraph イベントのリスナーを作成します。

obj.HLUpdateGraph = addlistener(obj.FcnObject,'UpdateGraph',...
            @(src,evnt)listenUpdateGraph(obj,src,evnt)); % Add obj to argument list

fcnview オブジェクトは、その HLUpdateGraph プロパティの event.listener オブジェクトにハンドルを保存します。これは、コンテキスト メニューによってリスナーを有効あるいは無効にするために使用されます。リスナーの有効化と無効化を参照してください。

fcnview オブジェクト (obj) は、リスナー コールバックに渡される、2 つの既定の引数 (srcevnt) に追加されます。イベントのソース (src) は fcneval オブジェクトですが、fcnview オブジェクトは、コールバックが更新する surface オブジェクトのハンドルを含むことを覚えておいてください。

関数 listenUpdateGraph は、以下のように定義されます。

function listenUpdateGraph(obj,src,evnt)
   if ishandle(obj.HSurface) % If surface exists
      obj.updateSurfaceData % Update surface data
   end
end

関数 updateSurfaceData は、異なる数学関数が fcneval オブジェクトに割り当てられるときに、表面データを更新するクラス メソッドです。グラフィックス オブジェクトのデータの更新は、新しいデータを使用して新しいオブジェクトを作成するよりも効率的です。

function updateSurfaceData(obj)
% Get data from fcneval object and set surface data
   set(obj.HSurface,...
      'XData',obj.FcnObject.Data.X,...
      'YData',obj.FcnObject.Data.Y,...
      'ZData',obj.FcnObject.Data.Matrix); 
end

PostSet イベントのリスナー

すべてのプロパティは、あらかじめ定義された PostSet イベントをサポートします。プロパティ イベントの詳細は、プロパティの設定とクエリのイベントを参照してください。この例は、fcneval Lm プロパティの PostSet イベントを使用します。このプロパティは、数学関数が評価される範囲を指定する 2 要素ベクトルを含みます。このプロパティが obj.Lm = [-3 5]; のようなステートメントによって変更された直後、このイベントをリッスンしている fcnview オブジェクトは、グラフを更新して新しいデータを反映します。

Diagram of PostSet event for Lm property

Lm プロパティの割り当て過程の順序

fcneval クラスは、Lm プロパティを設定する関数を定義します。オブジェクトの作成中、またはプロパティを再度割り当てるときに、値がこのプロパティに割り当てられると、以下の過程が起こります。

  1. Lm プロパティに引数値を割り当てる試みが行われます。

  2. set.Lm メソッドを実行して、値が適切な範囲にあるかどうかを確認します。範囲内にある場合、割り当てます。範囲外にある場合は、エラーが発生しします。

  3. Lm の値が適切に設定されると、MATLAB は PostSet イベントをトリガーします。

  4. すべてのリスナーはそれらのコールバックを実行しますが、その順序は決まっていません。

プロパティが実際に割り当てられるまでは、PostSet イベントは発生しません。PostSet イベントが発生する前に、プロパティの関数 set は、起こり得る割り当てエラーを処理することができます。

PostSet プロパティ イベントの有効化

PostSet イベントのリスナーを作成するには、プロパティの SetObservable 属性を true に設定する必要があります。

properties (SetObservable = true)
   Lm = [-2*pi 2*pi];  % specifies default value
end 
end

MATLAB は、イベントを自動的にトリガーするので、notify を呼び出す必要はありません。

プロパティの属性では、すべてのプロパティ属性の一覧を示しています。

PostSet イベントのリスナーおよびコールバック

fcnview クラスは、addlistener メソッドを使用して、PostSet イベントのリスナーを作成します。

obj.HLLm = addlistener(obj.FcnObject,'Lm','PostSet',...
            @(src,evnt)listenLm(obj,src,evnt)); % Add obj to argument list

fcnview オブジェクトは、その HLLm プロパティの event.listener オブジェクトにハンドルを保存します。これは、コンテキスト メニューによってリスナーを有効または無効にするために使用されます (リスナーの有効化と無効化を参照)。

fcnview オブジェクト (obj) は、リスナー コールバックに渡される、2 つの既定の引数 (srcevnt) に追加されます。イベントのソース (src) は fcneval オブジェクトですが、fcnview オブジェクトは、コールバックが更新する surface オブジェクトのハンドルを含むことを覚えておいてください。

コールバックは、範囲を変更すると、数学関数が異なる範囲で評価されるので、Axes の範囲を設定し、表面データを更新します。

function listenLm(obj,src,evnt)
   if ishandle(obj.HAxes) % If there is an axes
      lims(obj); % Update its limits
      if ishandle(obj.HSurface) % If there is a surface
         obj.updateSurfaceData % Update its data
      end
   end
end 

リスナーの有効化と無効化

fcnview オブジェクトはそれぞれ、作成するリスナー オブジェクトのハンドルを保存します。したがって、リスナーは、グラフの作成後、コンテキスト メニューにより有効化または無効化できます。すべてのリスナーは、Enabled というプロパティを定義する、event.listener クラスのインスタンスです。既定では、このプロパティは、リスナーを有効化する true の値をもちます。このプロパティを false に設定すると、リスナーは存在しますが無効化されます。この例は、各グラフの座標上にアクティブなコンテキスト メニューを作成します。これにより、Enabled プロパティの値を変更することが可能になります。

コンテキスト メニュー コールバック

メニュー上の 2 つの項目に対応するコンテキスト メニューが使用する 2 つのコールバックがあります。

  • ListenUpdateGraphPostSet リスナーの Enabled プロパティを true に設定し、Listen メニュー項目の隣にチェック マークを追加します。

  • Don't ListenUpdateGraphPostSet リスナーの Enabled プロパティを false に設定し、Don't Listen メニュー項目の隣にチェック マークを追加します。

いずれのコールバックも、リスナー オブジェクトのハンドルにアクセスするために、必要となるソースとイベント データ引数の他に fcnview オブジェクトを引数とします

関数 enableLisn は、ユーザーがコンテキスト メニューから Listen を選択するときに呼び出されます。

function enableLisn(obj,src,evnt)
   obj.HLUpdateGraph.Enabled = true; % Enable listener
   obj.HLLm.Enabled = true; % Enable listener
   set(obj.HEnableCm,'Checked','on') % Check Listen
   set(obj.HDisableCm,'Checked','off') % Uncheck Don't Listen
end

関数 disableLisn は、ユーザーがコンテキスト メニューから Don't Listen を選択するときに呼び出されます。

function disableLisn(obj,src,evnt)
   obj.HLUpdateGraph.Enabled = false; % Disable listener
   obj.HLLm.Enabled = false; % Disable listener
   set(obj.HEnableCm,'Checked','off') % Uncheck Listen
   set(obj.HDisableCm,'Checked','on') % Check Don't Listen
end

@fcneval/fcneval.m クラスのコード

classdef fcneval < handle
   properties
      FofXY
   end 
   
   properties (SetObservable = true)
      Lm = [-2*pi 2*pi]
   end % properties SetObservable = true
   
   properties (Dependent = true)
      Data
   end 
   
   events
      UpdateGraph 
   end
   
   methods
      function obj = fcneval(fcn_handle,limits)
         if nargin > 0
            obj.FofXY = fcn_handle;
            obj.Lm = limits;
            
         end
      end 
      
      function set.FofXY(obj,func)
         me = fcneval.isSuitable(func);
         if ~isempty(me)
            throw(me)
         end
         obj.FofXY = func;
         notify(obj,'UpdateGraph');  
      end 
      
      function set.Lm(obj,lim)  
         if  ~(lim(1) < lim(2))
            error('Limits must be monotonically increasing')
         else
            obj.Lm = lim;
         end
      end 
      
      function data = get.Data(obj)   
         [x,y] = fcneval.grid(obj.Lm);  
         matrix = obj.FofXY(x,y);
         data.X = x;
         data.Y = y;
         data.Matrix = real(matrix);  
         
      end 
      
   end % methods
   
   methods (Static = true)      
      function [x,y] = grid(lim)
         inc = (lim(2)-lim(1))/20;
         [x,y] = meshgrid(lim(1):inc:lim(2));
      end % grid
      
      function isOk = isSuitable(funcH)
         v = [1 1;1 1];
         try
            funcH(v,v);
         catch  %#ok<CTCH>
            me = MException('DocExample:fcneval',...
               ['The function ',func2str(funcH),' Is not a suitable F(x,y)']);
            isOk = me;
            return
         end
         if isscalar(funcH(v,v));
            me = MException('DocExample:fcneval',...
               ['The function ',func2str(funcH),' Returns a scalar when evaluated']);
            isOk = me;
            return
         end
         isOk = [];
      end
      
   end 
   
end 

@fcnview/fcnview.m クラスのコード

classdef fcnview < handle
   properties
      FcnObject     % fcneval object
      HAxes         % subplot axes handle
      HLUpdateGraph % UpdateGraph listener handle
      HLLm          % Lm property PostSet listener handle
      HEnableCm     % "Listen" context menu handle
      HDisableCm    % "Don't Listen" context menu handle
      HSurface      % Surface object handle
   end
   
   methods
      function obj = fcnview(fcnobj)
         if nargin > 0
            obj.FcnObject = fcnobj;
            obj.createLisn;
         end
      end
      
      function createLisn(obj)
         obj.HLUpdateGraph = addlistener(obj.FcnObject,'UpdateGraph',...
            @(src,evnt)listenUpdateGraph(obj,src,evnt));
         obj.HLLm = addlistener(obj.FcnObject,'Lm','PostSet',...
            @(src,evnt)listenLm(obj,src,evnt));
      end
      
      function lims(obj)
         lmts = obj.FcnObject.Lm;
         set(obj.HAxes,'XLim',lmts);
         set(obj.HAxes,'Ylim',lmts);
      end
      
      function updateSurfaceData(obj)
         data = obj.FcnObject.Data;
         set(obj.HSurface,...
            'XData',data.X,...
            'YData',data.Y,...
            'ZData',data.Matrix);
      end
      
      function listenUpdateGraph(obj,~,~)
         if ishandle(obj.HSurface)
            obj.updateSurfaceData
         end
      end
      
      function listenLm(obj,~,~)
         if ishandle(obj.HAxes)
            lims(obj);
            if ishandle(obj.HSurface)
               obj.updateSurfaceData
            end
         end
      end
      
      function delete(obj)
         if ishandle(obj.HAxes)
            delete(obj.HAxes);
         else
            return
         end
      end
      
   end
   methods (Static)
      createViews(a)
   end
end

@fcnview/createViews

function createViews(fcnevalobj)
   p = pi; deg = 180/p;
   hfig = figure('Visible','off',...
      'Toolbar','none');
   
   for k=4:-1:1
      fcnviewobj(k) = fcnview(fcnevalobj);
      axh = subplot(2,2,k);
      fcnviewobj(k).HAxes = axh;
      hcm(k) = uicontextmenu;
      set(axh,'Parent',hfig,...
         'FontSize',8,...
         'UIContextMenu',hcm(k))
      fcnviewobj(k).HEnableCm = uimenu(hcm(k),...
         'Label','Listen',...
         'Checked','on',...
         'Callback',@(src,evnt)enableLisn(fcnviewobj(k),src,evnt));
      fcnviewobj(k).HDisableCm = uimenu(hcm(k),...
         'Label','Don''t Listen',...
         'Checked','off',...
         'Callback',@(src,evnt)disableLisn(fcnviewobj(k),src,evnt));
      az = p/k*deg;
      view(axh,az,30)
      title(axh,['View: ',num2str(az),' 30'])
      fcnviewobj(k).lims;
      surfLight(fcnviewobj(k),axh)
   end
   set(hfig,'Visible','on')
end
function surfLight(obj,axh)
   obj.HSurface = surface(obj.FcnObject.Data.X,...
      obj.FcnObject.Data.Y,...
      obj.FcnObject.Data.Matrix,...
      'FaceColor',[.8 .8 0],'EdgeColor',[.3 .3 .2],...
      'FaceLighting','phong',...
      'FaceAlpha',.3,...
      'HitTest','off',...
      'Parent',axh);
   lims(obj)
   camlight left; material shiny; grid off
   colormap copper
end

function enableLisn(obj,~,~)
   obj.HLUpdateGraph.Enabled = true;
   obj.HLLm.Enabled = true;
   set(obj.HEnableCm,'Checked','on')
   set(obj.HDisableCm,'Checked','off')
end

function disableLisn(obj,~,~)
   obj.HLUpdateGraph.Enabled = false;
   obj.HLLm.Enabled = false;
   set(obj.HEnableCm,'Checked','off')
   set(obj.HDisableCm,'Checked','on')
end