JPopupPanel closed when click on its JPopupMenu - java

I have a JPopupPanel showing up when a button is clicked.
This JPopupPanel has a JPopupMenu which shows up with the mouse right click, showing different options.
When left button pressed to choose one of this options in the PopupMenu, the PopupPanel closes leaving the PopupMenu by itself for a moment, and when the button is released, the PopupMenu also dissapears (as expected), but the action cannot be seen in the PopupPanel since it is already closed.
How can I avoid the JPopupPanel to close when choosing one of the options of the JPopupMenu?
Thanks

Sorry I do not have experience using JPopupPanel. Thus, I can only offer a simple hack.
I would suspect that you can do something similar as in my example (below) where I 'ignore' hiding popup menu on an option select.
My approach here is to reshow the popup menu on an option selection. So fallowing this maybe you can try and reshow your panel when it hides.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
public class JPopupMenuIgnoreOptionCloseOnClick extends JPanel
{
private static final long serialVersionUID = 1L;
private JPopupMenu popup = new JPopupMenu("Oi I am popup");
private MouseListener mL = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
System.out.println("mL mousePressed e.isP="+e.isPopupTrigger());
super.mousePressed(e);
showPopup(e);
}
#Override
public void mouseReleased(MouseEvent e)
{
System.out.println("mL mouseReleased e.isP="+e.isPopupTrigger());
super.mouseReleased(e);
showPopup(e);
}
private void showPopup(MouseEvent e)
{
if(e.isPopupTrigger())
{
prevLocation = e.getPoint();
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
};
private Point prevLocation = null;
private MouseListener optionML = new MouseAdapter()
{
#Override
public void mouseReleased(MouseEvent e)
{
System.out.println("optionML mouseReleased prevLocation="+prevLocation);
e.consume();
popup.show(JPopupMenuIgnoreOptionCloseOnClick.this, prevLocation.x,prevLocation.y);
}
};
public JPopupMenuIgnoreOptionCloseOnClick()
{
addMouseListener(mL);
JMenuItem opt1 =new JMenuItem("Option 1");
opt1.addMouseListener(optionML);
popup.add(opt1);
JMenuItem opt2 =new JMenuItem("Option 2");
opt2.addMouseListener(optionML);
popup.add(opt2);
JMenuItem opt3 =new JMenuItem("Option 3");
opt3.addMouseListener(optionML);
popup.add(opt3);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPopupMenuIgnoreOptionCloseOnClick p = new JPopupMenuIgnoreOptionCloseOnClick();
p.setPreferredSize(new Dimension(400, 400));
JPanel contentPane = new JPanel();
contentPane.setBackground(Color.CYAN);
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}

I came across this problem myself when installing a custom JPopupMenu on a JCommandButton.
For your JCommandButton I found this to be helpful in preventing premature disposal of the parent popup:
this.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS, true);
If what you are looking for is instead that upon making a JPopupMenu JMenuItem selection, the parent popup panel will stay open, you have a couple options. The problem stems from JPopupMenu's broken link in the ancestry container chain, on which the UI relies. Instead of getParent(), you need to return getInvoker().
1:
modify the library source in BasicPopupPanelUI.WindowTracker.eventDispatched(). Either change the SwingUtilities.getAncestorOfClass() calls to use SwingXUtilities.getAncestorOfClass() which accounts for this special case. Or implement the logic yourself.
if(parent instanceof JPopupMenu) parent = ((JPopupMenu)parent).getInvoker()
2:
Add this code to the widget (CustomButton?)
final JPopupMenu popper = new JPopupMenu(){ //hack
#Override public Container getParent(){
StackTraceElement ste = Thread.currentThread().getStackTrace()[2];
if(ste.getClassName().equals(SwingUtilities.class.getName()))
return CustomButton.this.getParent();
return super.getParent();
}
};
I chose #2, since I have issues with modifying 3rd party libraries.

Related

How to hide a JPopupMenu by pressing a button?

