MATLAB Answers

0

queue addlistener events or place event on EDT

Asked by Jim Hokanson on 24 Nov 2017
Latest activity Answered by Jim Hokanson on 5 Feb 2018
Every time the xlim changes I want to run some code.
plot(1:100)
L = addlistener(gca, 'XLim', 'PostSet', @my_callback);
If I use a listener, I seem to miss events if the callback is still running.
Thus I would like to queue property change event callbacks. Is this possible?
Alternatively, is it possible to have the callback queue a function on the EDT (event-dispatch thread) so that any changes are not missed?
The only really ugly solution I can come up with involves using the 'PropertyChangeCallback' of a java class since it seems to respond to being set in Matlab, as opposed to uicontrols which don't throw callbacks (I think) when changing their values via Matlab.
Update:
It appears that this is dependent on the callback code.
I had something like this:
disp(now)
t = tic;
pause(3)
toc(t);
If you do something like a horizontal zoom 2 - 3 times (quickly enough), only the first listener callback will run, even though you've changed xlim multiple times.
However if instead the code is:
disp(now)
t = tic;
r = rand(1,1e8);
toc(t);
Then rendering will not occur until the code has finished executing. Here I am using the rand() command to cause a reasonable delay (on my laptop 1.5s)
So in other words the blocking behavior of listeners depends on the callback code.
The missed events also only occur with user input. When running code the events run sequentially. For example, try this with the 'pause' version of the callback. In my case I get three events, rather than just 1.
set(gca,'xlim',[0 10]);
set(gca,'xlim',[0 20]);
set(gca,'xlim',[0 30]);

  4 Comments

Show 1 older comment
The axes is using the defaults (so interruptible with queuing). I'll try 'Interruptible' 'off' but I'd be surprised if that changed anything.
I tried the following and it only registers the first event, not subsequent ones:
clf
ax = gca;
plot(1:100)
L = addlistener(gca, 'XLim', 'PostSet', @my_callback);
set(ax,'Interruptible','off','BusyAction','queue')
%Inside my_callback
disp(now)
t = tic;
pause(3)
toc(t);
Just to follow up on this before I submit for technical support since I'm not getting the performance that I want. I want the following behavior:
1) Ensure that a function has always run after the xlim property changes. I don't need it to execute following every change, but if some time has passed with no change (a few hundred milliseconds), I want my function to have run after the change. More specifically, I can't miss a terminal event because of the function running.
2) It should not block other code execution for long periods of time. One approach to getting the last change is to check the xlim property as the function is about to terminate. If the property has changed, the function could run again. However, this could go on for a while and it should persistently block execution of other code, which is not desirable.
3) Indications of some rendering should be nearly immediate, even if the final processing is delayed.

Sign in to comment.

2 Answers

Answer by Jim Hokanson on 5 Feb 2018
 Accepted Answer

I found a different solution which appears to work nicely: https://undocumentedmatlab.com/blog/matlab-and-the-event-dispatch-thread-edt
I don't understand the blog post details but below is what I am using based on the callbackOnEDTQueue() function referenced at the end of the post.
so now in initialize:
obj.L3 = addlistener(axes_handle.XRuler,'MarkedClean',@(~,~)obj.xrulerMarkedClean);
obj.callback_obj = handle(com.mathworks.jmi.Callback,'callbackProperties');
set(obj.callback_obj,'delayedCallback',@(~,~)obj.renderDataCallback());
xRulerMarkerClean
if isequal(obj.last_processed_xlim,get(obj.axes_handle,'XLim'))
return
end
obj.throwCallbackOnEDT();
throwCallbackOnEDT()
%Some logging not shown, but the main call is:
obj.callback_obj.postCallback();

  0 Comments

Sign in to comment.


Answer by Jim Hokanson on 26 Nov 2017
Edited by Jim Hokanson on 27 Nov 2017

Here's one (ugly) solution.
This setup function does the following:
  1. Sets up listeners that I care about.
  2. Attaches a Java button to a figure which I've hidden ('Visible','off','HandleVisibility','off')
  3. Sets up the actual callback that I want on the Java button
function initialize(obj,axes_handle)
[obj.j_comp, temp] = javacomponent('javax.swing.JButton',[],obj.fig_handle);
obj.h_container = handle(temp);
set(obj.h_container,'BusyAction','queue','Interruptible','off');
obj.axes_handle = axes_handle;
if verLessThan('matlab', '8.4')
size_cb = {'Position', 'PostSet'};
else
size_cb = {'SizeChanged'};
end
obj.L1 = addlistener(axes_handle,'XLim','PostSet', @(~,~)obj.listenerCallback);
obj.L2 = addlistener(axes_handle, size_cb{:}, @(~,~) obj.listenerCallback);
set(obj.j_comp,'PropertyChangeCallback',@(~,~)obj.renderDataCallback());
end
The listener code is as follows. By toggling the text we generate the callbacks of interest on the EDT.
function listenerCallback(obj)
%Note the try statement because j_comp gets deleted asynchronously
try
%fprintf('1 %s\n',mat2str(get(obj.axes_handle,'xlim')));
if obj.last_string_index == 1
obj.last_string_index = 2;
obj.j_comp.setText('a');
else
obj.last_string_index = 1;
obj.j_comp.setText('b');
end
%fprintf('2 %s\n',mat2str(get(obj.axes_handle,'xlim')));
end
end
end

  1 Comment

I should also mention more control over callback contents could be obtained by following:

Sign in to comment.