Reputation: 5106
I have a code that has a button. When a button is pressed, circles appear at random positions with random colors. There can only be 10 circles.
Now that I added random colors functionality, the problem is that after each circle is drawn, its color starts changing infinetely.
How can I make it so the colors don't change?
class Panel extends JPanel {
private JButton button;
private Ellipse2D.Double[] circles;
Integer count;
public Panel() {
setup();
}
private void setup() {
count=new Integer(0);
circles=new Ellipse2D.Double[10];
button=new JButton(count.toString());
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Random r=new Random();
//position circles with diameter 100 in a way
//that it would fit in a window's size
int highX=getWidth()-100;
int highY=getHeight()-100;
circles[count]=new
Ellipse2D.Double(r.nextInt(highX),
r.nextInt(highY), 100, 100);
count++;
button.setText(count.toString());
}
});
add(button);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
paintStuff(g);
repaint();
}
private void paintStuff(Graphics g) {
Graphics2D g2=(Graphics2D) g;
g2.setPaint(Color.RED);
if (count!=0) {
for (int i=0; i<count; i++) {
g2.draw(circles[i]);
Random r=new Random();
int red=r.nextInt(256);
int green=r.nextInt(256);
int blue=r.nextInt(256);
g2.setPaint(new Color(red, green, blue));
g2.fill(circles[i]);
}
}
}
}
public class Frame extends JFrame {
private Panel panel;
public Frame() {
panel=new Panel();
add(panel);
}
public static void main(String[] args) {
Frame frame=new Frame();
}
}
Upvotes: 1
Views: 1845
Reputation: 285403
Never call repaint within a painting method as that causes a "poor-man's" animation to occur. Instead call it in your JButton's ActionListener. Also, don't randomize within the painting method, but rather do this within the ActionListener. The painting method is not under your control, and you don't want to use it to change your object's state, but rather only to display it.
Other suggestions:
public Dimension getPreferredSize()
of your JPanel and call pack()
on the JFrame after adding the JPanel but before displaying it.HashMap<Shape, Color>
.For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import javax.swing.*;
@SuppressWarnings("serial")
public class Panel2 extends JPanel {
// preferred size constants
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
// map to hold circles and colors
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
public Panel2() {
add(new JButton(new RandomColorAction()));
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// create *smooth* drawings
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintStuff(g2);
}
private void paintStuff(Graphics2D g2) {
// iterate through our map extracting all circles and colors
// and drawing them
for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
Shape shape = entry.getKey();
Color color = entry.getValue();
g2.setColor(color);
g2.fill(shape);
}
}
// listener for our button
private class RandomColorAction extends AbstractAction {
private static final int CIRC_WIDTH = 100;
private Random random = new Random();
private int count = 0;
public RandomColorAction() {
super("Random Circle: 0");
putValue(MNEMONIC_KEY, KeyEvent.VK_R);
}
@Override
public void actionPerformed(ActionEvent e) {
// create our random ellipses
int x = random.nextInt(getWidth() - CIRC_WIDTH);
int y = random.nextInt(getHeight() - CIRC_WIDTH);
Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);
// create our random color using HSB for brighter colors
float hue = random.nextFloat();
float saturation = (float) (0.8 + random.nextFloat() * 0.2);
float brightness = (float) (0.8 + random.nextFloat() * 0.2);
Color color = Color.getHSBColor(hue, saturation, brightness);
shapeColorMap.put(shape, color);
// increment count, place items into map, repaint
count++;
putValue(NAME, "Random Circle: " + count);
repaint();
}
}
private static void createAndShowGui() {
Panel2 mainPanel = new Panel2();
JFrame frame = new JFrame("Panel2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
In comments, Camickr astutely points out:
A painting method should paint the current state of the component. By using the HashMap you are introducing the possibility of randomness. The order of iteration through the map can't be guaranteed. Therefore as new entries are added to the map the order each Shape is painted could change. Generally not a problem, but if two random shapes ever overlap, the result good be flip flopping which shape is painted on top of one another.
And of course, he is absolutely correct, since there is no guaranteed order for a HashMap. Fortunately the variable itself was declared to be of Map type, and so to preserve order all one needs to do is to change the actual object type from HashMap to that of LinkedHashMap, a class which per its API:
This implementation spares its clients from the unspecified, generally chaotic ordering provided by HashMap (and Hashtable), without incurring the increased cost associated with TreeMap.
So for TLDR, change this:
private Map<Shape, Color> shapeColorMap = new HashMap<>();
to this:
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
Edited to fix the color calculation.
A just for the fun of it version that introduces Path2D and AffineTransform with a MouseListener/MouseMotionListener to allow for dragging the circles:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.swing.*;
@SuppressWarnings("serial")
public class Panel2 extends JPanel {
// preferred size constants
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
// map to hold circles and colors
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
public Panel2() {
add(new JButton(new RandomColorAction()));
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// create *smooth* drawings
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintStuff(g2);
}
private void paintStuff(Graphics2D g2) {
// iterate through our map extracting all circles and colors
// and drawing them
for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
Shape shape = entry.getKey();
Color color = entry.getValue();
g2.setColor(color);
g2.fill(shape);
}
}
private class MyMouse extends MouseAdapter {
private Entry<Shape, Color> selected = null;
private Path2D path;
private Point p = null;
@Override
public void mousePressed(MouseEvent e) {
Set<Entry<Shape, Color>> entrySet = shapeColorMap.entrySet();
// get Shape pressed
for (Entry<Shape, Color> entry : entrySet) {
if (entry.getKey().contains(e.getPoint())) {
selected = entry;
}
}
if (selected != null) {
path = new Path2D.Double(selected.getKey());
// move it to the top
entrySet.remove(selected);
shapeColorMap.put(path, selected.getValue());
p = e.getPoint();
repaint();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (selected != null) {
moveSelected(e);
}
selected = null;
}
@Override
public void mouseDragged(MouseEvent e) {
if (selected != null) {
moveSelected(e);
}
}
private void moveSelected(MouseEvent e) {
int x = e.getX() - p.x;
int y = e.getY() - p.y;
p = e.getPoint();
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
path.transform(at);
repaint();
}
}
// listener for our button
private class RandomColorAction extends AbstractAction {
private static final int CIRC_WIDTH = 100;
private Random random = new Random();
private int count = 0;
public RandomColorAction() {
super("Random Circle: 0");
putValue(MNEMONIC_KEY, KeyEvent.VK_R);
}
@Override
public void actionPerformed(ActionEvent e) {
// create our random ellipses
int x = random.nextInt(getWidth() - CIRC_WIDTH);
int y = random.nextInt(getHeight() - CIRC_WIDTH);
Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);
// create our random color using HSB for brighter colors
float hue = random.nextFloat();
float saturation = (float) (0.8 + random.nextFloat() * 0.2);
float brightness = (float) (0.8 + random.nextFloat() * 0.2);
Color color = Color.getHSBColor(hue, saturation, brightness);
shapeColorMap.put(shape, color);
// increment count, place items into map, repaint
count++;
putValue(NAME, "Random Circle: " + count);
repaint();
}
}
private static void createAndShowGui() {
Panel2 mainPanel = new Panel2();
JFrame frame = new JFrame("Panel2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Upvotes: 5
Reputation: 1358
paintStuff(Graphics g);
Is called many times, and each time it refreshes the circle color. That's the wrong place to set the color, you need to set it when you add the circle.
Create a java.awt.Color array as a global variable
private Color[] circlesColors;
Then just fill this array in the actionPerformed(...)
method. This is the setup
method with the changes
private void setup() {
count=new Integer(0);
circles=new Ellipse2D.Double[10];
circlesColors = new Color[10]; //Init the colors array to the same size of circles array
button=new JButton(count.toString());
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Random r=new Random();
int highX=getWidth()-100;
int highY=getHeight()-100;
circles[count]=new Ellipse2D.Double(r.nextInt(highX), r.nextInt(highY), 100, 100);
circlesColors[count] = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256)); //Assign random color
count++;
button.setText(count.toString());
}
});
add(button);
}
Then in your paint(...)
method
private void paintStuff(Graphics g) {
Graphics2D g2=(Graphics2D) g;
g2.setPaint(Color.RED);
if (count!=0) {
for (int i=0; i<count; i++) {
g2.draw(circles[i]);
g2.setPaint(circlesColors[i]); //Get and set the color associated to the circle
g2.fill(circles[i]);
}
}
}
Upvotes: 2