I'm making a program that has a popup menu with two buttons, one of which should close the popup menu, but I have no idea how to do that and googling hasn't gone too well.
I've tried using popup.hide() but then the menu wouldn't come back, despite doing so when I tried just moving the popup. It also required me to put a SuppressWarning in that case and it took a few seconds for it to close at all. Is there any better way of doing it?
I'm not sure what kind of code is relevant, but here's the relevant buttons and their roles in this(I skipped all the creating the GUI parts that didn't seem relevant, everything looks good and I know that the buttons are working):
package test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
interface CustomButton {
JButton create();
void react(JPopupMenu popup, JFrame frame);
}
class ErrandsButton implements CustomButton {
private JButton errands = new JButton("Errands");
public JButton create() {
return errands;
}
public void react(JPopupMenu popup, JFrame frame) {
errands.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
popup.show(frame, 120, 65);
}
});
}
}
class Test {
static JFrame frame = new JFrame("List");
static CustomButton errands = new ErrandsButton();
static JButton cancelTask = new JButton("Cancel");
static JPopupMenu popup = new JPopupMenu();
static void cancelTask() {
cancelTask.addActionListener(new ActionListener() {
#SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
popup.hide();
}
});
}
public static void main(String args[]) {
createInterface();
cancelTask();
errands.react(popup, frame);
}
static void createInterface() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JPanel popup1 = new JPanel();
JPanel button = new JPanel();
popup1.add(cancelTask);
popup.add(popup1);
frame.add(popup);
button.add(errands.create());
frame.getContentPane().add(BorderLayout.CENTER, button);
frame.setVisible(true);
}
}
Use popup.setVisible(true) and popup.setVisible(false).
frame.add(popup); is the problem. Do not add a JPopupMenu to a Container. Instead, use setComponentPopupMenu.
Alternatively, you could do the work yourself by adding a MouseListener whose mousePressed, mouseReleased and mouseClicked methods call isPopupTrigger and show. (It is vital that you do this in all three of those methods—different platforms have different conditions for showing popup menus.)
But really, using setComponentPopupMenu is easier.

JComboBox popup appears and hide immediately when clicking on its border (Bad User Experience)

