MATLAB: Multiple objects sharing a context menu

oopuicontextmenu

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

Best Answer

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.