Multiple objects sharing a context menu?
15 views (last 30 days)
Show older comments
Hi all,
I'm trying to have multiple objects in the same figure share a common context menu. In the menu callback, is there a way to tell which one of the objects initiated the callback (i.e. was right-clicked on)? Or do I have to create a separate contextmenu object for each one of them?
Thanks!
Niko
6 Comments
Adam
on 21 Aug 2015
You can set the callback and when you do you can pass in other arguments than just those that come automatically. If your context menu is embedded in a class you only need to define the context menu once, but the class can also contain e.g. a line object handle or whatever else it needs to allow it to link together the context menu and the thing it is being attached to.
Then you would create and instance of your class for each context menu you want, each instance of the class would link to a different graphics object which it would then pass into the context menu's callback to allow you to get the context specific information.
Accepted Answer
Adam
on 18 Feb 2016
Edited: Adam
on 18 Feb 2016
This is far too late top be of use to the person asking the question I am sure, but today I had to solve this same problem myself, did an internet search and bizarrely landed here to find my own suggestion which I hadn't even considered when thinking about a solution to this before my search today. So I have now implemented my idea and since it is just a general wrapper type class without any proprietary code I will add my solution here.
It may be there is some simpler way that both myself and the original asker of the question both missed, but even if there is, I enjoyed creating my own solution anyway!
classdef GraphicsObjectWithContextMenu < handle
properties( SetAccess = immutable, GetAccess = private )
hGraphics = [];
hContextMenu = matlab.ui.container.ContextMenu.empty;
end
methods
function obj = GraphicsObjectWithContextMenu( hGraphics )
assert( isgraphics( hGraphics ), 'GraphicsObjectWithContextMenu:InvalidGraphicsObject', 'hGraphics must be a handle to a valid graphics object' );
obj.hGraphics = hGraphics;
obj.hContextMenu = uicontextmenu( );
obj.hGraphics.UIContextMenu = obj.hContextMenu;
end
function addMenuItem( obj, menuItem, callback )
validateattributes( menuItem, { 'matlab.ui.container.Menu' }, { 'scalar' } )
validateattributes( callback, { 'function_handle' }, { 'scalar' } )
assert( nargin( callback ) == 1, 'GraphicsObjectWithContextMenu:InvalidMenuCallback', 'callback must be a function handle of exactly 1 argument, representing the graphics object to which the context menu is attached' );
menuItem.Parent = obj.hContextMenu;
fCallback = @( src,evt ) callback( obj.hGraphics );
menuItem.Callback = fCallback;
end
end
end
is the class I mentioned in the comments to the question. I had to put a little thought in to its structure as my original quickly thrown out suggestion didn't fully consider all the difficulties. Also this is not the only way to setup this class of course, it is just the way that I am happy with for my work.
Obviously some aspects of it are optional, but I like to validate things in my classes and assert where I consider it appropriate too for ease of usage, but that is just my preference - it is not crucial to the bare minimum solution.
And here is some simple example code to show it in use with multiple line objects. I just added one menu item to the context menu as it is the only one I need for my current work, but others can be added in just the same manner.
figure; hAxes = gca;
hold on
hLines(1) = plot( hAxes.XLim, [2 2], 'LineWidth', 2 );
hLines(2) = plot( hAxes.XLim, [3 3], 'LineWidth', 2 );
hLines(3) = plot( hAxes.XLim, [7 7], 'LineWidth', 2 );
hLines(4) = plot( hAxes.XLim, [8 8], 'LineWidth', 2 );
m1 = uimenu( 'Label', 'Remove' );
linesWithContextMenu = GraphicsObjectWithContextMenu.empty( 0, numel( hLines ) );
for n = 1:numel( hLines )
linesWithContextMenu( n ) = GraphicsObjectWithContextMenu( hLines( n ) );
linesWithContextMenu( n ).addMenuItem( m1.copy( ), @( lineHandle ) delete( lineHandle ) );
end
The key points of the solution are:
- The handle-derived class allows you to wrap up an individual graphics object handle together with a context menu so that the context menu knows its triggering graphics object
- The menu items have been added via a function which takes the callback as a separate argument to the menuItem in which it would normally live. This you can see is because the callback should have 1 variable argument, the graphics object handle, which this class function fills in to create the actual callback which the menu item will trigger. This means that your callback function can be anything you want that works with the handle of the graphics object that triggered the callback.
- Usage should be very simple, but beware to take a copy of the menu item object when adding it to multiple objects, otherwise the same item will just keep getting re-parented and only the last object it is assigned to will actually have the context menu item.
2 Comments
Adam Danz
on 7 Oct 2020
I had to make two changes to get this working in App Designer when the context menu belongs to ROI objects on a UIAxes.
1. Define uimenu parent as figure handle
m1 = uimenu( 'Label', 'Remove' );
% changed to
m1 = uimenu(app.UIFigure, 'Label', 'Remove' );
2. Define uicontextmenu parent as figure handle
obj.hContextMenu = uicontextmenu( );
% changed to
obj.hContextMenu = uicontextmenu(ancestor(hGraphics,'figure') )
More Answers (3)
Phil
on 4 Oct 2016
There is a simpler answer. You can use "gco" in callbacks called by the context menu to get the handle of the current graphics object that was clicked. Thus the menu can be shared between objects without difficulty and still have different behavior for each object. For example, to delete the specific object clicked, use the callback "@(~,~)delete(gco)".
4 Comments
Kristoffer Walker
on 21 Oct 2020
Folks,
This does not work with AppDesigner. Is there a similar simple solution for AppDesigner? This is a fundamental need for developers. Many thanks to all of you who contribute to knowledge sharing on this forum!!
Kris
Adam Danz
on 21 Oct 2020
Edited: Adam Danz
on 21 Oct 2020
@Kristoffer Walker, that's because the HandleVisibility of uifigures is set to 'off' by default which prevents getting access to the figure handle or any object within the figure.
solution 1: set the uifigure's HandleVisibility to 'callback' or 'on'--(not recommended)
solution 3 (best): instead of relying on the object being 'current', pass the object's handles into the function or use a pre-defined obj handle in the function definition.
래충 강
on 17 Dec 2021
get(app.UIFigure, 'CurrentObject')
It tell you which object called the context menu
1 Comment
Rina Blomberg
on 3 Feb 2023
Edited: Rina Blomberg
on 3 Feb 2023
Thank you!! Exactly the solution I needed.
Walter Roberson
on 3 Aug 2015
You could Label or Tag them differently; http://www.mathworks.com/help/matlab/ref/uicontextmenu.html
3 Comments
Walter Roberson
on 3 Aug 2015
No, notice that the single UiContextMenu callback is used in the example there and that it pulls information out of the Label (in that case) to decide what to do. It is not well-documented in the description of uicontextmenu what the source object is (the first parameter passed in) but you can deduce from the example that it is the menu object rather than the uicontextmenu object
See Also
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!