3

I have just started working with Swing and am trying to draw a button with a custom shape, a triangle in this example. I called the JButton subclass 'ShiftingButton' in the following code because of its unusual behavior. When the mouse enters its region, it is repainted with an offset from its original position. Furthermore the shifted, offset version is drawn in addition to the original position so that both the original and shifted versions appear together. That is, when I run this code, the button is shown as a triangle along the left edge of the window. Then when I run the mouse over the button, a new triangle is drawn (in addition to the old one), shifted down and to the right by about 10 pixels. Resizing the window changes the offset of the phantom button from the original.

Experimenting with mouse clicks shows that only the original, correctly-positioned button is active. The region of the offset phantom button is not active.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;

public class ShiftingButton extends JButton implements ActionListener {
    private Polygon shape;
    public ShiftingButton () {
        initialize();
        addActionListener(this);
    }
    protected void initialize() {
        shape = new Polygon();
        setSize(120, 120);
        shape.addPoint(0, 0);
        shape.addPoint(0, 60); 
        shape.addPoint(90, 0);
        setMinimumSize(getSize());
        setMaximumSize(getSize());
        setPreferredSize(getSize());
    }
    // Hit detection
    public boolean contains(int x, int y) {
        return shape.contains(x, y);
    }
    @Override
    public void paintComponent (Graphics g) {
        System.err.println("paintComponent()");
        g.fillPolygon(shape);
    }
    protected void paintBorder(Graphics g) {
    }
    @Override
    public void actionPerformed (ActionEvent ev) {
        System.out.println("ShiftingButton ActionEvent!");
    }
    public static void main (String[] args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        ShiftingButton button = new ShiftingButton();
        panel.add(button);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Jorocco
  • 83
  • 2
  • 9

1 Answers1

2

You failed to call super.paintComponent(g) inside the overridden paintComponent(...) method. Moreover, while overriding a method of the Base class, always try to keep the access specifier of the methods, the same, as much as possible. In this case it's protected and not public :-) Now function should be like this :

@Override
protected void paintComponent (Graphics g) {
    System.err.println("paintComponent()");
    super.paintComponent(g);
    g.fillPolygon(shape);
}

EDIT 1 :

Moreover, since you are using a custom shape to be drawn, hence you again failed to specify the ContentAreaFilled property for this JButton in question, hence inside your constructor, you should write setContentAreaFilled(false), for it to work nicely. Though if this doesn't works (for reasons specified in the Docs), then you have to use the plain old Opaque property and set it to false for this JButton using setOpaque(false) :-)

Here is your code with modified changes :

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;

public class ShiftingButton extends JButton implements ActionListener {

    private Polygon shape;

    public ShiftingButton () {
        setContentAreaFilled(false);
        initialize();
        addActionListener(this);
    }

    protected void initialize() {
        shape = new Polygon();
        setSize(120, 120);
        shape.addPoint(0, 0);
        shape.addPoint(0, 60); 
        shape.addPoint(90, 0);
    }

    @Override
    public Dimension getPreferredSize() {
        return (new Dimension(120, 120));
    }

    // Hit detection
    public boolean contains(int x, int y) {
        return shape.contains(x, y);
    }

    @Override
    protected void paintComponent(Graphics g) {
        System.err.println("paintComponent()");
        super.paintComponent(g);
        g.fillPolygon(shape);       
    }

    protected void paintBorder(Graphics g) {
    }

    @Override
    public void actionPerformed (ActionEvent ev) {
        System.out.println("ShiftingButton ActionEvent!");
    }

    public static void main (String[] args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        ShiftingButton button = new ShiftingButton();
        panel.add(button);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}
nIcE cOw
  • 24,468
  • 7
  • 50
  • 143
  • @Jorocco : No need to call `setSize(120, 120)` even, inside the constructor, I just caught that in your code, I removed the other statements, though missed this one in my code. Since the overridden `getPreferredSize()` will take care of that thingy too :-) – nIcE cOw Aug 18 '13 at 17:28
  • Thanks! That took care of it. I have one follow-up question though. While it seems like good OO policy to call super.paintComponent(), I'm not sure of its practical significance. That is, if my custom-shaped button exactly defines how the button should look and does not look at all like its superclass, why should it rely on the JButton's painting mechanism? Sorry if this sounds nit-picky, but I just want to understand every line of the code. When I ran it without the super.painComponent() call, I didn't see any difference. Perhaps it would with more elaborate subclassing? – Jorocco Aug 18 '13 at 18:32
  • @Jorocco : Please refer to this [answer](http://stackoverflow.com/a/5446768/1057230), the person has explained this in a very nice way. I hope it helps. For the rest you're MOST WELCOME and KEEP SMILING :-) – nIcE cOw Aug 18 '13 at 18:42