イベントとリスナーを使用するための手法
例の概要
この例は、2 つのクラスを定義します。
fcneval
— 評価機能をもつクラスは、MATLAB® 式を含み、指定した範囲でこの式を評価します。fcnview
— 表示機能をもつクラスは、fcneval
オブジェクトを含み、fcneval
に含まれるデータを使用して評価した式の表面グラフを表示します。
このクラスは、2 つのイベントを定義します。
新しい値が MATLAB 関数に対して指定されるときに起こる、クラス定義されたイベント
範囲を含むプロパティが変更されるときに発生するプロパティ イベント
次の図は、2 つのオブジェクトの関係を示します。fcnview
オブジェクトは、fcneval
オブジェクトを含み、このオブジェクトのデータからグラフを作成します。fcnview
は、fcneval
オブジェクトのデータに変化があると、グラフを変更するために、リスナーを作成します。
この例で示した手法
クラス定義内でのイベントの名前付け
notify
を呼び出すことによるイベントのトリガーSetObservable
属性によるプロパティ イベントの有効化クラス定義イベントとプロパティ
PostSet
イベントに対するリスナーの作成追加の引数を受け取るリスナー コールバック関数の定義付け
リスナーの有効化と無効化
fcneval クラスのまとめ
fcneval
クラスは、2 変数の指定範囲で MATLAB 式を評価します。fcneval
は fcnview
クラスのオブジェクトが表面としてグラフ化するデータのソースです。fcneval
はこの例で使用されているイベントのソースです。クラス定義のリストは、@fcneval/fcneval.m クラスのコードを参照してください。
プロパティ | 値 | 目的 |
---|---|---|
FofXY | 関数ハンドル | MATLAB 式 (2 変数の関数) |
Lm | 2 要素ベクトル | 関数が評価される両変数の範囲。SetObservable 属性を true に設定すると、プロパティ イベント リスナーが有効になります。 |
Data | x、y、および z 行列をもつ構造体 | 関数を評価した結果のデータ。表面グラフに使用されます。Dependent 属性が true に設定されると、クエリされたときに保存されたデータがない場合に、get.Data メソッドが呼び出されてプロパティ値を決定します。 |
イベント | トリガーされるとき |
---|---|
UpdateGraph | FofXY プロパティ関数 set (set.FofXY ) は、このクラスのオブジェクトに上の MATLAB 式に対して新しい値が指定されると、notify メソッドを呼び出します。 |
メソッド | 目的 |
---|---|
fcneval | クラス コンストラクター。入力は、関数ハンドルと、関数を評価する範囲を指定する 2 要素ベクトルです。 |
set.FofXY | FofXY プロパティの関数 set。オブジェクトの作成中など、プロパティ値の設定時に常に呼び出されます。 |
set.Lm | Lm プロパティの関数 set。有効な範囲の判定に使用されます。 |
get.Data | Data プロパティの関数 get。このメソッドは、クラス メンバーあるいは外部からデータがクエリされると常に Data プロパティの値を計算します。 |
grid | データの計算に使用される静的メソッド (Static 属性が true )。 |
fcnview クラスのまとめ
fcnview
クラスのオブジェクトは、関数表示で作成される 4 つの表面グラフのデータ ソースとして、fcneval
オブジェクトを含んでいます。fcnview
は、fcneval
オブジェクトに含まれるデータの変更に応答するリスナーとコールバック関数を作成します。クラス定義のリストは、@fcnview/fcnview.m クラスのコードを参照してください。
プロパティ | 値 | 目的 |
---|---|---|
FcnObject | fcneval オブジェクト | このオブジェクトは、関数グラフの作成に使用するデータを含みます。 |
HAxes | Axes のハンドル | fcnview オブジェクトの各インスタンスは、サブプロットを含む Axes のハンドルを保存します。 |
HLUpdateGraph | UpdateGraph イベントに対する event.listener オブジェクト | event.listener オブジェクトの Enabled プロパティを true に設定すると、リスナーが有効になり、false にするとリスナーが無効になります。 |
HLLm | Lm プロパティ イベントに対する event.listener オブジェクト | event.listener オブジェクトの Enabled プロパティを true に設定すると、リスナーが有効になり、false にするとリスナーが無効になります。 |
HEnableCm | uimenu ハンドル | コンテキスト メニュー上にあるリスナーを有効にするための項目 (確認した動作の処理に使用します)。 |
HDisableCm | uimenu ハンドル | コンテキスト メニュー上にあるリスナーを無効にするための項目 (確認した動作の管理に使用します)。 |
HSurface | surface ハンドル | 表面データを更新するイベント コールバックが使用します。 |
メソッド | 目的 |
---|---|
fcnview | クラス コンストラクター。入力は fcneval オブジェクトです。 |
createLisn | addlistener を呼び出し、UpdateGraph および Lm プロパティの PostSet リスナーを作成します。 |
lims | Axes の範囲を fcneval オブジェクトの Lm プロパティの現在値に設定します。イベント ハンドラーによって使用されます。 |
updateSurfaceData | 新しいオブジェクトを作成せずに表面データを更新します。イベント ハンドラーによって使用されます。 |
listenUpdateGraph | UpdateGraph イベントに対するコールバック。 |
listenLm | Lm プロパティの PostSet イベントに対するコールバック。 |
delete | fcnview クラスの delete メソッド。 |
createViews | 各サブプロットに対して fcnview クラスのインスタンスを作成する静的メソッド。リスナーを有効化/無効化するコンテキスト メニューを定義し、サブプロットを作成します。 |
ハンドル クラスから継承されるメソッド
fcneval
と fcnview
クラスは、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 つの表示を作成します。
各サブプロットは、そのグラフに関連するリスナーを有効にしたり無効にできる、コンテキスト メニューを定義します。たとえば、サブプロット 221 (左上) 上のリスナーを無効にし、fcneval
オブジェクトに含まれている MATLAB 式を変更する場合、UpdateGraph
イベントがトリガーされると、残りの 3 つのサブプロットだけが更新されます。
feobject.FofXY = @(x,y) x.*exp(-x.^.5-y.^.5);
同様に、feobject.Lm
プロパティに値を割り当てて範囲を変更する場合、feobject
が PostSet
プロパティ イベントをトリガーすると、リスナー コールバックがグラフを更新します。
feobject.Lm = [-8 3];
この図では、サブプロット 221 のコンテキスト メニューが、リスナーを再び有効にしています。PostSet
プロパティのイベントのリスナー コールバックは、表面データの更新も行うので、すべての表示が同期します。
UpdateGraph イベントおよびリスナーの実装
UpdateGraph
イベントは、fcneval
オブジェクトに含まれる数学関数の MATLAB 式が変更されるときに発生します。表面グラフを含む fcnview
オブジェクトは、このイベントをリッスンしています。したがって、これらはグラフを更新して新しい関数を表示することができます。
UpdateGraph イベントの定義とトリガー
UpdateGraph
イベントは、クラス定義のイベントです。fcneval
クラスは、イベントに名前を付け、イベントが発生すると notify
を呼び出します。
fcnview
クラスは、このイベントに対してリスナーを定義します。fcneval
がイベントをトリガーすると、fcnview
のリスナーは、以下のアクションを実行するコールバック関数を実行します。
fcnview
オブジェクトによって保存された surface オブジェクトのハンドルがまだ有効であるかどうかを判定します。すなわち、オブジェクトはまだ存在します。fcneval
オブジェクトのData
プロパティをクエリすることによって、表面のXData
、YData
、ZData
を更新します。
fcneval
クラスは、event
ブロックでイベントの名前を定義します。
events
UpdateGraph
end
イベントのトリガー タイミングの決定
fcneval
クラスは FofXY
プロパティの set メソッドを定義します。FofXY
は、数学関数で使用する MATLAB 式を保存するプロパティです。この式は、2 変数関数の有効な MATLAB 式でなければなりません。
set.FofXY
メソッドには、次の機能があります。
式の適性を判別します。
式が適している場合は、次のことを行います。
式を
FofXY
プロパティに割り当てます。UpdateGraph
イベントをトリガーします。
fcneval.isSuitable
が MException
オブジェクトを返さなかった場合は、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.isSuitable
は MException
オブジェクトを返します。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 つの既定の引数 (src
、evnt
) に追加されます。イベントのソース (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
オブジェクトは、グラフを更新して新しいデータを反映します。
Lm
プロパティの割り当て過程の順序
fcneval
クラスは、Lm
プロパティを設定する関数を定義します。オブジェクトの作成中、またはプロパティを再度割り当てるときに、値がこのプロパティに割り当てられると、以下の過程が起こります。
Lm
プロパティに引数値を割り当てる試みが行われます。set.Lm
メソッドを実行して、値が適切な範囲にあるかどうかを確認します。範囲内にある場合、割り当てます。範囲外にある場合は、エラーが発生しします。Lm
の値が適切に設定されると、MATLAB はPostSet
イベントをトリガーします。すべてのリスナーはそれらのコールバックを実行しますが、その順序は決まっていません。
プロパティが実際に割り当てられるまでは、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 つの既定の引数 (src
、evnt
) に追加されます。イベントのソース (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 つのコールバックがあります。
Listen —
UpdateGraph
とPostSet
リスナーのEnabled
プロパティをtrue
に設定し、Listen メニュー項目の隣にチェック マークを追加します。Don't Listen —
UpdateGraph
とPostSet
リスナーの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