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.
Related
I need help about nimbus behaviour for JTree and JPopupMenu. I am setting a right click menu to a JTree. If I left click to a node after open the right click menu with another node, clicked node becoming selected. but in nimbus look and feel, a second click needed for select another node. My code is below, you can try it with default look and feel with comment the nimbus part.
public class JTreeDemo {
public static void main(String[] args) {
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
}
}
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root");
rootNode.add(new DefaultMutableTreeNode("Child1"));
rootNode.add(new DefaultMutableTreeNode("Child2"));
rootNode.add(new DefaultMutableTreeNode("Child3"));
DefaultTreeModel model = new DefaultTreeModel(rootNode);
JTree tree = new JTree(model);
tree.addMouseListener(new TreeMouseListener());
JFrame jf = new JFrame();
jf.getContentPane().add(new JScrollPane(tree));
jf.setSize(new Dimension(300, 300));
jf.setVisible(true);
}
}
class TreeMouseListener extends MouseAdapter {
#Override
public void mouseReleased(MouseEvent e) {
if(SwingUtilities.isRightMouseButton(e)) {
JTree tree = (JTree) e.getSource();
TreePath jClickedPath = tree.getPathForLocation(e.getX(), e.getY());
tree.setSelectionPath(jClickedPath);
JPopupMenu menu = new JPopupMenu();
menu.add(new JMenuItem("menu1"));
menu.show(tree, e.getX(), e.getY());
}
}
}
If you print out the pressed, released and clicked mouse events you will see that with the default L&F you get
// right click
tree: pressed
tree: released
tree: clicked
// click on node
tree: pressed
tree: released
tree: clicked
whereas with the Nimbus L&F you get
// right click
tree: pressed
tree: released
tree: clicked
// first click on node, the pressed event is not passed to the listener
tree: released
tree: clicked
// second click on node
tree: pressed
tree: released
tree: clicked
This is the desired behavior of Nimbus popups to consume the event on close. (see the explanation in the bug report #JDK-6770445)
You can change this behavior after setting the L&F.
UIManager.setLookAndFeel(info.getClassName());
UIManager.put("PopupMenu.consumeEventOnClose", false);
edit Snippet to change the default behavior only for a specific JTree
// instruct the JTree not to close the popup
tree.putClientProperty("doNotCancelPopup",
new JComboBox().getClientProperty("doNotCancelPopup"));
// create the popup menu not inside the listener
JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem("menu1"));
// add the listener to the JTree
MouseListener popupListener = new PopupListener(popup);
tree.addMouseListener(popupListener);
Show and hide the popup programmatically
static class PopupListener extends MouseAdapter {
JPopupMenu popup;
PopupListener(JPopupMenu popupMenu) {
popup = popupMenu;
}
#Override
public void mousePressed(MouseEvent e) {
togglePopup(e);
}
#Override
public void mouseReleased(MouseEvent e) {
togglePopup(e);
}
private void togglePopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(), e.getX(), e.getY());
} else if (popup.isVisible()) {
popup.setVisible(false);
}
}
}
There are 2 solutions that you can try without changing nimbus:
1. Simulate second click using Robot class.
Add this to your MouseListener.
if(SwingUtilities.isLeftMouseButton(e) && e.getSource() instanceof JTree) {
Robot bot = null;
try {
bot = new Robot();
} catch (AWTException e1) {
e1.printStackTrace();
}
bot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
}
2. Add TreeSelectionLisitener and same as in the first method use
MouseListener to select the proper node with TreeSelectionListener.
I have a popupmenu like this
final JPopupMenu contextMenu = new JPopupMenu();
final JMenuItem addTask = new JMenuItem("Add Task");
Then i add a MouseListener:
component.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger()) {
contextMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
}
In my Actionlistener for the MenuItem i would like to access the x/y-data from my MouseEvent. Is this possible without saving them in an extra variable? i would like to get something like this:
addTask.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//System.out.println(Mouse.getX()+", "+Mouse.getY());
}
});
If you want to get the mouse coordinate of event which has trggered the popup menu - no it's impossible without to save it. If you want to get the mouse event whcih has triggered the menu item action - yes it's possible: EventQueue.getCurrentEvent(); will return the event (you should check whether this event is a mouse event and if yes - cast it, because the action can also be triggered with key event).
public void actionPerformed(ActionEvent arg0) {
AWTEvent evt = EventQueue.getCurrentEvent();
if (evt instanceof MouseEvent) {
MouseEvent me = (MouseEvent) evt;
}
}
I've been playing about with mouse listeners etc on my tabbedpane but cant seem to get anything going. Trying to make a little menu appears when you right-click a tab which will give you the option to close that tab. Could someone point me in the right direction please
tabbedPane.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e)
{
if(SwingUtilities.isRightMouseButton(e))
{
System.out.print(tabbedPane.getSelectedIndex());
}
}
});
tabbedPane.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e)
{
if(SwingUtilities.isRightMouseButton(e))
{
JPopupMenu menu = new JPopupMenu();
JMenuItem closer = new JMenuItem(new AbstractAction("Close") {
#Override
public void actionPerformed(ActionEvent e) {
tabbedPane.removeTabAt(tabbedPane.getSelectedIndex());
}
});
menu.add(closer);
menu.show(tabbedPane, e.getX(), e.getY());
}
}
});
It might be better to install the menu on the tab component which can be accessed via tabbedPane.getTabComponentAt. The tab component is the component that renders the text tag for the tab. If you wanted to add an X button to the tab, that is where you put it.
I have a JTextField for which I'm hoping to suggest results to match the user's input. I'm displaying these suggestions in a JList contained within a JPopupMenu.
However, when opening the popup menu programmatically via show(Component invoker, int x, int y), the focus is getting taken from the JTextField.
Strangely enough, if I call setVisible(true) instead, the focus is not stolen; but then the JPopupMenu is not attached to any panel, and when minimizing the application whilst the box is open, it stays painted on the window.
I've also tried to reset the focus to the JTextField using requestFocus(), but then I have to restore the caret position using SwingUtilities.invokeLater(), and the invoke later side of things is giving the user a slight margin to mess around with the existing contents / overwrite it / or do other unpredictable things.
The code I've got is effectively:
JTextField field = new JTextField();
JPopupMenu menu = new JPopupMenu();
field.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) {
JList list = getAListOfResults();
menu.add(list);
menu.show(field, 0, field.getHeight());
}
});
Can anyone suggest the best avenue to go down to show the JPopupMenu programmatically whilst preserving the focus on the JTextField?
The technical answer is to set the popup's focusable property to false:
popup.setFocusable(false);
The implication is that the textField has to take over all keyboard and mouse-triggered actions that are normally handled by the list itself, sosmething like:
final JList list = new JList(Locale.getAvailableLocales());
final JPopupMenu popup = new JPopupMenu();
popup.add(new JScrollPane(list));
popup.setFocusable(false);
final JTextField field = new JTextField(20);
Action down = new AbstractAction("nextElement") {
#Override
public void actionPerformed(ActionEvent e) {
int next = Math.min(list.getSelectedIndex() + 1,
list.getModel().getSize() - 1);
list.setSelectedIndex(next);
list.ensureIndexIsVisible(next);
}
};
field.getActionMap().put("nextElement", down);
field.getInputMap().put(
KeyStroke.getKeyStroke("DOWN"), "nextElement");
As your context is very similar to a JComboBox, you might consider having a look into the sources of BasicComboBoxUI and BasicComboPopup.
Edit
Just for fun, the following is not answering the focus question :-) Instead, it demonstrates how to use a sortable/filterable JXList to show only the options in the dropdown which correspond to the typed text (here with a starts-with rule)
// instantiate a sortable JXList
final JXList list = new JXList(Locale.getAvailableLocales(), true);
list.setSortOrder(SortOrder.ASCENDING);
final JPopupMenu popup = new JPopupMenu();
popup.add(new JScrollPane(list));
popup.setFocusable(false);
final JTextField field = new JTextField(20);
// instantiate a PatternModel to map text --> pattern
final PatternModel model = new PatternModel();
model.setMatchRule(PatternModel.MATCH_RULE_STARTSWITH);
// listener which to update the list's RowFilter on changes to the model's pattern property
PropertyChangeListener modelListener = new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("pattern".equals(evt.getPropertyName())) {
updateFilter((Pattern) evt.getNewValue());
}
}
private void updateFilter(Pattern newValue) {
RowFilter<Object, Integer> filter = null;
if (newValue != null) {
filter = RowFilters.regexFilter(newValue);
}
list.setRowFilter(filter);
}
};
model.addPropertyChangeListener(modelListener);
// DocumentListener to update the model's rawtext property on changes to the field
DocumentListener documentListener = new DocumentListener() {
#Override
public void removeUpdate(DocumentEvent e) {
updateAfterDocumentChange();
}
#Override
public void insertUpdate(DocumentEvent e) {
updateAfterDocumentChange();
}
private void updateAfterDocumentChange() {
if (!popup.isVisible()) {
popup.show(field, 0, field.getHeight());
}
model.setRawText(field.getText());
}
#Override
public void changedUpdate(DocumentEvent e) {
}
};
field.getDocument().addDocumentListener(documentListener);
It looks straight forward to me. Add the following
field.requestFocus();
after
menu.add(list);
menu.show(field, 0, field.getHeight());
Of course, you will have to code for when to hide the popup etc based on what is going on with the JTextField.
i.e;
menu.show(field, field.getX(), field.getY()+field.getHeight());
menu.setVisible(true);
field.requestFocus();
You may take a look to JXSearchField, which is part of xswingx
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);
}
}