Hello, I have just discovered the beauty of uimenu's and think there brilliant as can unclog a GUI since less button!
However, Im not too sure about reusing the code, for example if I have 3 UITables as below (in reality I have about 10), there seems to be a lot of duplicity. Is there a more elegant way to ahcieve this
Also, what if for one of the tables I wanted the menu to e.g. have one more item thats not in the other menu's?
function startupFcn(app)
fig= app.UIFigure;
cm = uicontextmenu(fig);
m = uimenu(cm,"Text","Delete Row");
m1 = uimenu(cm,"Text","Paste Data");
%m.Text = "Delete Row";
%m1.Text = "Paste Data";
tbl=app.UITable1;
tbl.ContextMenu = cm;
m.MenuSelectedFcn = @app.deleteRow; %create callback. IMPORTANT: You must use app.****
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,tbl);
cm.ContextMenuOpeningFcn = @(src,event)app.toggleVisibility(src,event,m);
%Apply row delete option to other UITABLE2
cm2 = uicontextmenu(fig);
m = uimenu(cm2,"Text","Delete Row");
m1 = uimenu(cm2,"Text","Paste Data");
tbl=app.UITable2;
tbl.ContextMenu = cm2;
m.MenuSelectedFcn = @app.deleteRow; %create callback. IMPORTANT: You must use app.****
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,tbl);
cm.ContextMenuOpeningFcn = @(src,event)app.toggleVisibility(src,event,m);
%Apply row delete option to other UITABLE3
cm3 = uicontextmenu(fig);
m = uimenu(cm3,"Text","Delete Row");
m1 = uimenu(cm3,"Text","Paste Data");
tbl=app.UITable3;
tbl.ContextMenu = cm3;
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,tbl);
and here is one of my functions for example
function deleteRow(app,src,event)
tbl = event.ContextObject;
row = event.InteractionInformation.Row;
tbl.Data(row,:) = [];
end
(and do I need the src & event in the function argument if instead I call it like this:
m.MenuSelectedFcn = @(src,event)app.deleteRow
instead of
m.MenuSelectedFcn = @app.deleteRow;

 채택된 답변

Voss
Voss 2024년 3월 4일
편집: Voss 2024년 3월 4일

0 개 추천

"there seems to be a lot of duplicity. Is there a more elegant way to ahcieve this"
Multiple components (e.g., uitables) can share the same context menu.
"what if for one of the tables I wanted the menu to e.g. have one more item thats not in the other menu's?"
If all uitables share a context menu:
  • have the context menu contain all menu items you need for all uitables
  • when right-clicking on a component, the component's ButtonDownFcn (if defined) executes before the context menu appears, so any logic you need to update the menu items for the specific component can be in the ButtonDownFcn
  • in this case, put code in the ButtonDownFcn to set the "extra" menu item visible if right-clicking on the one specific table and invisible if right-clicking on any other table (the first input to the ButtonDownFcn is the clicked component, so you can use that to know which uitable was clicked on)
If each table has its own context menu: make one context menu have an additional menu item.
"and do I need the src & event in the function argument"
Depends on how the MenuSelectedFcn works.

댓글 수: 16

Jason
Jason 2024년 3월 4일
편집: Jason 2024년 3월 4일
"Multiple components (e.g., uitables) can share the same context menu."
so use just the a single cm and m, m1 and just assign the relevant UITable to cm like this?
tbl=app.UITable1;
tbl.ContextMenu = cm;
tbl=app.UITable2;
tbl.ContextMenu = cm;
tbl=app.UITable3;
tbl.ContextMenu = cm;
"Depends on how the MenuSelectedFcn works."
This is how the document says:
m.MenuSelectedFcn = @app.deleteRow;
function deleteRow(app,src,event)
tbl = event.ContextObject;
row = event.InteractionInformation.Row;
tbl.Data(row,:) = [];
end
But I want to make deleteRow a general function where I don't always pass in scr and event, so was asking if this is the same
m.MenuSelectedFcn = @(src,event)app.deleteRow
and then define the deleteRow function as
function deleteRow(app)
tbl = event.ContextObject;
row = event.InteractionInformation.Row;
tbl.Data(row,:) = [];
end
Voss
Voss 2024년 3월 4일
"so use just the a single cm and m, m1 and just assign the relevant UITable to cm like this?"
Exactly. Or more succinctly:
set([app.UITable1 app.UITable2 app.UITable3],'ContextMenu',cm);
"I want to make deleteRow a general function where I don't always pass in scr and event"
event is used inside the function deleteRow, so it must be passed in as an input argument.
function deleteRow(app,src,event)
tbl = event.ContextObject;
row = event.InteractionInformation.Row;
tbl.Data(row,:) = [];
end
Generally, if src/event are not used, you don't need to pass them in; if they are used, you need to pass them in. That's why it depends on what the actual function does.
Thanks!
doesn't this pass src and event in?
m.MenuSelectedFcn = @(src,event)app.deleteRow
but allows the argument list to be empty?
Also, i didn't realised that I dont have to pass the actual table in the argument as below
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,app.UITable1);
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,app.UITable2);
m1.MenuSelectedFcn = @(src,event)app.TablePasteData(src,event,app.UITable3);
Instead it lookslike I can get hold of the table in the function by:
tbl = event.ContextObject;
If thats correct, thats awesome!
"doesn't this pass src and event in?"
m.MenuSelectedFcn = @(src,event)app.deleteRow;
No. The purpose of that syntax is to ignore src and event.
Example: The anonymous function f below just does disp('a') regardless of what x and y are passed in:
f = @(x,y)disp('a');
f(1,2) % passing x = 1 and y = 2
a
f(3,4) % passing x = 3 and y = 4
a
Voss
Voss 2024년 3월 4일
"it lookslike I can get hold of the table in the function by:"
tbl = event.ContextObject;
That's right.
Jason
Jason 2024년 3월 4일
Great, thanks for your help!
Voss
Voss 2024년 3월 4일
You're welcome!
Hmm, I got it all working for UITables, and tried to do the same for UIAxes, but its not working and I can't see my error: Is not picking up the axes from that trick of using ax = event.ContextObject;
%Create Menu for UIAXES
cm2 = uicontextmenu(fig);
m = uimenu(cm2,"Text","LineScans"); m.ForegroundColor='b';
m1 = uimenu(cm2,"Text","Brightness"); m1.ForegroundColor='r';
%Apply to all UIAxes in one go like above
set([app.UIAxes app.UIAxes2],'ContextMenu',cm2);
%Sub Menus
sbm1=uimenu(m1,"Text","Brighter x 2", "MenuSelectedFcn",@(src,event)app.Brighter(src,event,0.5));
sbm2=uimenu(m1,"Text","Dimmer x 2", "MenuSelectedFcn",@(src,event)app.Brighter(src,event,2));
And this is my function
function Brighter(app,src,event,factor)
factor
%src
%properties(event)
ax=event.ContextObject;
ax.CLim(2)= ax.CLim(2)*factor;
end
This is the error:
Unrecognized method, property, or field 'ContextObject' for class 'matlab.ui.eventdata.ActionData'.
Error in LuthienAlignmentSoftware/Brighter (line 2854)
ax=event.ContextObject;
Error in LuthienAlignmentSoftware>@(src,event)app.Brighter(src,event,0.5) (line 2925)
sbm3=uimenu(m1,"Text","Brighter x 2", "MenuSelectedFcn",@(src,event)app.Brighter(src,event,0.5));
Error using matlab.ui.internal.controller.WebMenuController/fireMenuSelectedEvent
Error while evaluating Menu Callback.
Voss
Voss 2024년 3월 4일
"Unrecognized method, property, or field 'ContextObject' for class 'matlab.ui.eventdata.ActionData'."
Apparently the event struct doesn't contain the same stuff when clicking a uiaxes as it does when clicking a uitable.
"The ContextMenuOpeningFcn and MenuSelectedFcn callbacks have additional event data when the callback is associated with a context menu on a UI component."
ContextObject is new in R2023b, and note that says it applies to context menus on a UI component; I guess it doesn't apply to axes/uiaxes.
Jason
Jason 2024년 3월 4일
Oh, OK, any idea how to get the uimenu calling axis into the function without actually passing in the actual uiaxes?
Voss
Voss 2024년 3월 4일
Actually, I think passing the uiaxes as an argument to the uimenu callback is a good approach.
You'll have to define the uimenu callback to take a uiaxes as an additional argument, and of course which uiaxes it is is not known until the user clicks on the uiaxes, so each uiaxes needs to have a ButtonDownFcn that sets up the uimenu's callback properly.
To illustrate more concretely how it works, I'm attaching a simple app that does it. There are two uiaxes in the app, each with the same context menu with one menu item; the menu item's callback displays the uiaxes title string to the command line so you can verify that the uiaxes it's using is in fact the one that was clicked on.
Jason
Jason 2024년 3월 5일
편집: Jason 2024년 3월 5일
Thanks Vos, I've taken a look and see that you need to assign the menu item "m" to a private property.
So if I have lots of menu items, does this mean I have to assign them all, i.e.
properties (Access = private)
m % uimenu
m1 % uimenu
m2 % uimenu
etc.
end
Or is there a way to assign them all in one go?
Also, do I have to assign the sub menu's too. If so its starting to expand the code which was my main reason for asking to help - to reduce the coding.
Voss
Voss 2024년 3월 5일
You need to be able to access the uimenu in order to update its MenuSelectedFcn in the axes ButtonDownFcn. In an app, the simplest way to make something accessible is to make it an app property.
If you create the uicontextmenu and uimenus in Design View of App Designer, then they each become separate app properties automatically, so maybe you want to do it that way.
However, they do not need to be separate properties. If you create them programmatically (e.g., in the startupFcn the way I've shown), then you can easily have them all contained in a single app property, say a structure containing all the handles (uimenus and whatever else) you need.
See the two attached apps for examples of the two approaches - both apps do the same thing. Each app has a few different uimenus. In one the menus were created in Design View and are separate app properties; in the other the menus are created in the startup function and are all contained in the app property "h".
As for sub-menus (i.e., any uimenu that is the child of another uimenu), just like the menus themselves (i.e., any uimenu that is the child of a uicontextmenu), you only need to access them if you need to be able to update them on-the-fly as the app is running. It depends on what you want to do, but in this case so far, you'd only need to access those that are going to have a MenuSelectedFcn assigned to them, so that's typically the uimenus that don't have any child uimenus.
Jason
Jason 2024년 3월 5일
thats awesome, thankyou very much for your effort!
Voss
Voss 2024년 3월 5일
You're welcome!

댓글을 달려면 로그인하십시오.

추가 답변 (0개)

카테고리

도움말 센터File Exchange에서 Interactive Control and Callbacks에 대해 자세히 알아보기

제품

릴리스

R2023b

질문:

2024년 3월 4일

댓글:

2024년 3월 5일

Community Treasure Hunt

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

Start Hunting!

Translated by