Java Popup Button - java

Note: You may have to compile and run my example to fully understand my question. If this is not kosher, I apologize in advance.
I am trying to create a Swing control that is based on a JToggleButton and a JPopupMenu.
The toggle button is selected iff the popup menu is visible, and the toggle button is deselected iff the popup menu is not visible. Thus, the behavior is similar to a JComboBox, except that the popup can contain arbitrary components.
The code that follows is an example of how I would create the control (except that it would be in its own class... something like a JPopupToggleButton). Unfortunately, it exhibits different behavior under different look and feels (I have tested it with Metal and Nimbus).
The code as posted here behaves as expected in Metal, but not in Nimbus. When using Nimbus, just show and hide the popup by repeatedly clicking the toggle button and you will see what I mean.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
public class PopupButtonExample extends JFrame
{
public static void main( String[] args )
{
java.awt.EventQueue.invokeLater( new Runnable()
{
#Override
public void run()
{
PopupButtonExample example = new PopupButtonExample();
example.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
example.setVisible( true );
}
});
}
public PopupButtonExample()
{
super( "Components in Popup" );
JPanel popupPanel = new JPanel();
popupPanel.setLayout( new BorderLayout() );
popupPanel.add( new JLabel( "This popup has components" ),
BorderLayout.NORTH );
popupPanel.add( new JTextArea( "Some text", 15, 20 ),
BorderLayout.CENTER );
popupPanel.add( new JSlider(), BorderLayout.SOUTH );
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add( popupPanel );
final JToggleButton popupButton = new JToggleButton( "Show Popup" );
popupButton.addActionListener( new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if( popupButton.isSelected() )
popupMenu.show( popupButton, 0, popupButton.getHeight() );
}
});
popupMenu.addPopupMenuListener( new PopupMenuListener()
{
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent pme) {}
#Override
public void popupMenuCanceled(PopupMenuEvent pme) {}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent pme) {
Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
Point componentLoc = popupButton.getLocationOnScreen();
mouseLoc.x -= componentLoc.x;
mouseLoc.y -= componentLoc.y;
if( !popupButton.contains( mouseLoc ) )
popupButton.setSelected( false );
}
});
JPanel toolBarPanel = new JPanel();
toolBarPanel.add( popupButton );
JToolBar toolBar = new JToolBar();
toolBar.add( toolBarPanel );
setLayout( new BorderLayout() );
add( toolBar, BorderLayout.PAGE_START );
setPreferredSize( new Dimension( 640, 480 ) );
pack();
}
}
Commeting out the following lines makes the code behave as expected in Nimbus, but not in Metal. Again, just keep clicking the toggle button to see what I mean.
// Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
// Point componentLoc = popupButton.getLocationOnScreen();
// mouseLoc.x -= componentLoc.x;
// mouseLoc.y -= componentLoc.y;
// if( !popupButton.contains( mouseLoc ) )
So here are my two questions:
(1) In Nimbus, why does the click that hides the popup panel not get passed to the toggle button, as it does with Metal?
(2) How can I solve this problem so that it works with all look and feels?

Nimbus is too buggy (and development ended somewhere in the middle) I see that you need three mouse click to the JToggleButton in compare with Metal
every standard L&F have got own specific issues, especially SystemLookAndFeel
use JWindow rather that JPopup, because with JPopup there are another Bugs too e.g. JPopup with JCombobox

After some investigation, I found the cause for the difference between Nimbus and Metal. The following flag is used (at least by BasicPopupMenuUI) to control the consumption of events when a popup is closed:
UIManager.getBoolean( "PopupMenu.consumeEventOnClose" );
When using Nimbus, this returns true. When using Metal, this returns false. Thus, the method popupMenuWillBecomeInvisible should be defined as follows:
if( UIManager.getBoolean( "PopupMenu.consumeEventOnClose" ) )
{
popupButton.setSelected( false );
}
else
{
Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
Point componentLoc = popupButton.getLocationOnScreen();
mouseLoc.x -= componentLoc.x;
mouseLoc.y -= componentLoc.y;
if( !popupButton.contains( mouseLoc ) )
{
popupButton.setSelected( false );
}
}

Related

setDefaultButton - Focus Loss

