addlistener for drawrectangle to trigger when the rectangle is first drawn
Show older comments
I want the user of my code to draw a rectangle in a plot, with:
roi = drawrectangle();
I understand how to set up a listener that gets triggered whenever the drawn rectangle gets moved/resized:
addlistener(roi,'ROIMoved', @roi_listener);
... but how do I do something similar when the rectangle is first drawn? This does not work:
addlistener(roi,'DrawingFinished', @roi_listener);
2 Comments
Adam Danz
on 8 Jun 2021
You'll have to set up a listener that monitors new objects added to the axes and determines if they are rectangles added by the drawrectangle function. That's why my answer does. It then removes the listener after the first rectangle is added (you can remove that section if you'd like).
Michal Merman
on 31 May 2023
Nice and elegant :)
However, this still leaves open the question how can DrawingFinished event be used?
I'm having the same issue, I'd like something to happen each time I draw a rectangle on the image - but I can't define the listener before object creation.
Would creating a custom class based on Rectangle do that?
Thanks :)
Answers (1)
How to add listener that responds to the first time drawrectangle is called
Create figure & axes
A random scatter plot is also added for demo purposes only.
fig = figure();
ax = axes(fig);
plot(ax, rand(1,20),'o')
Create listener
- The listener will respond when objects are added to the axes but contains a condition to check for image rectangle objects.
- The listener is stored within the axes userdata. Alternatively, you could use set/getappdata.
ax.UserData.Listener = addlistener(ax,'ChildAdded',@drawRectDetectionFcn);
Call drawrectangle
roi = drawrectangle(ax)
Listener callback function (Version 1)
The callback function performs the following tasks
- Extract all children handles from axes.
- Looks for "images.roi.Rectangle" objects.
- If none are found the function returns without doing anything.
- If a images.roi.Rectangle object is detected, the function will perform whatever action you want (fill in the code) and at the end, it will remove/disable the listener since you only want this to trigger when the first rectangle is added.
The listener can be either disabled (with the option to re-enable it again) or it can be permanently deleted. Both commands are included - you can decide which is best for your needs.
Important:, the listener will trigger when the object is added and the image rectangle object is added when drawrectangle is called before the user interactively draws the rectangle. Nevertheless, you can change properties of the rectangle such as color, for example, before the rectangle is drawn.
function drawRectDetectionFcn(ax,~)
% Evoked by listener when children are added to the axes in ax.
% Detects the presence of an images.roi.Rectangle object created
% by drawrectangle() and performs [SOME ACTION]. After the first
% rectangle obj is detected, the listener is removed.
axChil = ax.Children;
chilClass = arrayfun(@(h){class(h)},axChil);
isRect = any(strcmpi(chilClass, 'images.roi.Rectangle'));
if isRect
%
%
% DO YOUR STUFF HERE
%
%
% Remove|Disable listener after the first rectangle is added.
delete(ax.UserData.Listener) % Permanently delete listener
% ax.UserData.Listener.Enabled = false; % Disable listener
end
end
Listener callback function (Version 2)
Update: This version works with Matlab Online but not in a local installation of Matlab.
Use this version of the callback function if you need the listener to respond after the first rectangle is draw. Unlike the first version, the condition requires that the position property of the rectangle is not empty. The position property is initially empty while waiting for the user to draw the rectangle and is filled it after the drawing is complete.
function drawRectDetectionFcn(ax,~)
% Evoked by listener when children are added to the axes in ax.
% Detects the presence of a drawn images.roi.Rectangle object created
% by drawrectangle() and performs [SOME ACTION]. After the first
% rectangle obj is detected, the listener is removed.
axChil = ax.Children;
chilClass = arrayfun(@(h){class(h)},axChil);
isRect = strcmpi(chilClass, 'images.roi.Rectangle');
if any(isRect) && ~isempty(axChil(find(isRect,1)).Position)
%
%
% DO YOUR STUFF HERE
%
%
% Remove listener after first rectangle is added.
delete(ax.UserData.Listener)
%ax.UserData.Listener.Enabled = false;
end
8 Comments
David Aronstein
on 8 Jun 2021
Adam Danz
on 8 Jun 2021
I don't see where you've implemented the listener. I see where you've implemented the other listener "roi_listener" to respond to ROIMoved events but not the one described in your question and my answer.
Since you're using appdesigner, there are some small but important changes to make but you still need to implement all of the steps in my answer.
- In my answer I'm saving the listener object in the UserData of the axes. You should create a private property named "ObjectAddedListener" or something descriptive like that. You'll also need to replace ax with your axis handle app.Plot. This is what the single line of code will look like in the properties section:
properties (Access = private)
roi = []; % - your line, not mine
ObjectAddedListener; % will contain listener assigned in startup
end
- Then define the listener in your startup function (how to create a startup function in AppDesigner). You'll need to add an additional input for the mandatory app input. I also added movegui() which ensures the GUI is on-screen after rendering (my laptop monitor is smaller than the GUI figure).
% Code that executes after component creation
function startupFcn(app)
app.ObjectAddedListener = addlistener(app.Plot,'ChildAdded',@(ax,evt)drawRectDetectionFcn(app,ax,evt)); % Description
movegui(app.UIFigure)
end
- Add a new private function named drawRectDetectionFcn and then copy-paste the function from my answer (don't change "ax" since this is the input var name). The only change needed is to add app as the first input,
function drawRectDetectionFcn(app,ax,~)
- Finally, to turn off the listener after the first rectangle is added, use the enable method in my answer.
app.ObjectAddedListener.Enabled = false;
I've tested these changes and confirmed that they work as expected.
If you continue to have problems after making these changes, share the updated version of your app and a description of the problem.
David Aronstein
on 9 Jun 2021
I'm not sure what difference between 20a and 20b would change the behavior. Once you get this working, you may want to test it again in 20a.
I see what you mean that the drawRectDetectionFcn isn't called after the rectangle is drawn. Here's the crazy part: yesterday when I wrote this I was using Matlab Online (R2021a) and it worked. Now I'm using my local version of Matlab (R2021a) and it doesn't work. I just tested it again with Matlab Online and it works!
For some reason the listener is invoked again after drawing the rectable with Matlab online but not with my local vs of matlab using the same release.
Nevertheless, obviously the 2nd version of my callback function is not reliable.
Before we spend too much time thinking of an alternative, please explain what your intentions are - what are you going to do with the rectangle object? Depending on your plans, it may not matter whether the listener responds before or after the rectangle is drawn.
David Aronstein
on 10 Jun 2021
David Aronstein
on 11 Jun 2021
Adam Danz
on 11 Jun 2021
The question is, what triggers/calls drawrectangle? Is that part of the code or does the user merely enter it in the command window any time they want?
Categories
Find more on Interactive Control and Callbacks 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!