Does anyone know how to control the display of the tiny little arrow that appears on submenus of JMenu?
Can I change it?
Can I disable it?
Can I move it?
Also, I notice that this arrow doesn't appear on top level JMenus only when they are submenus of other JMenu. This inconsistency annoys me since I have a mix of JMenuItem and JMenu attached to the root of my JMenuBar and so I wish it would always indicate it. Anyway to do this as well?
thanks!
Take a look at the Menu.arrowIcon UI property
(Thanks to AndrewThompson for the test code).
Doining this will effect ALL the menus created AFTER you apply the modifications.
So after you init Look and Feel and before you create any menus call UIManager.getLookAndFeelDefaults().put("Menu.arrowIcon", null);
I'd just like to say I think this is a terrible idea and would highly discourage you from doing it.
this arrow doesn't appear on top level JMenus only when they are submenus of other JMenu.
It seem (monotonously) consistent in its appearance using Metal here.
import javax.swing.*;
public class MenuArrows {
MenuArrows() {
JMenuBar mb = new JMenuBar();
JMenu root1 = new JMenu("Root Menu 1");
JMenu root2 = new JMenu("Root Menu 2");
addSubMenus(root1, 5);
addSubMenus(root2, 3);
mb.add(root1);
mb.add(root2);
JOptionPane.showMessageDialog(null, mb);
}
public void addSubMenus(JMenu parent, int number) {
for (int i=1; i<=number; i++) {
JMenu menu = new JMenu("Sub Menu " + i);
parent.add(menu);
addSubMenus(menu, number-1);
addMenuItems(menu, number);
}
}
public void addMenuItems(JMenu parent, int number) {
for(int i=1; i<=number; i++) {
parent.add(new JMenuItem("Item " + i));
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new MenuArrows();
}
};
SwingUtilities.invokeLater(r);
}
}
Related
I'm using the javax.swing library, and I try to solve this problem:
I have a MenuBar in which I created JMenu, this menu has JCheckBoxMenuItem items. Like this:
//creating objects:
jMenuBar = new javax.swing.JMenuBar();
jMenuAlgorithms = new javax.swing.JMenu();
jCheckBoxSPEA = new javax.swing.JCheckBoxMenuItem();
jCheckBoxNSGAII = new javax.swing.JCheckBoxMenuItem();
jSeparator1 = new javax.swing.JPopupMenu.Separator();
jCheckBoxMenuEnableAll = new javax.swing.JCheckBoxMenuItem();
jCheckBoxMenuDisableAll = new javax.swing.JCheckBoxMenuItem();
//settings and putting them together:
jCheckBoxSPEA.setSelected(true);
jCheckBoxSPEA.setText("SPEA");
jMenuAlgorithms.add(jCheckBoxSPEA);
jCheckBoxNSGAII.setSelected(true);
jCheckBoxNSGAII.setText("NSGAII");
jMenuAlgorithms.add(jCheckBoxNSGAII);
jMenuAlgorithms.add(jSeparator1);
jCheckBoxMenuEnableAll.setSelected(true);
jCheckBoxMenuEnableAll.setText("Enable all");
jCheckBoxMenuEnableAll.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jCheckBoxMenuEnableAllMouseClicked(evt);
}
});
jMenuAlgorithms.add(jCheckBoxMenuEnableAll);
jCheckBoxMenuDisableAll.setText("Disable all");
jMenuAlgorithms.add(jCheckBoxMenuDisableAll);
jMenuBar.add(jMenuAlgorithms);
If the user selects jCheckBoxMenuEnableAll item, I would like to select all the items above the separator. If he selects jCheckBoxMenuDisableAll, I would like to deselect all the items above the separator.
As you can see, I've added mouseClicked action to the jCheckBoxMenuEnableAll item. Now, I would like to do something like this:
private void jCheckBoxMenuEnableAllMouseClicked(java.awt.event.MouseEvent evt) {
for(JCheckBoxMenuItem item : jMenuAlgorithms){
item.setSelected(true);
}
//deselect then jCheckBoxMenuDisableAll, it's not essential for instance
}
But apparently, I can't do the for loop like this, as the menu item isn't an array or Iterable.
Well, just for testing, I had done something very stupid (code below) - I pass all the items in the menu, and if the item is a check box, I make its copy, set ist value to "true" (selected) and then replace the original item by its copy. Very stupid, I know and I absolutely don't want to do like this, however, I didn't find another way to do it. I just wanted to see if this would work. I supposed it should, but it stoll doesn't work. What am I doing wrong? Thank you very, very much for your time.
private void jCheckBoxMenuEnableAllMouseClicked(java.awt.event.MouseEvent evt) {
if(jCheckBoxMenuEnableAll.isSelected()){
for(int i =0; i< jMenuAlgorithms.getItemCount(); i++){ //for all items in the menu, separators included
if(jMenuAlgorithms.getItem(i) instanceof JCheckBoxMenuItem){
JCheckBoxMenuItem item = ((JCheckBoxMenuItem)jMenuAlgorithms.getItem(i));
item.setSelected(true);
jMenuAlgorithms.insert(item, i);
}
}
}
}
I think JPopupMenu#getSubElements() is what you are looking for.
see also: JMenu#getSubElements()
Returns an array of MenuElements containing the submenu for this menu
component. If popup menu is null returns an empty array. This method
is required to conform to the MenuElement interface. Note that since
JSeparators do not conform to the MenuElement interface, this array
will only contain JMenuItems.
import java.awt.*;
import java.util.Arrays;
import java.util.stream.Stream;
import javax.swing.*;
public class MenuSubElementsTest {
public JComponent makeUI() {
JMenu jMenuAlgorithms = new JMenu("MenuAlgorithms");
JMenuItem jCheckBoxMenuEnableAll = new JMenuItem("Enable all");
jCheckBoxMenuEnableAll.addActionListener(e -> {
for (MenuElement me: jMenuAlgorithms.getPopupMenu().getSubElements()) {
System.out.println("debug1: " + me.getClass().getName());
if (me instanceof JCheckBoxMenuItem) {
((JCheckBoxMenuItem) me).setSelected(true);
}
}
//or: getJCheckBoxMenuItem(jMenuAlgorithms).forEach(r -> r.setSelected(true));
});
JMenuItem jCheckBoxMenuDisableAll = new JMenuItem("Disable all");
jCheckBoxMenuDisableAll.addActionListener(e -> {
getJCheckBoxMenuItem(jMenuAlgorithms).forEach(r -> r.setSelected(false));
});
jMenuAlgorithms.add(new JCheckBoxMenuItem("SPEA", true));
jMenuAlgorithms.add(new JCheckBoxMenuItem("NSGAII", true));
jMenuAlgorithms.addSeparator();
jMenuAlgorithms.add(jCheckBoxMenuEnableAll);
jMenuAlgorithms.add(jCheckBoxMenuDisableAll);
JMenuBar jMenuBar = new JMenuBar();
jMenuBar.add(jMenuAlgorithms);
JPanel p = new JPanel(new BorderLayout());
p.add(jMenuBar, BorderLayout.NORTH);
return p;
}
private static Stream<JCheckBoxMenuItem> getJCheckBoxMenuItem(MenuElement p) {
Class<JCheckBoxMenuItem> clz = JCheckBoxMenuItem.class;
return stream(p).filter(clz::isInstance).map(clz::cast);
}
// public static Stream<MenuElement> stream(MenuElement p) {
// return Arrays.stream(p.getSubElements())
// .map(MenuSubElementsTest::stream).reduce(Stream.of(p), Stream::concat);
// }
public static Stream<MenuElement> stream(MenuElement p) {
return Stream.concat(Stream.of(p), Arrays.stream(p.getSubElements())
.peek(me -> System.out.println("debug2: " + me.getClass().getName()))
.flatMap(MenuSubElementsTest::stream));
}
public static void main(String... args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new MenuSubElementsTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
I'm trying to create a JPopupMenu, but for some reason, it doesn't show the text I've set on the JMenuItems. The menu itself works, there are menuitems in it and they are responsive, but the text is not showing. I'm creating the menu like this:
private void createPopupMenu() {
this.popupMenu = new JPopupMenu();
this.addMouseListener(new PopupListener(this));
JMenuItem addPlaceMenuItem = new JMenuItem(SketchPad.ADD_PLACE_POPUP_TEXT);
addPlaceMenuItem.setAction(new PopupAction(ActionType.AddPlace));
this.popupMenu.add(addPlaceMenuItem);
JMenuItem addTransitionMenuItem = new JMenuItem(SketchPad.ADD_TRANSITION_POPUP_TEXT);
addTransitionMenuItem.setAction(new PopupAction(ActionType.AddTransition));
this.popupMenu.add(addTransitionMenuItem);
}
In case it matters, here is the PopupListener:
class PopupListener extends MouseAdapter {
SketchPad pad;
public PopupListener(SketchPad pad)
{
this.pad = pad;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1)
{
this.pad.getController().deselectAllNodes();
}
else
{
maybeShowPopup(e);
}
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
pad.popupPosition = new Point(e.getX(), e.getY());
pad.popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
}
What am I missing here?
but for some reason, it doesn't show the text I've set on the JMenuItems.
addPlaceMenuItem.setAction(new PopupAction(ActionType.AddPlace));
The setAction(...) method reset the properties of the menu item with the properties of the Action. So you need to make sure you set the NAME property of the Action to set the text of the menu item.
So in your case it looks like the value of the NAME property should be:
SketchPad.ADD_PLACE_POPUP_TEXT
Or the other approach is to reset the text of the menu item after you set the Action
JMenuItem addPlaceMenuItem = new JMenuItem( new PopupAction(ActionType.AddPlace) );
addPlaceMenuItem.setText(SketchPad.ADD_PLACE_POPUP_TEXT);
The effect is platform specific. In particular, "In Microsoft Windows, the user by convention brings up a popup menu by releasing the right mouse button while the cursor is over a component that is popup-enabled." Your implementation of mouseReleased() precludes even checking isPopupTrigger(). Instead, handle the selection and check the trigger. A similar approach is shown in GraphPanel in order to handle multiple selection and a context menu.
I Have a trouble, I want to make a minesweeper game, but I,m stuck at this point
the point is, I want that if a the panel that have no boms nearby opened, it it'll open another panel around, I know it has to use listener, but until now I haven't found the right one, so far it just open around the panel that I click but not all the opened panel
`
public class Buttons extends JPanel {
public Mines Mine[][];
public Buttons(){
this.setSize(400,400);
this.setLayout(new GridLayout(10,10));
Mine = new Mines[10][10];
String[][] P = platmain();
buatkotak(P);
//this is the code of listener, but it don't work on every panel that open, but in only work on panel that I click
for(int j=0;j<10;j++){
for(int k=0;k<10;k++){
int b = j;
int c = k;
Mine[j][k].addComponentListener(new java.awt.event.ComponentAdapter() {
#Override
public void componentShown(java.awt.event.ComponentEvent evt) {
if (Mine[b][c].IsOpen()&&(!"Bom".equals(Mine[b][c].Jenisnya()))&&(Mine[b][c].GetTanda()==0)){
if(c>1||c<9||b>1||b<9){
minebuttonActionPerformed(Mine[b][c+1]);
minebuttonActionPerformed(Mine[b][c-1]);
minebuttonActionPerformed(Mine[b+1][c+1]);
minebuttonActionPerformed(Mine[b-1][c-1]);
minebuttonActionPerformed(Mine[b+1][c-1]);
minebuttonActionPerformed(Mine[b-1][c+1]);
minebuttonActionPerformed(Mine[b+1][c]);
minebuttonActionPerformed(Mine[b-1][c]);
}
}
}
});
}
}
}
private void minebuttonActionPerformed(Mines M) {
M.MineLabel.setVisible(true);
M.minebutton.setVisible(false);
} `
How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off? This shouldn't have anything to do with caret, which is what I seem to be finding all over Google. :(
I think my program below meets your requirements exactly, with one possible caveat: you're not allowed to type in the text area. So this would be good for a log viewer, but not an interactive console. The code runs a little long because I have made it into a ready-to-run demo of the approach. I suggest running the program as-is and checking out the behavior. If the behavior works well for you, then invest a little time in studying the code. I have included comments in the code to highlight some of the more important sections.
Update 2013-07-17: You may also want to check out random dude's solution in his separate answer farther down the page. His approach is more elegant than mine.
Also see Swing: Scroll to bottom of JScrollPane, conditional on current viewport location for a potential solution that does not interfere with the caret position.
SCCE source code follows:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class ScrollingJTextAreaExample extends JFrame {
// Worker thread to help periodically append example messages to JTextArea
Timer timer = new Timer();
// Merely informative counter, will be displayed with the example messages
int messageCounter = 0;
// GUI components
JScrollPane jScrollPane;
JTextArea jTextArea;
public ScrollingJTextAreaExample() {
initComponents(); // Boiler plate GUI construction and layout
// Configure JTextArea to not update the cursor position after
// inserting or appending text to the JTextArea. This disables the
// JTextArea's usual behavior of scrolling automatically whenever
// inserting or appending text into the JTextArea: we want scrolling
// to only occur at our discretion, not blindly. NOTE that this
// breaks normal typing into the JTextArea. This approach assumes
// that all updates to the ScrollingJTextArea are programmatic.
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Schedule a task to periodically append example messages to jTextArea
timer.schedule(new TextGeneratorTask(), 250, 250);
// This DocumentListener takes care of re-scrolling when appropriate
Document document = jTextArea.getDocument();
document.addDocumentListener(new ScrollingDocumentListener());
}
// Boring, vanilla GUI construction and layout code
private void initComponents() {
jScrollPane = new javax.swing.JScrollPane();
jTextArea = new javax.swing.JTextArea();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jScrollPane.setViewportView(jTextArea);
getContentPane().add(jScrollPane, java.awt.BorderLayout.CENTER);
setSize(320, 240);
setLocationRelativeTo(null);
}
// ScrollingDocumentListener takes care of re-scrolling when appropriate
class ScrollingDocumentListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void insertUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void removeUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
private void maybeScrollToBottom() {
JScrollBar scrollBar = jScrollPane.getVerticalScrollBar();
boolean scrollBarAtBottom = isScrollBarFullyExtended(scrollBar);
boolean scrollLock = Toolkit.getDefaultToolkit()
.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
if (scrollBarAtBottom && !scrollLock) {
// Push the call to "scrollToBottom" back TWO PLACES on the
// AWT-EDT queue so that it runs *after* Swing has had an
// opportunity to "react" to the appending of new text:
// this ensures that we "scrollToBottom" only after a new
// bottom has been recalculated during the natural
// revalidation of the GUI that occurs after having
// appending new text to the JTextArea.
EventQueue.invokeLater(new Runnable() {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
scrollToBottom(jTextArea);
}
});
}
});
}
}
}
class TextGeneratorTask extends TimerTask {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
String message = (++messageCounter)
+ " Lorem ipsum dolor sit amet, consectetur"
+ " adipisicing elit, sed do eiusmod tempor"
+ " incididunt ut labore et dolore magna aliqua.\n";
jTextArea.append(message);
}
});
}
}
public static boolean isScrollBarFullyExtended(JScrollBar vScrollBar) {
BoundedRangeModel model = vScrollBar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public static void scrollToBottom(JComponent component) {
Rectangle visibleRect = component.getVisibleRect();
visibleRect.y = component.getHeight() - visibleRect.height;
component.scrollRectToVisible(visibleRect);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ScrollingJTextAreaExample().setVisible(true);
}
});
}
}
Little late to this question, but I came up with this solution.
conversationPane = new JTextPane();
final JScrollPane conversationScrollPane = new JScrollPane(conversationPane);
conversationScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = conversationScrollPane.getVerticalScrollBar().getModel();
boolean wasAtBottom = true;
public void adjustmentValueChanged(AdjustmentEvent e) {
if (!brm.getValueIsAdjusting()) {
if (wasAtBottom)
brm.setValue(brm.getMaximum());
} else
wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
});
Seems to work perfectly for my needs. Little explanation: Essentially if the scroll bar is not being moved by a person and the bar was last at the maximum/bottom then reset it to the maximum. If it's being manually adjusted, then check to see if it was adjusted to be at the bottom.
Text Area Scrolling may be of interest.
I have no idea how the scroll lock key affects it. I found the following from the Wikipedia page on Scroll Lock:
Therefore, Scroll Lock can be regarded as a defunct feature in almost all modern programs and operating systems.
So I wouldn't worry about it.
I needed to do the same for a logging text area. The solutions I found on the web did not worked for me (they either stop auto scrolling when logging to much messages quickly, or they blocked the scrollbar at bottom even if you scroll up whith your mouse wheel).
I did it this way :
public static void makeTextAreaAutoScroll(JTextArea textArea) {
// Get the text area's scroll pane :
final JScrollPane scrollPane = (JScrollPane) (textArea.getParent().getParent());
// Disable the auto scroll :
((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Add a listener to the vertical scroll bar :
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
private int _val = 0;
private int _ext = 0;
private int _max = 0;
private final BoundedRangeModel _model = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Get the new max :
int newMax = _model.getMaximum();
// If the new max has changed and if we were scrolled to bottom :
if (newMax != _max && (_val + _ext == _max) ) {
// Scroll to bottom :
_model.setValue(_model.getMaximum() - _model.getExtent());
}
// Save the new values :
_val = _model.getValue();
_ext = _model.getExtent();
_max = _model.getMaximum();
}
});
}
Just use it this way :
makeTextAreaAutoScroll(yourTextArea);
You can test with this piece of code :
new Timer().schedule(new TimerTask() {
#Override
public void run() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String line = "test " + Math.random();
yourTextArea.append(yourTextArea.getText().isEmpty() ? line : "\n" + line);
}
});
}
}, 0, 5);
Now your text area should auto scroll if the scroll bar is at bottom, stop auto scrolling if you move the scroll bar (by dragging the bar or by using the wheel), and auto scroll again if you put the scroll bar at bottom again.
Try this :
JTextArea txt = new JTextArea();
JScrollPane jsp = new JScrollPane(history, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
txt.setCaretPosition(txt.getDocument().getLength()); // do this afeter any event
Hope that helps you
After I read Mike Clark and random dude's solution, I end up with below snippet code.
private boolean doAutoScroll = true;
private JTextPane textPane;
private JScrollPane scrollPane;
public void setup() {
/* Left Panel */
textPane = new JTextPane();
textPane.setPreferredSize(new Dimension(600, 400)); // width, height
/*
* Not update the cursor position after inserting or appending text to the JTextPane.
* [NOTE]
* This breaks normal typing into the JTextPane.
* This approach assumes that all updates to the JTextPane are programmatic.
*/
DefaultCaret caret = (DefaultCaret) textPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
scrollPane = new JScrollPane(textPane);
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Invoked when user select and move the cursor of scroll by mouse explicitly.
if (!brm.getValueIsAdjusting()) {
if (doAutoScroll) brm.setValue(brm. getMaximum());
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
scrollPane.addMouseWheelListener(new MouseWheelListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Invoked when user use mouse wheel to scroll
if (e.getWheelRotation() < 0) {
// If user trying to scroll up, doAutoScroll should be false.
doAutoScroll = false;
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
}
The difference is that it is additionally using MouseWheelListener to update doAutoScroll flag even if user uses mouse wheel to scroll up and down.
We have recently attached a GWT MenuBar to a part of our application for, well, menu purposes.
Basically I want the sub menus to open when you mouse over the top level menu, which is easy enough to do:
menubar.setAutoOpen(true);
I would also like to have the sub menu automatically hide when the user's mouse leaves the sub menu. Ideally with some sort of delay to prevent it vanishing too abruptly, but I'd settle for just the hiding.
This doesn't seem to be built in and the MenuItem object in GWT directly subclasses UIObject which means there isn't a relatively trivial onBrowserEvent() or somewhere to attach mouse listeners. Possibly extending MenuItem and sinking/unsinking events would let me add this behavior, but I am unsure if that is the best approach.
So what would be the best approach to autohiding a GWT submenu?
Thank you.
After much horrible hacking trying to achieve something similar we wrote our own cascading menu as part of the GWT Portlets framework. It displays menu items and submenus from an HTML template looking something like this:
Home
Sub Menu 1
Away
<div id="submenu1">
Hello World
Free Memory
Sub Menu 2
</div>
<div id="submenu2">
Command Demo
Command1
Command2
</div>
The URLs that look like method calls broadcast CommandEvent's. The others trigger a history token change like normal. Have a look at the online demo to see the menu in action.
Here's a fairly complete solution, not perfect, explained after code:
public class MyMenuBar extends Composite {
private class OpenTab implements ScheduledCommand {
private String wid;
public OpenTab(String windowId) {
wid = windowId;
}
#Override
public void execute() {
WinUtl.newAppTab(wid);
}
}
interface MyMenuBarUiBinder extends UiBinder<Widget, MyMenuBar> {}
private static MyMenuBarUiBinder uiBinder =
GWT.create(MyMenuBarUiBinder.class);
#UiField MenuBar mainMenu;
#UiField MenuBar subsMenu;
#UiField MenuItem subsChoice1;
#UiField MenuItem subsChoice2;
#UiField MenuItem subsChoice3;
#UiField MenuBar svcPrvdrMenu;
#UiField MenuItem svcPrvdrChoice1;
#UiField MenuItem svcPrvdrChoice2;
#UiField MenuBar netMgtMenu;
#UiField MenuItem netMgtChoice1;
#UiField MenuBar reportsMenu;
#UiField MenuItem reportsChoice1;
#UiField MenuBar auditsMenu;
#UiField MenuItem auditsChoice1;
#UiField MenuBar securityMenu;
#UiField MenuItem securityChoice1;
#UiField MenuBar helpMenu;
#UiField MenuItem helpChoice1;
private boolean subMenuPopped = false;
private boolean subMenuEntered = false;
private static Type<MouseOverHandler> OVR_EVT = MouseOverEvent.getType();
private static Type<MouseOutHandler> OUT_EVT = MouseOutEvent.getType();
private MouseOverHandler mainOverHandler = new MouseOverHandler() {
#Override
public void onMouseOver(MouseOverEvent event) {
subMenuPopped = true;
}
};
private MouseOutHandler mainOutHandler = new MouseOutHandler() {
#Override
public void onMouseOut(MouseOutEvent event) {
Element e = event.getRelativeElement()
boolean movedUp = (event.getRelativeY(e) < 0);
if ((movedUp && subMenuPopped) || subMenuEntered) {
subMenuPopped = false;
subMenuEntered = false;
mainMenu.closeAllChildren(true);
}
}
};
private MouseOverHandler subOverHandler = new MouseOverHandler() {
#Override
public void onMouseOver(MouseOverEvent event) {
subMenuEntered = true;
}
};
private MouseOutHandler subOutHandler = new MouseOutHandler() {
#Override
public void onMouseOut(MouseOutEvent event) {
subMenuPopped = false;
subMenuEntered = false;
mainMenu.closeAllChildren(true);
}
};
public MyMenuBar() {
initWidget(uiBinder.createAndBindUi(this));
mainMenu.addStyleName("npac-MenuBar");
mainMenu.setAutoOpen(true);
mainMenu.setAnimationEnabled(true);
mainMenu.setFocusOnHoverEnabled(true);
subsChoice1.setScheduledCommand(new OpenTab(Names.Wid.NPA));
mainMenu.addDomHandler(mainOverHandler, OVR_EVT);
mainMenu.addDomHandler(mainOutHandler, OUT_EVT);
addHandlers(subsMenu);
addHandlers(svcPrvdrMenu);
addHandlers(netMgtMenu);
addHandlers(reportsMenu);
addHandlers(auditsMenu);
addHandlers(securityMenu);
addHandlers(helpMenu);
}
private void addHandlers(MenuBar m) {
m.addDomHandler(subOverHandler, OVR_EVT);
m.addDomHandler(subOutHandler, OUT_EVT);
}
}
This handles the case where mouseOver opens subMenu, user then mouses UP, off mainMenu (subMenu closes). It does not handle the mouse moving diagonally down, past either side of subMenu (submenu stays open)
Certainly can be improved, but I just got it to work and wanted to share ;-)
There's no need for horrible hacking or relying on CSS within JAVA to achieve autohiding of a MenuBar with submenus. I created a fully working example of a Parent+Children dropdown menu with mouseover opening and mouseOut closing with explanations of each part for others to use.
The common problem I've witnessed folks having is Running a ((JMenu)e.getSource()).doClick(); on the mouseEntered simulates the click into one of the JMenu parents but can't be simply added to the mouseExited method as the MouseListener needs to be attached to the child MenuItems as well as the JMenu parents. (Which it doesn't do in the normal assignment to the MenuBar - only attaching to the parent JMenu objects).
Additionally, a problem arises due to trying to get the MouseExit listener to fire a "close" method ONLY when the mouse has left the entire Menu structure (ie the Child menu dropdowns).
Below is a fully working answer taken from my live app:
The way I solved the menu close on mouse out was to run a boolean variable "isMouseOut" in the top of the constructor to keep track, and then allocate the MouseListener in a more OO friendly way to keep track of the multiple MouseIn-MouseOut events as a user interacts with the menu. Which calls a separate menuClear method acting upon the state of the boolean "isMouseOut". The class implements MouseListener. This is how its done.
Create an ArrayList adding all the menu items to this array first. Like so:
Font menuFont = new Font("Arial", Font.PLAIN, 12);
JMenuBar menuBar = new JMenuBar();
getContentPane().add(menuBar, BorderLayout.NORTH);
// Array of MenuItems
ArrayList<JMenuItem> aMenuItms = new ArrayList<JMenuItem>();
JMenuItem mntmRefresh = new JMenuItem("Refresh");
JMenuItem mntmNew = new JMenuItem("New");
JMenuItem mntmNormal = new JMenuItem("Normal");
JMenuItem mntmMax = new JMenuItem("Max");
JMenuItem mntmStatus = new JMenuItem("Status");
JMenuItem mntmFeedback = new JMenuItem("Send Feedback");
JMenuItem mntmEtsyTWebsite = new JMenuItem("EtsyT website");
JMenuItem mntmAbout = new JMenuItem("About");
aMenuItms.add(mntmRefresh);
aMenuItms.add(mntmNew);
aMenuItms.add(mntmNormal);
aMenuItms.add(mntmMax);
aMenuItms.add(mntmStatus);
aMenuItms.add(mntmFeedback);
aMenuItms.add(mntmEtsyTWebsite);
aMenuItms.add(mntmAbout);
then iterate over the arrayList at this stage adding a MouseListener using the for() loop:
for (Component c : aMenuItms) {
if (c instanceof JMenuItem) {
c.addMouseListener(ml);
}
}
Now set JMenu parents for the MenuBar:
// Now set JMenu parents on MenuBar
final JMenu mnFile = new JMenu("File");
menuBar.add(mnFile).setFont(menuFont);
final JMenu mnView = new JMenu("View");
menuBar.add(mnView).setFont(menuFont);
final JMenu mnHelp = new JMenu("Help");
menuBar.add(mnHelp).setFont(menuFont);
Then add the dropdown menuItems children to the JMenu parents:
// Now set menuItems as children of JMenu parents
mnFile.add(mntmRefresh).setFont(menuFont);
mnFile.add(mntmNew).setFont(menuFont);
mnView.add(mntmNormal).setFont(menuFont);
mnView.add(mntmMax).setFont(menuFont);
mnHelp.add(mntmStatus).setFont(menuFont);
mnHelp.add(mntmFeedback).setFont(menuFont);
mnHelp.add(mntmEtsyTWebsite).setFont(menuFont);
mnHelp.add(mntmAbout).setFont(menuFont);
Add the mouseListeners to the JMenu parents as a separate step:
for (Component c : menuBar.getComponents()) {
if (c instanceof JMenu) {
c.addMouseListener(ml);
}
}
Now that the child menuItem elements all have their own listeners that are separate to the parent JMenu elements and the MenuBar itself - It is important to identify the object type within the MouseListener() instantiation so that you get the menu auto opening on mouseover (in this example the 3x JMenu parents) BUT ALSO avoids child exception errors and allows clean identification of mouseOUT of the menu structure without trying to monitor where the mouse position is. The MouseListener is as follows:
MouseListener ml = new MouseListener() {
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
isMouseOut = true;
timerMenuClear();
}
public void mouseEntered(MouseEvent e) {
isMouseOut = false;
Object eSource = e.getSource();
if(eSource == mnHelp || eSource == mnView || eSource == mnFile){
((JMenu) eSource).doClick();
}
}
};
The above only simulates the mouse click into the JMenu 'parents' (3x in this example) as they are the triggers for the child menu dropdowns. The timerMenuClear() method calls on the MenuSelectionManager to empty whatever selectedpath point was live at the time of real mouseOUT:
public void timerMenuClear(){
ActionListener task = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(isMouseOut == true){
System.out.println("Timer");
MenuSelectionManager.defaultManager().clearSelectedPath();
}
}
};
//Delay timer half a second to ensure real mouseOUT
Timer timer = new Timer(1000, task);
timer.setInitialDelay(500);
timer.setRepeats(false);
timer.start();
}
It took me a little testing, monitoring what values I could access within the JVM during its development - but it Works a treat! even with nested menus :) I hope many find this full example very useful.
Use this code:
public class MenuBarExt extends MenuBar {
public MenuBarExt()
{
super();
}
#Override
public void onBrowserEvent(Event event)
{
switch (DOM.eventGetType(event))
{
case Event.ONMOUSEOUT:
closeAllChildren(false);
break;
default:
super.onBrowserEvent(event);
break;
}
super.onBrowserEvent(event);
}
}