When you have a swing JComboBox and click on its border, the popup appears and disappears immediately. When I say click, I mean press the left button of the mouse and release immediately.
It may be considered a bad user experience because no user would expect it to happen. Any user would expect one of the following behaviors when clicking on a border of a combobox:
The popup to open and remain opened,
Or it not to open at all.
Surely no user would expect the popup to be opened and closed immediately.
The user does not click on the border on purpose. But it may happen frequently when the combobox is small and he tries to click on it quickly.
In the year 2000 somebody registered this behavior as a bug in openjdk site: https://bugs.openjdk.java.net/browse/JDK-4346918
They've recognized it as a bug, but closed it with the resolution: "Won't fix", with the following observation:
I've been able to reproduce the problem but it's not significant so
I'm not going to fix it. The problem is that the drop down portion of
the combo box will hide when the mouse is released after clicking on
the border. This bug doesn't have a very major impact.
I agree with them, that it doesn't have a very major impact. But I still think that it leads to a bad user experience and I would like to know if there is a simple workaround to make the popup either to remain opened or not to open at all when the user clicks on its border.
The described behavior can be reproduced by clicking the left mouse button on the border of any JComboBox. See below a simple code where it can be reproduced:
import java.awt.FlowLayout;
import javax.swing.*;
public class JComboBoxUX{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JComboBox<String> combobox = new JComboBox<String>(
new String[]{"aaaaaaaaaa","bbbbbbbb","ccccccccc"});
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
panel.add(combobox);
JFrame frame = new JFrame("JComboBox UX");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setSize(300, 150);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
The problem seems to be in:
class BasicComboPopup extends ... {
private Handler getHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
private class Handler implements ... MouseListener ... {
public void mouseReleased(MouseEvent e) {
//...
Component source = (Component)e.getSource();
Dimension size = source.getSize();
Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
if ( !bounds.contains( e.getPoint() ) ) {
//...
comboBox.setPopupVisible(false);
}
}
}
}
By subtracting one from size.width and size.height, the mouse falls outside of the bounds of the arrow button, and the popup menu is hidden.
Fixing the issue is problematic. The Handler class is private, so we can't extend it, the getHandler() is private, so we can't override that in BasicComboPopup either.
One could extend MetalComboBoxUI and override createPopup() to return a custom ComboPopup, such as one extending BasicComboPopup but extending createMouseListener() to return a similar class to the Handler above, but without the subtract ones.
Oh, and do the same thing for each LAF you wish to support. Yuk.
Attacking the problem from the other direction, one could extend the MetalComboBoxButton (which is returned by e.getSource()) and override the getSize() method to return a dimension one pixel larger in both directions, when the menu is displayed. Of course, you'd still need to extend and override the MetalComboBoxUI to create and install this custom button.
And again, you'd need to do the same thing for each LAF you wish to support. Again, yuk.
Unfortunately, it does not appear that Swing has the needed hooks to easily override the needed functionalities, and has marked various classes as private internal implementation details, preventing their reuse (in order to prevent breakage later if they want to change the internals).
AJNeufeld's suggestion worked perfectly. Thank you!
Below is the code, if someone needs it.
JComboBoxGoodBorder.java:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;
public class JComboBoxGoodBorder<T> extends JComboBox<T> {
public JComboBoxGoodBorder(){
super();
}
public JComboBoxGoodBorder(ComboBoxModel<T> aModel){
super(aModel);
}
public JComboBoxGoodBorder(T[] items){
super(items);
}
public JComboBoxGoodBorder(Vector<T> items){
super(items);
}
#Override
public void updateUI(){
setUI(MetalComboBoxUIGoodBorder.createUI(this));
}
private static class MetalComboBoxUIGoodBorder extends MetalComboBoxUI {
public static ComponentUI createUI(JComponent c) {
return new MetalComboBoxUIGoodBorder();
}
#Override
protected ComboPopup createPopup() {
return new BasicComboPopup(comboBox) {
#Override
protected MouseListener createMouseListener(){
return new MouseAdapter(){
#Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == list) {
return;
}
if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
return;
if ( comboBox.isEditable() ) {
Component comp = comboBox.getEditor().getEditorComponent();
if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
comp.requestFocus();
}
}
else if (comboBox.isRequestFocusEnabled()) {
comboBox.requestFocus();
}
togglePopup();
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getSource() == list) {
if (list.getModel().getSize() > 0) {
// JList mouse listener
if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
comboBox.setSelectedIndex( list.getSelectedIndex() );
} else {
comboBox.getEditor().setItem( list.getSelectedValue() );
}
}
comboBox.setPopupVisible(false);
// workaround for cancelling an edited item (bug 4530953)
if (comboBox.isEditable() && comboBox.getEditor() != null) {
comboBox.configureEditor(comboBox.getEditor(),
comboBox.getSelectedItem());
}
return;
}
// JComboBox mouse listener
Component source = (Component)e.getSource();
Dimension size = source.getSize();
Rectangle bounds = new Rectangle( 0, 0, size.width, size.height);
if ( !bounds.contains( e.getPoint() ) ) {
MouseEvent newEvent = convertMouseEvent( e );
Point location = newEvent.getPoint();
Rectangle r = new Rectangle();
list.computeVisibleRect( r );
if ( r.contains( location ) ) {
if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
comboBox.setSelectedIndex( list.getSelectedIndex() );
} else {
comboBox.getEditor().setItem( list.getSelectedValue() );
}
}
comboBox.setPopupVisible(false);
}
hasEntered = false;
stopAutoScrolling();
}
};
}
};
}
}
}
Test.java:
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JComboBoxGoodBorder<String> combobox = new JComboBoxGoodBorder<String>(
new String[]{"aaaaaaaaaa","bbbbbbbb","ccccccccc"});
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
panel.add(combobox);
JFrame frame = new JFrame("JComboBox Good Border");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Swing persistent popup

