I have a class named Foo that extends a class named Bar that extends JPanel and implements ActionListener. When I select Circle and click the draw button, I draw a circle, and w
Along side all of Hovercraft's comments
The Graphics context is shared between components. One of the tasks of the super.paintComponent
is to "clean" the graphics context before painting on it.
This is why you're seeing two version of your buttons...
I would also do a number of things differently. This should help with extendability and reuse over time, as well also reduce the logic a little.
I would...
Action
s to simply the creation of those shapes and adding them to the model...I've only created a triangle shape (and it has no attributes beyond location and size), but I'm sure you'll get the general idea...(ps you'll need to supply your own triangle icon for the action ;))
public class DrawMe {
public static void main(String[] args) {
new DrawMe();
}
public DrawMe() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
DrawModel model = new DefaultDrawModel();
model.addElement(new Triangle(new Rectangle(10, 10, 100, 100)));
DrawPane drawPane = new DrawPane(model);
JToolBar toolBar = new JToolBar();
toolBar.add(new AddTriangleAction(model));
frame.add(toolBar, BorderLayout.NORTH);
frame.add(drawPane);
frame.setSize(400, 400);
frame.setVisible(true);
}
});
}
/**
* Simple action used to add triangles to the model...the model acts
* as a bridge between the action and the UI.
*/
protected class AddTriangleAction extends AbstractAction {
private DrawModel model;
public AddTriangleAction(DrawModel model) {
// Supply your own icon
putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/shape_triangle.png")));
this.model = model;
}
public DrawModel getModel() {
return model;
}
@Override
public void actionPerformed(ActionEvent e) {
// Randomly add the triangles...
int x = (int)(Math.random() * 400);
int y = (int)(Math.random() * 400);
model.addElement(new Triangle(new Rectangle(x, y, 100, 100)));
}
}
/**
* This is the background pane, from which the draw pane extends...
*/
protected class BackgroundPane extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = getWidth() / 2;
int y = getHeight() / 2;
Graphics2D g2d = (Graphics2D) g.create();
RadialGradientPaint rgp = new RadialGradientPaint(
new Point(x, y),
Math.max(getWidth(), getHeight()),
new float[]{0f, 1f},
new Color[]{Color.GRAY, Color.WHITE}
);
g2d.setPaint(rgp);
g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2d.setBackground(Color.BLACK);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
/**
* This is a simple model, I stole the list model because it was quicker
* and easier to demonstrate (don't need to write all the listeners)
*/
public interface DrawModel extends ListModel {
public void addElement(DrawMeShape shape);
public void removeElement(DrawMeShape shape);
}
/**
* A default implementation of the DrawModel...
*/
public class DefaultDrawModel extends DefaultListModel implements DrawModel {
@Override
public void removeElement(DrawMeShape shape) {
removeElement((Object)shape);
}
}
/**
* The actually "canvas" that shapes are rendered to
*/
protected class DrawPane extends BackgroundPane {
// Should provide ability to setModel...
private DrawModel model;
public DrawPane(DrawModel model) {
this.model = model;
model.addListDataListener(new ListDataListener() {
@Override
public void intervalAdded(ListDataEvent e) {
repaint();
}
@Override
public void intervalRemoved(ListDataEvent e) {
repaint();
}
@Override
public void contentsChanged(ListDataEvent e) {
repaint();
}
});
}
public DrawModel getModel() {
return model;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the shapes from the model...
Graphics2D g2d = (Graphics2D) g.create();
DrawModel model = getModel();
for (int index = 0; index < model.getSize(); index++) {
DrawMeShape shape = model.getElementAt(index);
shape.paint(g2d, this);
}
g2d.dispose();
}
}
/**
* A abstract concept of a shape. Personally, if I was doing it, I would
* generate an interface first, but this is just a proof of concept...
*/
public abstract class DrawMeShape {
private Rectangle bounds;
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Rectangle getBounds() {
return bounds;
}
protected abstract Shape getShape();
/**
* The shape knows how to paint, but it needs to know what to paint...
* @param g2d
* @param parent
*/
public void paint(Graphics2D g2d, JComponent parent) {
g2d = (Graphics2D) g2d.create();
Rectangle bounds = getBounds();
Shape shape = getShape();
g2d.translate(bounds.x, bounds.y);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(shape);
g2d.setColor(Color.BLACK);
g2d.draw(shape);
g2d.dispose();
}
}
/**
* An implementation of a Triangle shape...
*/
public class Triangle extends DrawMeShape {
public Triangle(Rectangle bounds) {
setBounds(bounds);
}
@Override
protected Shape getShape() {
// This should be cached ;)
Path2D path = new Path2D.Float();
Rectangle bounds = getBounds();
path.moveTo(bounds.width / 2, 0);
path.lineTo(bounds.width, bounds.height);
path.lineTo(0, bounds.height);
path.lineTo(bounds.width / 2, 0);
path.closePath();
return path;
}
}
}
Happy drawing...