Customizing menu items part 2

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 , while JMenus allow any combination of Alt/Ctrl/Shift/Meta (depending on the platform). Let us modify the default File/Save accelerator key from ‘Ctrl-S’ to ‘Alt-Shift-S’ as an example. We need a reference for the “Save” menu item. Note that unlike regular Java components, menu items are retrieved using the getMenuComponent() method and not getComponent():

% 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.

Modification of menu item accelerator and tooltip

Modification of menu item accelerator and tooltip

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.

Categories: GUI, Java, Medium risk of breaking in future versions, Undocumented feature

Tags: , , , , ,

Bookmark and SharePrint Print

14 Responses to Customizing menu items part 2

  1. Attilio Funel says:

    Hi!

    I’m trying to use your solutions, but they do not work (I’m using MATLAB 2008b)…

    myGUI = figure('Visible','on','Name','Urania alternativa DB Utility',...
                        'MenuBar','none', ...            %No menu bar 
                        'NumberTitle', 'off', ...
                        'Position',[360,500,450,285]);
    set(myGUI,'CloseRequestFcn',@EditMenuExitFunction);
    ChangeIcon(myGUI);
     
    jFrame = get(handle(myGUI),'JavaFrame');
    try
        jMenuBar = jFrame.fHG1Client.getMenuBar;
    catch
    end
     
    editMenu = uimenu(myGUI,'Label','Edit');
    jEditMenu = jMenuBar.getComponent(0);
    hjMenu = handle(jEditMenu,'CallbackProperties');
    set(hjMenu,'MouseEnteredCallback','doClick(gcbo)');
    editMenuOpen = uimenu(editMenu, 'Label', 'Open DB', ..., 
                        'Accelerator', 'O',...
                        'Callback', @EditMenuOpenFunction);

    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!

    • Attilio Funel says:

      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.

    • Attilio Funel says:

      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…

      editMenu = uimenu(myGUI,'Label','Edit');
      editMenuOpen = uimenu(editMenu, 'Label', 'Open DB', ..., 
                          'Accelerator', 'O',...
                          'Callback', @EditMenuOpenFunction);
      drawnow;
      editMenuSave = uimenu(editMenu, 'Label', 'Save DB', ...
                          'Accelerator', 'S', ...
                          'Callback', @EditMenuSaveFunction, ...
                          'Enable', 'off');
       
      %Se ho acceduto come attiliosfunel abilito "Save"
      if(strcmp(accessedUserName, 'attiliosfunel') == true)
          set(editMenuSave,'Enable','on');
      end
      drawnow;
      editMenuClose = uimenu(editMenu, 'Label', 'Close DB', ...
                          'Callback', @EditMenuCloseFunction, ...
                          'Accelerator', 'C');
      drawnow;
      editMenuExit = uimenu(editMenu, 'Label', 'Exit', ...
                          'Accelerator', 'X', ...
                          'Callback', @EditMenuExitFunction);
      drawnow;
      pause(0.1);
       
      %Handle della finestra
      jFrame = get(handle(myGUI),'JavaFrame');
      try
         jMenuBar = jFrame.fHG1Client.getMenuBar;
      catch ERROR_GET_HANDLE_WINDOW
      end
       
      jEditMenu = jMenuBar.getComponent(0);
      jEditMenu.getComponentCount

      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.

  2. sebbo says:

    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:

    while true
        menu.doClick()
        pause(1);
    end

    Are there other ways to force the menu to hide again?

    • Roger Svensson says:

      Late answer but maybe useful for others with the same problem. Issue the following to close any open menus:

      javax.swing.MenuSelectionManager.defaultManager().clearSelectedPath();
  3. Stephan Heise says:

    Hi!

    In your post you play with the figure window

  4. Stephan Heise says:

    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?

  5. Thierry Dalon says:

    The setAccelerator method throws an error:
    http://docs.oracle.com/javase/7/docs/api/javax/swing/JMenu.html

    public void setAccelerator(KeyStroke keyStroke)
    setAccelerator is not defined for JMenu. Use setMnemonic instead.
    Overrides:
    setAccelerator in class JMenuItem
    Parameters:
    keyStroke - the keystroke combination which will invoke the JMenuItem's actionlisteners without navigating the menu hierarchy
    Throws:
    Error - if invoked -- this method is not defined for JMenu. Use setMnemonic instead
    
    • @Thierry – you have looked at the incorrect javadoc: JMenu is the top-level menu (File/Edit/View/… – the jFileMenu 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 rather com.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)

    • Thierry Dalon says:

      sorry and thank you Yair for the prompt reply. I was using JMenu.getItem instead of jMenu.getMenuComponent as you advised. It works as you advise.

Leave a Reply


Your email address will not be published. Required fields are marked *