addlistener for drawrectangle to trigger when the rectangle is first drawn

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

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).
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 :)

Sign in to comment.

Answers (1)

How to add listener that responds to the first time drawrectangle is called
For instructions on how to implement this in AppDesigner, see my comment below.
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

Adam, your answer is AMAZING, thank you!! However, I seem to have left out the fact that I hope to do this with an App Designer UIAxes object, and I am not seemingly able to get this working.
In the attached App Desiger file, press GO to plot a sine curve, and then press Box to create a drawrectangle() object. I created a listener for ChildAdded (which does not generate an error), following your code, but then when the rectangle is first drawn, the listener isn't triggered -- a function here called rectangle_added_listener doesn't seem to get called. There is a separate listener for ROIMoved on the rectangle that is working.
Might you have any thoughts about this?
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.
Adam, your help is amazingly kind and extremely useful! Thank you so much!
In the current version, added here:
  • It is working "more" now that I'm using MATLAB 2020b; I was using 2020a and it seems that one of the listeners was not yet implemented in that version?
  • I am hoping to implement Version 2 of your code, where the listener triggers when the rectangle is actually drawn by the user.
  • Your drawRectDetectionFcn function is being called when the drawrectangle function is called, but before the rectangle is drawn. Using the Version 2 of your code, since the .Position is empty at that point, the drawRectangleDetectionFcn function doesn't implement the main part of the code (which here just shows the .Position of the rectangle)
  • Then when the rectangle is actually drawn by the user, that doesn't add a child to the axes, so drawRectangleDetectionFcn isn't being called again.
  • My existing listener, for the ROIMoved event, is being called after the rectangle is drawn and once it is then moved.
  • I am not deleting the listener, as you had in your code, yet. That isn't a big deal, I think.
  • So, I think I am still not able to trigger a function call when the rectangle has been drawn by the user?
Do you have any further thoughts about this?
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.
Great point!
My goal is to have a way for the user to use the mouse to indicate regions of the plot (here, a simple sine curve is shown) to analyze.
This version, as you see, is based on the idea of drawing rectangles. The code would use the left and right side of the rectangles to define the regions of the plot, not necessarily the top and bottom sides. Each time a box is drawn or moved, the code would update the analysis automatically.
Another idea would be to switch to using the drawpoint routine in the Image Processing Toolbox. Every two points drawn would be interpreted as a left and right boundary for the region of the plot to analyze. There is an issue here about what happens if the user clicks on the wrong point, so either I'd want an "Undo" button or only proceed with processing when they press a "Go" button or something like that.
Yet another idea would be to use WindowButtonDownFcn on the parent UIFigure, and then deal with pairs of points like in the last idea. That's my least favorite idea, because the event is only triggered when you click exactly on the plot, whereas the last two ideas just use the x coordinate of the ROI and let the user be inexact about the y coordinate.
Something would have to trigger drawrectangle. Why can't that same event extract and return the rectangle coordinates?
I'm not sure I understand.
In my thinking of what I am trying to do, the whole problem is that calling drawrectangle without arguments (so it waits for the user to use the mouse to draw the rectangle) and then actually drawing that rectangle are separate events, and I want the program to respond immediately to the latter, not the former.
I could, instead, call drawrectangle with a 'Position' argument, so the rectangle gets drawn immediately, but then I just use a listener to trigger when it is moved. That's ok but seems a bit inelegant.
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?

Sign in to comment.

Categories

Find more on Interactive Control and Callbacks in Help Center and File Exchange

Products

Release

R2020a

Asked:

on 4 Jun 2021

Commented:

on 31 May 2023

Community Treasure Hunt

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

Start Hunting!