I need to display a swing popup with my custom component. The popup should stay visible, until I hide it myself, but shouldn't get focus.
I have a code written by some other developer that does it in the following way:
popupMenu = new JPopupMenu();
popupMenu.add(myCustomComponent, BorderLayout.CENTER);
popupMenu.setFocusable(false);
popupMenu.setVisible(true);
popupMenu.show(parentComponent, x, y);
This seems to work, but has a bug - when the popup is visible, first mouse click outside the component is consumed by the popup. So I need to click twice to set focus to another component.
How can I fix it? Or what is correct way to make the popup?
UPDATE
At last I've managed to reproduce my problem in short code fragment. Thanks to Guillaume Polet for giving me a starting point.
Here's the code:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class TestJPopup {
protected void initUI() {
JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField textField = new JTextField("Some text field");
frame.add(textField, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(200, 100);
frame.setVisible(true);
final JPopupMenu popup = new JPopupMenu();
popup.add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
BorderLayout.NORTH);
popup.setFocusable(false);
popup.setVisible(true);
popup.show(textField, 60, 60);
// I want to activate popup when user clicks in the text field
textField.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (popup != null) {
popup.show(textField, 60, 60);
}
}
});
}
public static void main(String[] args) throws Exception {
Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
Thread.currentThread().getContextClassLoader());
LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
UIManager.setLookAndFeel(feel);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestJPopup().initUI();
}
});
}
}
Two critical moments:
Windows look and feel used (with default not reproducible)
Mouse listener attached to text field in main frame
Not an answer, but just an example SSCCE in which I can't currently reproduce the behaviour you described. Maybe start from this code, try to reproduce the error and the edit your post with modified non-working code.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class TestJPopup {
protected void initUI() {
JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel leftLabel = new JLabel("Left");
frame.add(leftLabel, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(500, 400);
frame.setVisible(true);
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new JLabel("<html>A Custom<br>component<br>made to<br> simulate <br>your custom component</html>"),
BorderLayout.NORTH);
JTextField textfield = new JTextField(30);
popupMenu.add(textfield);
popupMenu.setFocusable(false);
popupMenu.setVisible(true);
popupMenu.show(leftLabel, 20, 20);
// Let's force the focus to be in a component in the popupMenu
textfield.requestFocusInWindow();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestJPopup().initUI();
}
});
}
}
Not a solution, but:
Looks like a bug to me, even a plain componentPopup exhibits the same mis-behaviour (in winLAF and Nimbus, not in Metal):
JTextField field = new JTextField("some popup owner");
JPopupMenu menu = new JPopupMenu();
menu.add("dummy");
field.setComponentPopupMenu(menu);
Action action = new AbstractAction("hit me!") {
#Override
public void actionPerformed(ActionEvent e) {
LOG.info("got hit!");
}
};
JComponent content = new JPanel();
content.add(new JButton(action));
content.add(field);
for quick research and/or for future readers,
this issue is reproducible and presented for,
a) JPopup
b) JMenu
tested on jdk1.6.0_25 and jdk1.7.0_04,
same issue on WinXp and Win7,
for Look and Feel to SystemLookAndFeel / WindowsLookAndFeel,
Here's a possible workaround with JWindow instead of JPopupMenu, that was proposed by mKorbel in comments:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestJPopup {
protected void initUI() {
final JFrame frame = new JFrame(TestJPopup.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField textField = new JTextField("Some text field");
frame.add(textField, BorderLayout.WEST);
final JButton buttonToHit = new JButton("Hit me");
buttonToHit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(buttonToHit, "You hit the button successfully");
}
});
frame.add(buttonToHit);
frame.setSize(200, 70);
frame.setVisible(true);
final JWindow popup = new JWindow();
popup.getContentPane().add(new JLabel("<html>Hey!<br>I'm the popup window!</html>"),
BorderLayout.NORTH);
popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
popup.pack();
popup.setFocusable(false);
popup.setVisible(true);
// I want to activate popup when user clicks in the text field
textField.addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
if (popup != null) {
popup.setVisible(true);
popup.setLocation(frame.getLocation().x + 60, frame.getLocation().y + 60);
popup.toFront();
}
}
});
textField.addFocusListener(new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
if (popup != null) {
popup.setVisible(false);
}
}
});
}
public static void main(String[] args) throws Exception {
Class lnfClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsLookAndFeel", true,
Thread.currentThread().getContextClassLoader());
LookAndFeel feel = (LookAndFeel) lnfClass.newInstance();
UIManager.setLookAndFeel(feel);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestJPopup().initUI();
}
});
}
}
Here is the magic line that fixes the problem:
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);
I found this after looking into the source code for the BasicPopupMenuUI class. Apparently this behaviour is a deliberate design choice according to the following comments in the code, but it sure feels like a bug to me.
// Ask UIManager about should we consume event that closes
// popup. This made to match native apps behaviour.
By the way, it happens in Java 5 and 6 too.

How to prevent a disabled JMenuItem from hiding the menu when being clicked?

