7

I want to add a hovering-effect to my customized Swing.JButton similar to the icon on my Chrome Browser:

Before hover >>

enter image description here

After hover >>

enter image description here

I am able to set the button in the "before" status when it is created, but I am not able to create the "border + raised-background" when it is hovered. When I try to re-add the border to the button, I got a moving effect as after repainting a new border is inserted.

This is my current code:

public class MyButton extends JButton implements MouseListener {

public MyButton(String iconPath, String toolTip) {
    super(new ImageIcon(TipButton.class.getResource(iconPath)));
    addMouseListener(this);
    setBorder(null);
    setBorderPainted(false);
    setFocusPainted(false);
    setOpaque(false);
    setContentAreaFilled(false);
    setToolTipText(toolTip);
}

public MyButton(String iconPath, String name, String toolTip) {
    this(observers, iconPath, toolTip);
    setText(name);
}

@Override
public void mouseClicked(MouseEvent e) {}

@Override
public void mousePressed(MouseEvent e) {}

@Override
public void mouseReleased(MouseEvent e) {}

@Override
public void mouseEntered(MouseEvent e) {
    if (e.getSource() != this) return;

    setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
}

@Override
public void mouseExited(MouseEvent e) {
    if (e.getSource() != this) return;

    setBorder(null);
}

}

I suppose the main logic should be in the methods mouseEntered/mouseExited but I don't know how to get the wanted effect. Any idea?

Randomize
  • 8,651
  • 18
  • 78
  • 133

4 Answers4

7

I think I have found a solution. Using EmptyBorder with the same sizes (insets) of a raised border does the trick. Code:

public class SwingUtils {

public static JButton createMyButton (String iconPath, String toolTip) {
    final JButton b = new JButton (new ImageIcon(SwingUtils.class.getResource(iconPath)));
    final Border raisedBevelBorder = BorderFactory.createRaisedBevelBorder();
    final Insets insets = raisedBevelBorder.getBorderInsets(b);
    final EmptyBorder emptyBorder = new EmptyBorder(insets);
    b.setBorder(emptyBorder);
    b.setFocusPainted(false);
    b.setOpaque(false);
    b.setContentAreaFilled(false);
    b.setToolTipText(toolTip);
    b.getModel().addChangeListener(new ChangeListener() {
        @Override
        public void stateChanged(ChangeEvent e) {
            ButtonModel model = (ButtonModel) e.getSource();
            if (model.isRollover()) {
                b.setBorder(raisedBevelBorder);
            } else {
                b.setBorder(emptyBorder);
            }
        }
    });
    return b;
}

}

Note: as mKorbel says it would be using ChangeListener and the button created in a factory method instead of subclass JButton.

Randomize
  • 8,651
  • 18
  • 78
  • 133
  • 1
    But why is there the need to inherit `JButton`, looking at the code, it seems to me, that there is none. This whole thing can be done on a mere instance of the `JButton` too as already suggested by `mKorbel` (that's why upvoted that answer). I hope you will consider composition over inheritance, in the actual implementation of the code. – nIcE cOw Jul 16 '13 at 11:21
  • 1
    Removed inheritance and replaced MouseListener with ChangeListener – Randomize Jul 16 '13 at 12:05
2

Use different images for every state. You can set a different Icon for selected, disabled selected, disabled, pressed, rollover, rolloverEnabled, rolloverSelected. More info here.

Jannis Alexakis
  • 1,279
  • 4
  • 19
  • 38
  • For the moment I am trying to avoid doing an extra image for every button and trying to get the effect programmatically. – Randomize Jul 15 '13 at 15:56
  • 1
    +1, I like this idea, (though my vote will come in 10 hours), since my limit is reached for today :( . Here is one related [example](http://stackoverflow.com/a/7919280/1057230) – nIcE cOw Jul 15 '13 at 16:42
1

In java 7 there is a BevelBorder class that looks to be what you are looking for. The 2 methods you will probably be interested in are

    paintRaisedBevel(Component c, Graphics g, int x, int y, int width, int height)

and

    paintLoweredBevel(Component c, Graphics g, int x, int y, int width, int height)

Here is the documentation on the class: http://docs.oracle.com/javase/7/docs/api/javax/swing/border/BevelBorder.html

Collin
  • 306
  • 3
  • 8
  • I liked your suggestion, though why not simply : button.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED/LOWERED)), watch [BorderFactory.createBevelBorder(int)](http://docs.oracle.com/javase/7/docs/api/javax/swing/BorderFactory.html#createBevelBorder(int)) – nIcE cOw Jul 15 '13 at 16:46
  • 1
    Yes, I honestly think that the createBevelBorder(int) is a simpler idea that works just as good, but according to Randomize's code, he has been trying that and has been having problems, so I am just offering an alternative solution that he might have more success with. – Collin Jul 15 '13 at 17:05
  • Ahha, my bad, didn't catch that before :(, though it will be a tricky task to implement, by subclassing BevelBorder since the method specified by you is having protected access specifier, though +1, but as said before, might will take time for that to convert virtual to physical :-) – nIcE cOw Jul 15 '13 at 17:16
  • Yes, he should really try and get the createBevelBorder() to work. I try to avoid subclassing at all possible and take the path of least resistance :). It just adds confusion, and as you said, it will most likely take more time to convert it. – Collin Jul 15 '13 at 17:24
  • I was actually talking about my vote, that it will take time, since my limit for yesterday was gone :-) – nIcE cOw Jul 16 '13 at 04:28
1
Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319