I have found that when a modal JDialog launches a second modal JDialog, and when that second JDialog is disposed, the first JDialog seems to (mostly) ignore the keyboard.
I have included a sample program to demonstrate the issue. On running the code, I should be able to hit the space bar (the button in the main JFrame has focus) and the first JDialog is launched, containing a button (also with focus).
I hit the space bar and the second JDialog is launched. Hitting enter, the second JDialog disposes.
Hitting space bar on the first JDialog results in no response. Alt- F4 will close the first JDialog, so the keyboard is not completely disconnected.
Repeating the above procedure using the mouse only, the issue does not occur.
The background to all of this is I wanted to set the default button on a dialog (in which the user enters some text and other data). If I comment out the setDefaultButton in the second JDialog and now use TAB to move around, the issue does not occur.
In short, I want to be able to set a default button on a JDialog (so I can hit the enter key), but at the same time, I want default focus on a JTextField.
I am on Ubuntu 13.10 using the OpenJDK (64 bit). I have found this happens regardless of using the GTK+ look and feel or the metal look and feel (that is, not setting any look and feel).
I cannot reproduce on Windows XP (using Oracle Java 32 bit).
On reading a few posts, it seems Linux and Java have a few focus problems and so I'm not sure if this is a programming error on my part or I've stumbled upon a Linux/Java issue.
Any ideas please?
Below is example code which reproduces the issue.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
public class Test extends JFrame
{
public Test()
{
JButton button = new JButton( "Test" );
button.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent actionEvent ) { new Test1( Test.this ); } } );
JPanel panel = new JPanel();
panel.add( button );
getContentPane().add( panel );
pack();
setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
setVisible( true );
}
protected class Test1 extends JDialog
{
public Test1( JFrame owner )
{
super( owner );
JButton button = new JButton( "1" );
button.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent actionEvent ) { new Test2( Test1.this ); } } );
JPanel panel = new JPanel();
panel.add( button );
getContentPane().add( panel );
pack();
setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
setLocationRelativeTo( null );
setModalityType( ModalityType.APPLICATION_MODAL );
setVisible( true );
}
}
protected class Test2 extends JDialog
{
public Test2( JDialog owner )
{
super( owner );
JTextField textField = new JTextField();
JButton button = new JButton( "2" );
getRootPane().setDefaultButton( button );
button.addActionListener
(
new ActionListener()
{
public void actionPerformed( ActionEvent actionEvent )
{
// Test2.this.getRootPane().setDefaultButton( null ); // Makes no difference!
Test2.this.dispose();
}
}
);
JPanel panel = new JPanel( new BorderLayout() );
panel.add( textField, BorderLayout.CENTER );
panel.add( button, BorderLayout.SOUTH );
getContentPane().add( panel );
pack();
setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
setLocationRelativeTo( null );
setModalityType( ModalityType.APPLICATION_MODAL );
setVisible( true );
}
}
public static void main( String[] args )
{
new Test();
}
}

Java button is working strangely

I am using Swing to build a GUI in Java. The code to make the button and add it is like this:
//Create a button
JButton exitButton = new JButton("Exit");
exitButton.setSize(90, 40);
exitButton.setLocation(800, 450);
exitButton.setVisible(true);
//Adding components
window.getContentPane().add(exitButton);
When I run the app, the button appears in the whole window, sometimes appears as its intended and sometimes doesn't come. Is this some sort of java bug or a prob with my sdk. In case you wish to know what sort of window it is,
//Create a window
JFrame window = new JFrame("First Window");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
window.setVisible(true);
window.setSize(1000, 550);
window.setLocation(150, 150);
It's all within static void main. BTW, how I get the button to close the window through System.exit(0); (I am a beginner and this is my first self-written GUI)
You need a layout. See A Visual Guide to Layout Managers.
Also please check my tutorials here.
I got this sample code. This might be helpful Simple swing buttons
package com.ack.gui.swing.simple;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class SimpleSwingButtons extends JFrame {
public static void main( String[] argv ) {
SimpleSwingButtons myExample = new SimpleSwingButtons( "Simple Swing Buttons" );
}
public SimpleSwingButtons( String title ) {
super( title );
setSize( 150, 150 );
addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent we ) {
dispose();
System.exit( 0 );
}
} );
init();
setVisible( true );
}
private void init() {
JPanel my_panel = new JPanel();
my_panel.setLayout( new GridLayout( 3, 3 ) );
for( int i = 1; i < 10; i++ ) {
ImageIcon icon = new ImageIcon( i + ".gif" );
JButton jb = new JButton( icon );
jb.setToolTipText( i + ".gif" );
my_panel.add( jb );
}
getContentPane().add( my_panel );
my_panel.setBorder( BorderFactory.createEtchedBorder() );
}
}
courtesy Java.happycodings
You must check your layout, if you want to use custom position for your components, set Layout as null and use the setBounds(x,y,weight,height) method.
JFrame window = new JFrame("First Window");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
window.setLayout(null);
JButton exitButton = new JButton("Exit");
exitButton.setBounds(15,45,150,30);//This is just an example
exitButton.setVisible(true);
Best regards.