In my Java swing application i have noticed that when i click on a disabled JMenuItem in a JPopupMenu it hides the menu, but i i do not want to hide it, as if nothing is clicked. Is there a way to prevent this ?
-----------------------------------> Update: Added Code sample :
JMenuItem saveMenuItem = new JMenuItem();
saveMenuItem.setEnabled(false);
saveMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
saveMenuItemActionPerformed();
}
});
add(saveMenuItem);
private void saveMenuItemActionPerformed() {
System.out.println( "Save clicked." );
}
This has been tested and works.
The Look & Feel decides how to handle the mouse events on disabled menu items. Anyway, you can intercept the undesired events by using a custom MenuItem. Simply use that code (copy/paste):
public class CustomMenuItem extends JMenuItem {
public CustomMenuItem(String text) {
super(text);
}
public CustomMenuItem() {
super();
}
protected void processMouseEvent(MouseEvent e) {
if (isEnabled()) super.processMouseEvent(e);
}
}
First, adapt the code to suit your needs (optional).
Finally, replace any JMenuItem with a CustomMenuItem.
That's it!
not sure how to prevent. but you can setVisible(false) to prevent it being displayed. Also if a user clicks on the disable menu no action will take place.
When you are disabling JMenuItem, you should remove the ActionListener associated with that JMenuItem by using
jMenuItem.removeActionListener() method.
If u remove that that action will not call the listener and popup will not be disappeared.
I hope this will help to achieve your target.
did you gave a try at this method:
http://download.oracle.com/javase/6/docs/api/javax/swing/JMenuItem.html#setArmed%28boolean%29
"arm the menu item so it can be selected", which I guess would do the trick if set to false.
In short, you can do this, but you will have to write your own mouse listener, which may require a lot of copy&paste from the jdk source code, which is not a very good idea, and I'm not sure about what license restrictions it will put on your code.
I would start digging from this method:
javax.swing.plaf.basic.BasicMenuItemUI.Handler#mouseReleased
which seems to be the entry point from where the menu handling mechanism hides the popup. I would take a closer look at
javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber#stateChanged
EDIT Developing answer by #Burhan Valikarimwala, try this apporach: remove all action listeners from the disabled JMenuItem and store them in some static temp structure (let's say a Map<WeakReference<JMenuItem>, List<MouseListener>>), this way it will not hide the popup. When you make the menu item enabled again, add all the listeners back. Make it into some util method and it will be seamless.
The only solution I could come up with, for your problem of a click on disable JMenuItem causing it to hide is below:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
public class PopupMenuDisableNoCloseTest extends JPanel implements ActionListener
{
public static void main(String[] args)
{
PopupMenuDisableNoCloseTest p = new PopupMenuDisableNoCloseTest();
p.setPreferredSize(new Dimension(200, 300));
p.setBackground(Color.GREEN);
JPanel contentPane = new JPanel();
contentPane.add(p);
final JFrame f = new JFrame();
final JPopupMenu popup = new JPopupMenu();
final JMenuItem menuItem1 = new JMenuItem("A popup menu item");
menuItem1.addActionListener(p);
menuItem1.addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
System.out.println(" menuItem1 mousePressed e.getPoint()=" + e.getPoint());
}
#Override
public void mouseReleased(MouseEvent e)
{
System.out.println(" menuItem1 mouseReleased e.getPoint()=" + e.getPoint());
if(!menuItem1.isEnabled())
popup.setVisible(true);
}
});
menuItem1.setEnabled(false);
popup.add(menuItem1);
JMenuItem menuItem2 = new JMenuItem("Another popup menu item");
menuItem2.addActionListener(p);
popup.add(menuItem2);
MouseListener popupListener = new PopupListener(popup);
f.addMouseListener(popupListener);
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e)
{
JMenuItem source = (JMenuItem) (e.getSource());
String s = "Action event detected. Event source: " + source.getText();
System.out.println("s=" + s);
}
static class PopupListener extends MouseAdapter
{
JPopupMenu popup;
PopupListener(JPopupMenu popupMenu)
{
popup = popupMenu;
}
#Override
public void mousePressed(MouseEvent e)
{
maybeShowPopup(e);
}
#Override
public void mouseReleased(MouseEvent e)
{
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e)
{
if(e.isPopupTrigger())
{
popup.show(e.getComponent(),
e.getX(), e.getY());
}
}
}
}
Basically the hiding happens when your release is inside the bounds of the JMenuItem, therefore we are checking if it is disabled then we show popup again. As by this time it is already decided that it will be hidden. I was trying calling super.mouseRelease with a different MouseEvent pointing outside component and consuming the previous one but it helps nothing.
Anyway this solution works.
Enjoy, Boro
I think in Java7 this has been fixed.

Java Swing add/remove jButtons on runtime

