queue addlistener events or place event on EDT

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

Is your object set to queue callbacks or discard them? Is it set to interruptible ?
Try setting to not interruptible and queue callbacks.
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.

 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();

1 Comment

Since apparently I can't understand notes to myself, here is a simpler version:
%Call the magic incantation
async_callback = handle(com.mathworks.jmi.Callback,'callbackProperties');
%Specify the function you want called
%Note in this version I am ignoring the event and source inputs
set(async_callback,'delayedCallback',@(~,~)obj.nextTask());
%ALTERNATIVELY, note, this must take in two inputs
set(async_callback,'delayedCallback',myAwesomeFunction);
%Then when you want to place something on EDT, call this:
async_callback.postCallback();

Sign in to comment.

More Answers (1)

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

Categories

Find more on Creating, Deleting, and Querying Graphics Objects in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!