MATLAB Answers

1

addlistener Behavior/Syntax

Asked by Peter Cook on 1 Feb 2018
Latest activity Commented on by Peter Cook on 2 Feb 2018
I've got a listener on an axes object that listens for changes to either XLim or YLim and fires a callback based on the new axes limits. The callback executes correctly, but I've noticed that if both XLim and YLim are updated simultaneously that the callback executes twice (once when XLim is updated, and again when YLim is updated), regardless of axes property assignment syntax. I'd like to avoid this behavior - that is, if both XLim and YLim are updated, I'd only like the callback to fire once.
Axes Listener Class:
classdef dasAxesPropEvent < handle
properties(SetObservable, GetObservable, AbortSet)
XLim
YLim
end
methods
%constructor
function obj = dasAxesPropEvent(hAx)
%hAx is a handle object to the parent axes
obj.XLim = hAx.XLim;
obj.YLim = hAx.YLim;
addlistener(hAx,{'XLim','YLim'},'PostSet',@obj.handlePropEvent);
end
%gettor
function propval = get.XLim(obj)
propval = obj.XLim;
end
%gettor
function propval = get.YLim(obj)
propval = obj.YLim;
end
%settor
function set.XLim(obj,val)
obj.XLim = val;
end
%settor
function set.YLim(obj,val)
obj.YLim = val;
end
%overload disp
function disp(obj)
disp(class(obj))
end
%updator
function handlePropEvent(obj,src,evnt)
switch src.Name
case 'XLim'
obj.XLim = evnt.AffectedObject.XLim;
case 'YLim'
obj.YLim = evnt.AffectedObject.YLim;
otherwise
%?
end
end
end
end
In another object, I add a listener to a dasAxesPropEvent to fire the callback if the axes is updated. I use the listener class instead of a listener directly on the axes in order to use the AbortSet property.
hObj.axesListener = addlistener(hDasPropEvent,{'XLim','YLim'},'PostSet',@hObj.updateDasImage);
Here's an example of the behavior. I added some debug/print lines (dbstack, XLim, and YLim) to @hObj.updateDasImage.
> In dasImage/updateDasImage (line 205)
In dasImage>@(varargin)hObj.updateDasImage(varargin{:}) (line 182)
In dasAxesPropEvent/handlePropEvent (line 48)
In dasAxesPropEvent>@(varargin)obj.handlePropEvent(varargin{:}) (line 16)
In matlab.graphics.internal.LinkProp/processUpdate (line 30)
In localUpdateListeners>@(varargin)hLink.processUpdate(varargin{:}) (line 54)
In dtsDasWaterfall01>aufbauschen (line 3793)
In gui_mainfcn (line 95)
In dtsDasWaterfall01 (line 44)
In matlab.graphics.internal.figfile.FigFile/read>@(hObject,eventdata)dtsDasWaterfall01('aufbauschen',hObject,eventdata,guidata(hObject))
XLim
01-Nov-2017 12:33:24
01-Nov-2017 14:17:36
YLim
14.3882 985.0474
> In dasImage/updateDasImage (line 205)
In dasImage>@(varargin)hObj.updateDasImage(varargin{:}) (line 182)
In dasAxesPropEvent/handlePropEvent (line 50)
In dasAxesPropEvent>@(varargin)obj.handlePropEvent(varargin{:}) (line 16)
In matlab.graphics.internal.LinkProp/processUpdate (line 30)
In localUpdateListeners>@(varargin)hLink.processUpdate(varargin{:}) (line 54)
In dtsDasWaterfall01>aufbauschen (line 3793)
In gui_mainfcn (line 95)
In dtsDasWaterfall01 (line 44)
In matlab.graphics.internal.figfile.FigFile/read>@(hObject,eventdata)dtsDasWaterfall01('aufbauschen',hObject,eventdata,guidata(hObject))
XLim
01-Nov-2017 12:33:24
01-Nov-2017 14:17:36
YLim
15.6671 984.4080
The dbstack is nearly identical for both calls - the XLim listener callback fires before YLim is assigned, then the YLim listener callback fires.
The line where XLim & YLim are assigned is this:
set(handles.axes1,'XLim',newXLim,'YLim',newYLim);
The same behavior happens if I do serial assignment (except the dbstack entry point for aufbauschen is +1 line):
handles.axes1.XLim = newXLim;
handles.axes1.YLim = newYLim;
and also the same thing happens if I use deal:
[handles.axes1.XLim,handles.axes2.YLim] = deal(newXLim,newYLim);
In this scenario (XLim & YLim both updated) is it possible to make the callback execute only once? It is possibly relevant information that there is also a LinkProp listener on the same axes, which is why there's a call to LinkProp ahead of the call to handlePropEvent in the dbStack.

  0 Comments

Sign in to comment.

1 Answer

Answer by Benjamin Kraus on 1 Feb 2018
 Accepted Answer

There is no way to merge the two events into one event. Even when you call set, the properties are still set one at a time, in order.
In other words:
set(handles.axes1,'XLim',newXLim,'YLim',newYLim);
is effectively equivalent to:
handles.axes1.XLim = newXLim;
handles.axes1.YLim = newYLim;
In both versions, the XLim property is set first, followed by the YLim property. The PostSet listener for the XLim property has no way to know that you are about to set the YLim property, so it cannot suppress the first event while waiting for the YLim to be set.

  3 Comments

Thank you - that makes sense.
Seems like what I need to do instead is to emulate a PostZoomCallback. It looks like PostZoomCallback is only triggered once when both X and Y axes limits are changed, but after some close inspection of the zoom.m file, it seems like its manually triggered at the end of the different zoom helper functions and not triggered by listeners.
I'm curious why it matters whether the callback fires twice or not. Does the callback have some side-effect (such as incrementing a counter) that behaves differently if it is called too many times? Or is your concern related to performance?
Another way to solve your question may be to look at the callback and identify if there is some way to make it resilient to being triggered twice.
The issue with it firing twice is a performance degradation/lag.
I made a kludgey workaround where the callback wont fire if the axes visible property is off when the limit is changed. Each place in the GUI parent code where both XLim & YLim are updated manually, the parent axes is made invisible prior to updating the first XLim or YLim.

Sign in to comment.