My application has a module which allows the user to add jButtons on the jLayeredpane during runtime. I want to add action listeners to this dynamically added contents and also i have to provide access to delete the dynamically added buttons during runtime. Is there any way to do this ?
private Map<String, JButton> dynamicButtons;
public void addButton(String name) {
JButton b = new JButton(name);
b.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
jLayeredPane2.add(b);
dynamicButtons.put(name, b);
jLayeredPane2.invalidate();
}
public void removeButton(String name) {
JButton b = dynamicButtons.remove(name);
jLayeredPane2.remove(b);
jLayeredPane2.invalidate();
}
Original Answer Good in general, but done differently in this case
In order to keep track of an arbitrary number of added JButtons, you will need to keep them in a list.
So, after you create a new button, add the listeners to it, and add it to the pane, you then need to save that new button in a list.
That way you can keep track of all of the buttons you have added.
You could also use a Map<String, JButton> that maps a button name to the button.
Example:
private Map<String, JButton> dynamicButtons;
public void addButton(String name) {
JButton b = new JButton(name);
b.addActionListener(someAction);
yourPanel.add(b);
dynamicButtons.put(name, b);
yourPanel.invalidate();
}
public void removeButton(String name) {
Button b = dynamicButtons.remove(name);
yourPanel.remove(b);
yourPanel.invalidate();
}
The following is a full class that lets you add and remove buttons dynamically. It's not exactly what you want, but it should get you really close.
Code for your specific case:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class ExampleFrame extends JFrame {
private JButton add, remove;
private JPanel dynamicButtonPane, addRemovePane;
private boolean waitingForLocationClick;
public ExampleFrame() {
super("Dynamic button example");
waitingForLocationClick = false;
add = new JButton("Add Button");
add.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
addButton(JOptionPane
.showInputDialog("Name of the new button:"));
}
});
remove = new JButton("Remove Button");
remove.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
lookingToRemove = true;
}
});
JPanel mainPane = new JPanel(new BorderLayout());
dynamicButtonPane = new JPanel();
dynamicButtonPane.setLayout(null);
dynamicButtonPane.setPreferredSize(new Dimension(300, 300));
addRemovePane = new JPanel();
addRemovePane.add(add);
addRemovePane.add(remove);
mainPane.add(dynamicButtonPane, BorderLayout.NORTH);
mainPane.add(addRemovePane, BorderLayout.SOUTH);
add(mainPane);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
dynamicButtonPane.addMouseListener(pointSelectorListener);
}
private JButton buttonToPlace;
public void addButton(String name) {
JButton b = new JButton(name);
b.setActionCommand(name);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (lookingToRemove) {
if (e.getSource() instanceof JButton) {
dynamicButtonPane.remove((Component) e.getSource());
dynamicButtonPane.validate();
dynamicButtonPane.repaint();
}
} else
JOptionPane.showMessageDialog(ExampleFrame.this, "This is " + e.getActionCommand());
}
});
waitingForLocationClick = true;
lookingToRemove = false;
buttonToPlace = b;
}
public void putButtonAtPoint(Point p) {
System.out.println("Placing a button at: " + p.toString());
dynamicButtonPane.add(buttonToPlace);
buttonToPlace.setBounds(new Rectangle(p, buttonToPlace
.getPreferredSize()));
dynamicButtonPane.validate();
buttonToPlace = null;
waitingForLocationClick = false;
}
private boolean lookingToRemove = false;
private final MouseListener pointSelectorListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (waitingForLocationClick) {
putButtonAtPoint(e.getPoint());
} else {
System.out.println("Not in waiting state");
}
}
};
public static void main(String[] args) {
new ExampleFrame();
}
}
Absolutely. All of this stuff can be done programatically at any time. Here are a couple of hints to avoid problems and pitfalls:
When you add components to any panel, make sure this is done on the Event Dispatch Thread through SwingUtilities.invokeLater(Runnable). Inside the Runnable, you want to add the component to the panel, hook up the listeners, and re-layout the panel.
Use SwingUtilities.isEventDispatchThread() to check to see if you are already on the event dispatch thread. If you are, then you can just run the Runnable immediately instead of calling invokeLater.
Once you've modified the layout of a panel, be sure to call Component.invalidate() on the panel to make sure it gets laid out again.
Maintain your own list of listeners. Overwrite the add and remove methods on the panel to add or remove them from your list and also from all existing buttons. When you add new buttons, add all listeners on the list.
This is a very common task, and it is fully supported by Java. You should be able to get it done without too much trouble.

Categories

Resources