JSlider Event Firing twice - java

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.

Related

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);
}
});
}
}

Java Swing Location On Screen after Resize

I'm trying to keep a JDialog centered inside of a JFrame, regardless if the JFrame is moved or resized.
Essentially, I have a class like (This is not the real code, just an example )
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.Dimension;
import java.awt.Color;
public class HelloWorldSwing extends ComponentAdapter implements AncestorListener {
private JDialog dialog;
private JFrame frame;
#Override
public void componentResized( ComponentEvent event ) {
//This value does not hcange when dragged from the left or right
System.err.println( " Location " + frame.getLocationOnScreen() );
recenter();
}
#Override
public void ancestorAdded( AncestorEvent evt ) {
}
#Override
public void ancestorRemoved( AncestorEvent evt ) {
}
#Override
public void ancestorMoved( AncestorEvent evt ) {
//This value does not hcange when dragged from the left or right
System.err.println( " Location " + frame.getLocationOnScreen() );
recenter();
}
private void recenter() {
dialog.setLocationRelativeTo( frame.getRootPane() );
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private void createAndShowGUI() {
//Create and set up the window.
frame = new JFrame("HelloWorldSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getRootPane().addAncestorListener( this );
frame.getRootPane().setBackground( Color.WHITE );
frame.addComponentListener( this );
frame.setMinimumSize( new Dimension( 500, 500 ) );
frame.setMaximumSize( new Dimension( 500, 500 ) );
dialog = new JDialog();
dialog.setResizable( true );
dialog.setAlwaysOnTop( true );
dialog.setVisible( true );
dialog.setMaximumSize( new Dimension( 200, 200 ) );
dialog.setMinimumSize( new Dimension( 200, 200 ) );
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
HelloWorldSwing swing = new HelloWorldSwing();
swing.createAndShowGUI();
}
});
}
}
When the JFrame is resized from the left or the top, the location on the screen changes, but the getLocationOnScreen() does not change it's result. It seems like the getLocationOnScreen should change as the top left corner has changed due to a resize.
What am I missing ? Thanks.
Update ::
This code seems to run fine on Windows, but does not run correctly on Fedora 21, java 7

Java Popup Button

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 );
}
}

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.

Java Swing rendering bug on Windows 7 look-and-feel?

The knob on vertical JSlider's on my Windows 7 machine (with native look-and-feel) is really, really tiny in both directions. Not just skinny but short as well.
Can anyone confirm this? Should I report it? If so, where? Thanks!
Here is the code for the sample program (in the screen shot):
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
public class SliderTest
{
public static void main( String[] args )
{
// Set the look and feel to that of the system
try
{ UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); }
catch ( Exception e )
{ System.err.println( e ); }
// Launch the GUI from the event dispatch thread
javax.swing.SwingUtilities.invokeLater( new Runnable()
{
public void run ()
{
JFrame window = new JFrame();
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
JPanel contentPane = new JPanel();
contentPane.add( new JSlider(SwingConstants.HORIZONTAL) );
contentPane.add( new JSlider(SwingConstants.VERTICAL) );
window.setContentPane( contentPane );
window.pack();
window.setLocationRelativeTo( null ); // Center window
window.setVisible( true );
}
});
}
}
First off, this happens in Windows Vista too. It seems to be the case, that the slider tries to take as little space as possible. If you want a bigger JSlider use JSlider.setPaintTicks. So you have to add the following:
JSlider vertical = new JSlider( SwingConstants.VERTICAL );
vertical.setPaintTicks( true );
contentPane.add( vertical );
That should do the trick.

Categories

Resources