JSlider Event Firing twice

I have a simple JSlider with an attached ChangeListerner. Here's the code:
JSlider slider = new JSlider();
slider.setMinorTickSpacing(2);
slider.setMajorTickSpacing(20);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
slider.setSnapToTicks(true);
slider.setOrientation(SwingConstants.VERTICAL);
contentPane.add(slider, BorderLayout.CENTER);
slider.addChangeListener(new SliderListener());
class SliderListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
System.out.println("boo");
}
}
}
As you can see, the code isn't doing much, all I want to do for now is make sure the event is only firing once, and hence my event is simply to print something to the Console within Eclipse.
But the above code is printing "boo" twice each time I change the Slider. I'm guessing this has got something to do with Mouse Release on Slider, but whatever it is, I want it to only fire the event once, and hence only print the word once.
How can I achieve that?
Thanks
Are you certain the listener is not added twice ? The following SSCCE works as expected on my machine (OS X, JDK7)
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.EventQueue;
public class SliderTest {
public static void main( String[] args ) {
EventQueue.invokeLater( new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame( );
final JSlider slider = new JSlider( 0, 100 );
frame.add( slider );
slider.addChangeListener( new ChangeListener() {
#Override
public void stateChanged( ChangeEvent e ) {
if ( !( slider.getValueIsAdjusting() ) ){
System.out.println( "SliderTest.stateChanged" );
}
}
} );
frame.pack();
frame.setVisible( true );
frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
}
} );
}
}
I know the issue. My workaround is to set global eventOnwer to all other listeners,
That solves other event fires to occure while one is the event owner.
And then, to solve the slider, I set the getValueIsAdjusting() to true in a if. In case of another fire, return.

How to show a tooltip on a mouse click

I have a JTreeTable and have successfully implemented a MouseMotionListener to show a tooltip whenever the mouse is over one of the cells. However when clicking on the cell the tooltip does not show up. I've tried several things like setting the text on the mouseClicked and mouseReleased events but that doesn't work. I found this code -
Action toolTipAction = treeTable.getActionMap().get("postTip");
if(toolTipAction != null){
ActionEvent postTip = new ActionEvent(treeTable,ActionEvent.ACTION_PERFORMED, "");
toolTipAction.actionPerformed(postTip);
}
to use in the mouseReleased method, which does make the tooltip popup, but it's then in the wrong position. So next i tried overriding the getTooltipLocation method on the JTreeTable, and this works fine for mouseMoved events but doesn't get called with the above method. Can anyone shed some light on how to do this?
Thanks
Andy
You can use the following approach to show the tooltip (there will be a slight delay). Then you can override the getToolTipLocation() method since a MouseEvent will now be generated:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ToolTipOnRelease extends JPanel
{
public ToolTipOnRelease()
{
JLabel label = new JLabel( "First Name:" );
add( label );
JTextField textField = new JTextField(15);
add( textField );
MouseListener ml = new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
JComponent component = (JComponent)e.getSource();
component.setToolTipText("Mouse released on: " + component.getClass().toString());
MouseEvent phantom = new MouseEvent(
component,
MouseEvent.MOUSE_MOVED,
System.currentTimeMillis(),
0,
0,
0,
0,
false);
ToolTipManager.sharedInstance().mouseMoved(phantom);
}
};
label.addMouseListener( ml );
textField.addMouseListener( ml );
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("ToolTipOnRelease");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new ToolTipOnRelease() );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
org.apache.jorphan.gui.JTreeTable extends javax.swing.JComponent
javax.swing.JComponent#setToopTipText() doesn't work?
I do realize that you want to use Action but for tooltips? I would use Action when multiple UI actions would need to share it.

