Last week I explained how to customize Matlab’s menu items using some undocumented tricks that do not need Java. Today I will show how using just a tiny bit of Java magic powder we can add much more complex customizations to menu items. Menu customizations are explored in depth in section 4.6 of my book.
Accessing the underlying Java object
Matlab menus (uimenu) are basically simple wrappers for the much more powerful and flexible Java Swing JMenu
and JMenuItem
on which they are based. Many important functionalities that are available in Java menus are missing from the Matlab uimenus.
Getting the Java reference for the figure window’s main menu is very easy:
jFrame = get(handle(hFig),'JavaFrame'); try % R2008a and later jMenuBar = jFrame.fHG1Client.getMenuBar; catch % R2007b and earlier jMenuBar = jFrame.fFigureClient.getMenuBar; end |
Note that we have used the figure handle’s hidden JavaFrame property, accessed through the reference’s handle() wrapper, to prevent an annoying warning message.
There are many customizations that can only be done using the Java handle: setting icons, several dozen callback types, tooltips, background color, font, text alignment, and so on. etc. Interested readers may wish to get/set/inspect/methodsview/uiinspect the jSave
reference handle and/or to read the documentation for JMenuItem
. Some useful examples are provided below.
Dynamic menu behavior
As a first example of Java-based customization, let us add DHTML-like behavior to the menu, such that the menu items will automatically be displayed when the mouse hovers over the item, without waiting for a user mouse click. First, get the jMenuBar
reference as described above. Now, set the MouseEnteredCallback to automatically simulate a user mouse click on each menu item using its doClick() method. Setting the callback should be done separately to each of the top-level menu components:
for menuIdx = 1 : jMenuBar.getComponentCount jMenu = jMenuBar.getComponent(menuIdx-1); hjMenu = handle(jMenu,'CallbackProperties'); set(hjMenu,'MouseEnteredCallback','doClick(gcbo)'); end |
Note that using this mechanism may be awkward if the top-level menu does not have a sub-menu but is rather a stand-alone menu item. For example, if the top-level menu-item “Help” is a stand-alone menu button (i.e., there are no help menu-items, just the single Help item), then moving the mouse over this item will trigger the help event, although the user did not actually click on the item. To prevent this behavior, we should modify the code snippet above to only work on those menu items that have sub-items.
Custom accelerator shortcut keys
As another example, Matlab automatically assigns a non-modifiable keyboard accelerator key modifier of
% File main menu is the first main menu item => index=0 jFileMenu = jMenuBar.getComponent(0); % Save menu item is the 5th menu item (separators included) jSave = jFileMenu.getMenuComponent(4); %Java indexes start with 0! inspect(jSave) % just to be sure: label='Save' => good! % Finally, set a new accelerator key for this menu item: jAccelerator = javax.swing.KeyStroke.getKeyStroke('alt shift S'); jSave.setAccelerator(jAccelerator); |
That is all there is to it – the label is modified automatically to reflect the new keyboard accelerator key. More info on setting different combinations of accelerator keys and modifiers can be found in the official Java documentation for KeyStroke
.
Note that the Save menu-item reference can only be retrieved after opening the File menu at least once earlier; otherwise, an exception will be thrown when trying to access the menu item. The File menu does NOT need to remain open – it only needs to have been opened sometime earlier, for its menu items to be rendered. This can be done either interactively (by selecting the File menu) or programmatically:
% Simulate mouse clicks to force the File main-menu to open & close jFileMenu.doClick; % open the File menu jFileMenu.doClick; % close the menu % Now the Save menu is accessible: jSave = jFileMenu.getMenuComponent(4); |
Tooltip and highlight
For some unknown reason, MathWorks did not include a tooltip property in its Matlab menu handle, contrary to all the other Matlab GUI components. So we must use the Java handle, specifically the ToolTipText property:
jSave.setToolTipText('modified menu item with tooltip'); |
Java menu items also contain a property called Armed, which is a logical value (default=false). When turned on, the menu item is highlighted just as when it is selected (see the Save As… menu item in the screenshot above). On a Windows system, this means a blue background:
jSave.setArmed(true); |
When the item is actually selected and then de-selected, Armed reverts to a false (off) value. Alternating the Armed property value can achieve an effect of flashing the menu item.
Callbacks
In addition to the standard Swing control callbacks discussed in an earlier article, menu items possess several additional callbacks that are specific to menu items, including:
- ActionPerformedCallback – fired when the menu item is invoked
- StateChangedCallback – fired when the menu item is selected or deselected
- MenuDragMouseXXXCallback (XXX=Dragged/Entered/Exited/Released) – fired when the menu item is dragged, for the corresponding event
- MenuKeyXXXCallback (XXX=Pressed/Released/Typed) – fired when a keyboard click event occurs (the menu item’s accelerator was typed)
Using these callbacks, together with the 30-odd standard Swing callbacks, we can control our menu’s behavior to a much higher degree than possible using the standard Matlab handle.
Next week, I will conclude this mini-series with an article explaining how to customize menu item icons.
Hi!
I’m trying to use your solutions, but they do not work (I’m using MATLAB 2008b)…
It crashes at jEditMenu = jMenuBar.getComponent(0);…
The strange thing is that if I put a breakpoint there, and I execute the following steps, THEY WORK!! I’m really puzzled. I fear there’s some lack of alignment between the MATLAB and Java interpreters when I run the program, or something stranger.
Ciao and thanks!
Just one correction: I’m using 2010a. I also tried to remove the option ‘MenuBar’, ‘none’, but with just slightly better results. It executes cycle on children of MenuBar; however, when I try to pick sons of first item (menu ‘File’) they are zero!! This does not happen if I execute the instructions one by one with the debugger.
@Attilio – the reason for this was explained in the EDT article. Basically, it takes time to display the figure and it may not yet be ready by the time you run the Java functions. The solution is either to add a call to drawnow (possibly with an additional pause(0.1)) right after your ChangeIcon() call, or use the javaMethodEDT/awtinvoke functions when running the Java functionality. For more details see the EDT article.
Hi! Thanks a lot for the suggestions, now it works… though partially. Perhaps I’m missing something else. The problem now is when I attempt to change items of the menu, it keeps saying “Edit” has 0 components…
The last call jEditMenu.getComponentCount returns 0…
@Attilio – Now you forgot to call doClick() as explained in the article. If you need any further customizations or help with your program please contact me via email (altmany at gmail).
In the future, please reply on the original comment thread, rather than creating a new thread.
Hi,
I’m having trouble with the doClick-method…
I have no problem finding the menu and getting it to open with menu.doClick().
But it won’t close on the next menu.doClick() call.
I already inserted drawnow’s and pause(1) statements between the doClick-calls, but that doesn’t help either – the menu simply remains open, even when I have a loop running à la:
Are there other ways to force the menu to hide again?
Late answer but maybe useful for others with the same problem. Issue the following to close any open menus:
Hi!
In your post you play with the figure window
I don’t understand your question…
Oops – it seems as though most of my question got lost – sorry for that. Here it is again:
In your post you play with the main menu of the figure window. I would like to do similar stuff with a uicontextmenu (e.g. adding icons). However, I have so far been unsuccessful in finding the according Java reference for the context menu. Could you help me with that, please?
@Stephan – as far as I can tell, Matlab’s context-menus are not implemented in Java and so they cannot be modified in the same manner. Instead of using Matlab’s context menus, you can easily implement your own Java-based context-menu (which is much more customizable), as I have shown here: http://undocumentedmatlab.com/blog/setting-listbox-mouse-actions/
The setAccelerator method throws an error:
http://docs.oracle.com/javase/7/docs/api/javax/swing/JMenu.html
@Thierry – you have looked at the incorrect javadoc:
JMenu
is the top-level menu (File/Edit/View/… – thejFileMenu
item in my article above) and for that you cannot set an accelerator – it is automatically set by the operating system, e.g. <Alt>-F to activate the File main menu.However, accelerators work just fine for menu items that are not top-level (
JMenuItem
, or rathercom.mathworks.mwswing.MJCheckBoxMenuItem
which extends it). If you try the code you’ll see that it works just fine. The relevant javadoc for these menu items is: http://docs.oracle.com/javase/7/docs/api/javax/swing/JMenuItem.html#setAccelerator(javax.swing.KeyStroke)sorry and thank you Yair for the prompt reply. I was using
JMenu.getItem
instead ofjMenu.getMenuComponent
as you advised. It works as you advise.