Handle of UI component from which a context menu has been opened

33 views (last 30 days)
Dear all,
I spent the last hour looking for a solution to this problem but I've not found anything useful. I have a very simple problem, and probably the solution is easy as well: I have a context menu shared among several UI components. How can I get an handle of the object for which the context menu has been opened? I can't use different context menus because the UI components are created interactively.
EDIT: I try to explain a little bit better my issue. I have a GUI created with App Designer. The GUI shows an image on which the user can draw several ROIs (line, boxes, etc.). The same context menu is attached on each ROI and contains two items only: rename and delete object. My point is: when I click on "rename" how can I get the handle of the element from which the context menu has been opened? I tried to setup the "ContextMenuOpeningFcn" but it doesn't work (the function is not called when the menu is opened).
Thank you for your help!

Accepted Answer

Adam Danz
Adam Danz on 7 Oct 2020
Edited: Adam Danz on 7 Oct 2020
Neither the cxMenuSelectionFcn nor the ContextMenuOpeningFcn functions indicate the object that evoked the context menu.
Check out Adam's excellent wrapper function but you'll need to make two important modifications that I listed in the comments under his answer.
Using gco or gcbo are other useful alternatives but they do not recognize ROI objects.
After some thinking, I came up with another way to identify which ROI objects evoked the context menu explained below. But this was before discovering Adam's (the other Adam) answer suggested above. I'd try that one first.
Method
  1. ROI object handles are saved as a private (or public) property of the app.
  2. Every ROI object is assigned a unique tag name.
  3. The context menu is assigned to every ROI object.
  4. A listener is added to each ROI object and responds to a button click (right or left mouse).
  5. When the ROI is clicked, it updates the context menu tag to match the tag of the clicked ROI.
  6. When the cxMenuSelectionFcn is evoked by a right-button click, the function uses the recently updated context menu tag and matches it with all ROI tags to identify what object evokes the menu.
Demo
The cxMenuSelectionFcn merely changes the color of the ROI. The full app is attached. Here are the key parts.
The variable containing the ROI handles is a private (or public) property of the app.
properties (Access = private)
ROIhandles=gobjects(1); % Stores ROI handles, produced in startupFcn
end
The startupFcn creates the context menu, plots the ROI objects, and assigns the listeners to the ROI objects. This could be done in any callback function. Importantly, unique tags must be assigned to the ROI objects and the context menu must exist prior to assigning the listeners.
% Code that executes after component creation
function startupFcn(app)
% Create context menu
cm = uicontextmenu(app.UIFigure);
m1 = uimenu(cm,'Text','ChangeColor','MenuSelectedFcn',@(mn,ad)cxMenuSelectionFcn(app,mn,ad));
% Plot 3 ROIs
% Assign unique tags to each ROI.
hold(app.UIAxes,'on')
app.ROIhandles(1) = images.roi.Rectangle(app.UIAxes,'Position',[.1 .1 .2 .2],'Tag','R1');
app.ROIhandles(2) = images.roi.Rectangle(app.UIAxes,'Position',[.5 .5 .1 .2],'Tag','R2');
app.ROIhandles(3) = images.roi.Rectangle(app.UIAxes,'Position',[.1 .6 .2 .3],'Tag','R3');
% Assign context menu to all ROIs (or any object that has a unique tag)
% This must come after assigning tags and before adding listeners below.
set(app.ROIhandles, 'ContextMenu', cm)
% Add listeners to the ROI objects.
% The listening will respond when the ROI obj is clicked (right or left mouse button)
% When clicked, the context menu tag will be changed to match the ROI's tag. This will
% inform the context menu selection function which object evoked it.
for i = 1:numel(app.ROIhandles)
app.ROIhandles(i).UserData = listener(app.ROIhandles(i), 'ROIClicked', ...
@(~,~)set(app.ROIhandles(i).ContextMenu,'Tag', app.ROIhandles(i).Tag));
end
end
Define the cxMenuSelectionFcn function
This is a private (or public) method defined in the App. Two assert()'s are used to make sure a tag was successfully assigned to the context menu and that one and only one ROI object was matched to the tag. It then changes the color of the ROI object to some random color.
methods (Access = private)
function cxMenuSelectionFcn(app,menu,~)
% cxMenuSelectionFcn; responds to selecting "ChangeColor" in context menu
% The context menu is assigned to ROI objects.
% Get ContextMenu tag
cmtag = menu.Parent.Tag;
assert(~isempty(cmtag),'Failure to assign tag to context menu.')
% get handle associated with tag
handleIdx = strcmp({app.ROIhandles.Tag}, cmtag);
assert(sum(handleIdx)==1, 'Failture to identify ROI.')
hRoi = app.ROIhandles(handleIdx);
% Change color of ROI
hRoi.Color = rand(1,3);
end
end
  4 Comments
Bruno Gonfiotti
Bruno Gonfiotti on 9 Oct 2020
I found the time to test your solution, and it works like a charm! Thank you again for your help! I marked your answer as the accepted.

Sign in to comment.

More Answers (2)

Jasmine Poppick
Jasmine Poppick on 20 Sep 2023
Starting in R2023b, the MenuSelectedFcn and ContextMenuOpeningFcn callback event data contains the UI component that was right-clicked to open the context menu, stored in the ContextObject property.
For example, if you run this code and open the context menu on the button, the callback displays the Button object.
fig = uifigure;
cm = uicontextmenu(fig,ContextMenuOpeningFcn=@(src,event)disp(event.ContextObject));
m = uimenu(cm,Text="Menu");
btn = uibutton(fig,ContextMenu=cm);
For more information about this event data, see ContextMenuOpeningFcn.
Before R2023b, see Adam's solution for getting the component information.

Paola De Cecco
Paola De Cecco on 23 Jan 2023
One alternative could be to set UserData on all the context menus to the event Source parent.CurrentObject.
then you can read from UserData of each of the context menu, the handle of the uicontrol that created the context menu.
function ContextMenuOpening(app, event)
disp('can I get the menu owner here?')
L=length(event.Source.Children)
for i=1:L
set(event.Source.Children(i),'UserData',event.Source.Parent.CurrentObject);
end
end

Categories

Find more on Develop uifigure-Based Apps 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!