How to enable ESC-Close for an JPopupMenu, if the underlying window closes on ESC?

Imagine two common situations combined: A JDialog(or JFrame) which closes on VK_ESCAPE (set as key binding on the root pane) and an inner JPopupMenu which is supposed to close on ESC as well. The problem is: pressing escape always closes the dialog - event if the popup is visible. Apparently the popup doesn't even receive the key event so it can't be consumed by the popup. Is there any way to get this working correctly, so that on the first ESC-event the popup is closed and on the second the dialog closes?
By the way: It does work with a JComboBox, which by default closes when escape is pressed.
Finding a generic solution was a bit of a challenge. We need to consider when:
a light weight popup is used
a heavy weight popup is used
I determined that in both cases the root pane actually has focus when the escape key is pressed.
In the first case I just search the root pane to see if a JPopupMenu has been added to the GUI. If so, then we can just close the popup.
In the second case a Window is created to contain the JPopupMenu so I do a search to see if a visible custom popup Window is displayed. If so, then I dispose of the window.
If neither of the above two cases is true then you can just close the dialog.
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
public class DialogEscape extends JDialog
{
private JPopupMenu popup;
public DialogEscape()
{
popup = new JPopupMenu();
popup.add( new JMenuItem("SubMenuA") );
popup.add( new JMenuItem("SubMenuB") );
popup.add( new JMenuItem("SubMenuC") );
popup.add( new JMenuItem("SubMenuD") );
String[] items = { "Select Item", "Color", "Shape", "Fruit" };
JComboBox comboBox = new JComboBox( items );
add(comboBox, BorderLayout.NORTH);
JTextField textField = new JTextField("Right Click For Popup");
textField.setComponentPopupMenu(popup);
add(textField);
KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
Action escapeAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
boolean openPopup = false;
Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
// Check if light weight popup is being used
List<JPopupMenu> popups = SwingUtils.getDescendantsOfType(JPopupMenu.class, (Container)c, true);
for (JPopupMenu p: popups)
{
p.setVisible( false );
openPopup = true;
}
// Check if a heavy weight popup is being used
Window window = SwingUtilities.windowForComponent(c);
Window[] windows = window.getOwnedWindows();
for (Window w: windows)
{
if (w.isVisible()
&& w.getClass().getName().endsWith("HeavyWeightWindow"))
{
openPopup = true;
w.dispose();
}
}
// No popups so close the Window
if (! openPopup)
// SwingUtilities.windowForComponent(c).setVisible(false);
SwingUtilities.windowForComponent(c).dispose();
}
};
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
getRootPane().getActionMap().put("ESCAPE", escapeAction);
}
public static void main(String[] args)
{
String laf = null;
laf = "javax.swing.plaf.metal.MetalLookAndFeel";
// laf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
// laf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
try { UIManager.setLookAndFeel(laf); }
catch (Exception e2) { System.out.println(e2); }
JDialog dialog = new DialogEscape();
dialog.setDefaultCloseOperation( HIDE_ON_CLOSE );
dialog.setSize(200, 200);
dialog.setLocationRelativeTo(null);
dialog.setVisible( true );
}
}
You will also need to download the Swing Utils class.
I encountered the problem as well. The solution provided by #camickr didn't seem very nice to me. So I checked out what's going on under the hood. When opening a popup, the component that is the invoker of the popup loses focus. The focus goes over to the JRootPane of the JDialog which then delegates to the popup. So instead of registering the "ESC" shortcut on the JRootPane, i simply put it on the ContentPane. This way, the dialog only closes on ESC when anything other than the JRootPane is focused.
final JComponent contentPane = (JComponent) dialog.getContentPane();
contentPane.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), "EXIT" );
contentPane.getActionMap().put( "EXIT", new AbstractAction()
{
#Override
public void actionPerformed( final ActionEvent e )
{
dialog.dispatchEvent( new WindowEvent( dialog, WindowEvent.WINDOW_CLOSING ) );
}
} );

Categories

Resources