简体   繁体   中英

How to set dismiss delay on JButton's rollover effect?

By analogy with ToolTipManager setDismissDelay(int milliseconds) method, i would like to implement a dismiss delay for the rollover effect on a JButton.

In my swing application i have set different icons for my JButtons (setIcon, setPressedIcon and setRolloverIcon methods), but i'm trying to solve an issue occurring when a particular JButton, which should open a modal dialog, is pressed. When the button is pressed and the modal dialog is shown, the jbutton still shows the Rollover icon, even if i passed the "normal" icon to setPressedIcon method. Also, the rollover icon won't disappear until the cursor returns to main frame, also if the jdialog has been closed.

I made an example to show what i mean. I placed only two buttons into main frame, each button has a green square icon as "normal" icon, and a red icon for rollover effect. As i sayed, i would like the buttons to show again the green icon when they are pressed. The first button will behave "wrongly", since the red icon is visible after the jdialog creation. For the second button i solved this issue overriding isPressed () method (in its DefaultButtonModel), by calling setRollover (false) when the button is pressed.

I don't think this is the best solution, i would prefer not to act directly on ButtonModel. So i would like to know if you have a better idea, maybe something similar to a setDismissDelay method, as i sayd before. Thanks !

Here there's an SSCE :

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SSCE
{
    public static void main (String[] a) {
        SwingUtilities.invokeLater (new Runnable () {
            public void run () {
                JFrame frame = new JFrame ("Icon Test");
                frame.setContentPane (new MainPanel (frame));
                frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
                frame.setResizable (false);
                frame.pack ();
                frame.setLocationRelativeTo (null);
                frame.setVisible (true);
            }
        });
    }
}
class MainPanel extends JPanel
{
    public MainPanel (JFrame parent) {
        JButton firstButton = createButton (createButtonImage (Color.GREEN), createButtonImage (Color.RED), parent);
        JButton secondButton = createButton (createButtonImage (Color.GREEN), createButtonImage (Color.RED), parent);
        secondButton.setModel (new DefaultButtonModel () {
            @Override public boolean isPressed () {
                boolean isPressed = super.isPressed ();
                if (isPressed) setRollover (false);
                return isPressed;
            }
        });
        add (firstButton);
        add (secondButton);
    }
    private JButton createButton (BufferedImage normalImage, BufferedImage rolloverImage, final JFrame parent) {
        ImageIcon normalIcon = new ImageIcon (normalImage), rolloverIcon = new ImageIcon (rolloverImage);
        JButton button = new JButton (new AbstractAction () {
            public void actionPerformed (ActionEvent e) {
                JDialog dialog = new JDialog (parent, "Test Dialog",true);
                dialog.setSize (400, 400);
                dialog.setLocationRelativeTo (parent);
                dialog.setVisible (true);
            }
        });
        button.setBorderPainted (false);
        button.setCursor (Cursor.getPredefinedCursor (Cursor.HAND_CURSOR));
        button.setFocusPainted (false);
        button.setContentAreaFilled (false);
        button.setIcon (normalIcon);    
        button.setPressedIcon (normalIcon);
        button.setRolloverEnabled (true);
        button.setRolloverIcon (rolloverIcon);
        return button;
    }
    private BufferedImage createButtonImage (Color color) {
        BufferedImage image = new BufferedImage (20, 20, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics ();
        g.setColor (color);
        g.fillRect (0, 0, 20, 20);
        g.dispose ();
        return image;
    }
}

EDIT :

As @camickr suggested, i tried to wrap the ActionListener code in a SwingUtilities.invokeLater ().

I won't repost the full code, i have only replaced those lines :

JButton button = new JButton (new AbstractAction () {
                public void actionPerformed (ActionEvent e) {
                    JDialog dialog = new JDialog (parent, "Test Dialog",true);
                    dialog.setSize (400, 400);
                    dialog.setLocationRelativeTo (parent);
                    dialog.setVisible (true);
                }
            });

with :

JButton button = new JButton ();
    button.addActionListener (new ActionListener () {
            public void actionPerformed (ActionEvent e) {
                SwingUtilities.invokeLater (new Runnable () {
                    public void run () {
                        JDialog dialog = new JDialog (parent, "Test Dialog",true);
                        dialog.setSize (400, 400);
                        dialog.setLocationRelativeTo (parent);
                        dialog.setVisible (true);
                    }
                });
            }
        });

However, this doesn't solve my problem, the red icon is still visible when the dialog is created. I tried some small adjustments, with addActionListener or setAction, also only calling setVisible into the invokeLater call, but it still doesn't work.

Also, how could i use a Timer without using the same code on ButtonModel which i am using now ? I already tried some "hacks" by setting "normal icon" inside the actionPerformed and then invoking the other Action with a "custom" ActionEvent, but i would like to have a "clean" solution.

All code in a listener executes on the Event Dispatch Thread (EDT) .

The problem is that the state of the button is not changed before the ActionListener code is invoked. Once the modal dialog is displayed, the button state change code isn't executed until the dialog is closed.

Wrap the code in the ActionListener in a SwingUtilities.invokeLater() . This code will be added to the end of the EDT allowing normal button processing to finish before the dialog is displayed.

Read the section from the Swing tutorial on Concurrency in Swing for more information about the EDT .

Edit:

i would prefer not to act directly on ButtonModel

Spend more time playing with the code. The problem is that there is no mouseExited that is generated when the dialog is displayed so the state of the ButtonModel is never updated.

Another option might be to manually generate a MouseEvent for the mouseExited event and dispatch the event to the button before the dialog is displayed.

Although this approach would also be considered a hack.

how could i use a Timer

Again, the problem is the state of the button. Even if you use a Timer you would manually need to reset the state of the model.

Your current solution seems reasonable since all the logic is located in a class that customizes the behaviour.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM