Java swing: Extended jpanel class will not brighten upon repainting. - java

I have done much more complicated problems with no issues for my course, but this is giving me fits! My problem is simple, I cannot brighten or change the color of my jpanels. I feel I a missing something crucial in how my extended jpanel class interacts with my overridden paint component as well as my other classes. The mission is simple, when one jpanel is clicked it's color should brighten, while the other two colors dim.
Run down: I extended Jframe and added 3 panels to it. I have added a mouse listener to each. When each is pressed the mouse listener does work "print statements confirmed". However it does not change the objects color. I thought that by including my mouse adapter in my TrafficLight class I would be able to change the color and call repaint. I have played with in all the ways I can think, is my organization wrong?
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
class TrafficLight3 extends JFrame {
//class variables
public JPanel red;
public JPanel yellow;
public JPanel green;
public Color r;
public Color y;
public Color gr;
public static void main ( String [] args ) {
TrafficLight3 tl = new TrafficLight3 ( );
}
// Constructor
public TrafficLight3( ) {
setTitle( "Traffic Light" );
setLayout ( new GridLayout ( 3, 1 ) );
setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
r = Color.RED;
red = new StopLightPanel( 100, r );
red.setPreferredSize( new Dimension ( 120, 120 ) );
red.addMouseListener ( new MouseClicked( ) );
add( red );
y = Color.YELLOW;
yellow = new StopLightPanel( 100, y );
yellow.setPreferredSize( new Dimension ( 120, 120 ) );
yellow.addMouseListener ( new MouseClicked( ) );
add( yellow );
gr = Color.GREEN;
green = new StopLightPanel( 100, gr );
green.addMouseListener ( new MouseClicked( ) );
green.setPreferredSize( new Dimension ( 120, 120 ) );
add ( green );
pack();
setLocationRelativeTo(null);
setVisible( true );
}
class MouseClicked extends MouseAdapter {
#Override
public void mouseClicked ( MouseEvent me ) {
if (me.getSource().equals( red ) ) {
r = r.brighter();
y = y.darker();
gr = gr.darker();
repaint();
}
if (me.getSource().equals( yellow )) {
r = r.darker();
y = y.brighter();
gr = gr.darker();
repaint();
}
if (me.getSource().equals( red )) {
r = r.darker();
y = y.darker();
gr = gr.brighter();
repaint();
}
}
}
class StopLightPanel extends JPanel {
private int diameter;
public Color color;
public StopLightPanel ( int d, Color c) {
diameter = d;
color = c;
}
public void paintComponent ( Graphics g ) {
super.paintComponent( g );
g.setColor ( color );
g.fillOval ( 10, 10, diameter, diameter );
}
}
}

You're changing a variable's value in one location, and expecting a completely different variable, the one owned by the JPanel, to change in concert, but that's not how variables work.
Instead, I would give my stop light panel a method, say public void setLightOn(boolean lightOn) and would call this method in my MouseListener. The method would change the color of the JPanel and repaint it.
For example:
class StopLightPanel extends JPanel {
private int diameter;
private Color onColor;
private Color offColor;
private boolean lightOn;
public boolean isLightOn() {
return lightOn;
}
public void setLightOn(boolean lightOn) {
this.lightOn = lightOn;
repaint();
}
public StopLightPanel(int d, Color c) {
diameter = d;
onColor = c.brighter();
offColor = c.darker().darker();
lightOn = false;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = lightOn ? onColor : offColor;
g.setColor(color);
g.fillOval(10, 10, diameter, diameter);
}
}
For example,
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
#SuppressWarnings("serial")
class TrafficLight3 extends JFrame {
private static final int DIAMETER = 100;
private static final Color[] COLORS = { Color.red, Color.yellow, Color.green };
private StopLightPanel[] lightPanels = new StopLightPanel[COLORS.length];
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TrafficLight3();
}
});
}
public TrafficLight3() {
setTitle("Traffic Light");
setLayout(new GridLayout(3, 1));
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
MouseAdapter mouseClicked = new MouseClicked();
for (int i = 0; i < COLORS.length; i++) {
lightPanels[i] = new StopLightPanel(DIAMETER, COLORS[i]);
lightPanels[i].addMouseListener(mouseClicked);
add(lightPanels[i]);
}
pack();
setLocationRelativeTo(null);
setVisible(true);
}
class MouseClicked extends MouseAdapter {
// better overriding mousePressed not mouseClicked
#Override
public void mousePressed(MouseEvent me) {
// set all lights dark
for (StopLightPanel lightPanel : lightPanels) {
lightPanel.setLightOn(false);
}
// turn only selected light on.
((StopLightPanel) me.getSource()).setLightOn(true);
}
}
class StopLightPanel extends JPanel {
// avoid "magic" numbers
private static final int GAP = 10;
private int diameter;
private Color onColor;
private Color offColor;
private boolean lightOn;
public boolean isLightOn() {
return lightOn;
}
public void setLightOn(boolean lightOn) {
this.lightOn = lightOn;
repaint();
}
public StopLightPanel(int diameter, Color color) {
this.diameter = diameter;
this.onColor = color.brighter();
this.offColor = color.darker().darker();
this.lightOn = false;
}
#Override
public Dimension getPreferredSize() {
int prefW = diameter + 2 * GAP;
int prefH = prefW;
return new Dimension(prefW, prefH);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// smooth out the edge of our circle
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color color = lightOn ? onColor : offColor;
g2.setColor(color);
g2.fillOval(GAP, GAP, diameter, diameter);
}
}
}
Somewhat MCV-ified. Missing interfaces though
import java.awt.*;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
#SuppressWarnings("serial")
class TrafficLight3 {
private static final int DIAMETER = 100;
private static void createAndShowGui() {
StopLightModel model = new StopLightModel();
StopLightView view = new StopLightView(DIAMETER);
new StopLightControl(view, model);
JFrame frame = new JFrame("Traffic Light");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getMainPanel());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum MyColor {
RED(Color.red, "Red"),
YELLOW(Color.yellow, "Yellow"),
GREEN(Color.green, "Green");
public Color getColor() {
return color;
}
public String getName() {
return name;
}
private MyColor(Color color, String name) {
this.color = color;
this.name = name;
}
private Color color;
private String name;
}
class StopLightModel {
public static final String SELECTED_COLOR = "selected color";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private MyColor selectedColor = null;
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public MyColor getSelectedColor() {
return selectedColor;
}
public void setSelectedColor(MyColor selectedColor) {
MyColor oldValue = this.selectedColor;
MyColor newValue = selectedColor;
this.selectedColor = selectedColor;
pcSupport.firePropertyChange(SELECTED_COLOR, oldValue, newValue);
}
}
class StopLightControl {
private StopLightView view;
private StopLightModel model;
public StopLightControl(final StopLightView view, final StopLightModel model) {
this.view = view;
this.model = model;
view.setStopLightControl(this);
model.addPropertyChangeListener(new ModelListener());
}
public void setSelectedColor(MyColor myColor) {
model.setSelectedColor(myColor);
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (StopLightModel.SELECTED_COLOR.equals(evt.getPropertyName())) {
MyColor myColor = model.getSelectedColor();
view.setSelectedColor(myColor);
}
}
}
}
class StopLightView {
private JPanel mainPanel = new JPanel(new GridLayout(0, 1));
private StopLightControl control;
private Map<MyColor, StopLightPanel> colorPanelMap = new EnumMap<>(MyColor.class);
public StopLightView(int diameter) {
MouseAdapter mouseClicked = new MouseClicked();
for (MyColor myColor : MyColor.values()) {
StopLightPanel lightPanel = new StopLightPanel(diameter, myColor);
lightPanel.addMouseListener(mouseClicked);
mainPanel.add(lightPanel);
colorPanelMap.put(myColor, lightPanel);
}
}
public void setSelectedColor(MyColor myColor) {
for (MyColor color : MyColor.values()) {
colorPanelMap.get(color).setLightOn(color == myColor);
}
}
public JComponent getMainPanel() {
return mainPanel;
}
public void setStopLightControl(StopLightControl control) {
this.control = control;
}
private class MouseClicked extends MouseAdapter {
#Override
public void mousePressed(MouseEvent mEvt) {
if (control == null) {
return;
}
StopLightPanel panel = (StopLightPanel) mEvt.getSource();
control.setSelectedColor(panel.getMyColor());
}
}
}
#SuppressWarnings("serial")
class StopLightPanel extends JPanel {
// avoid "magic" numbers
private static final int GAP = 10;
private int diameter;
private MyColor myColor;
private Color onColor;
private Color offColor;
private boolean lightOn;
public boolean isLightOn() {
return lightOn;
}
public void setLightOn(boolean lightOn) {
this.lightOn = lightOn;
repaint();
}
public StopLightPanel(int diameter, MyColor myColor) {
this.diameter = diameter;
this.myColor = myColor;
this.onColor = myColor.getColor().brighter();
this.offColor = myColor.getColor().darker().darker();
this.lightOn = false;
}
public MyColor getMyColor() {
return myColor;
}
#Override
public Dimension getPreferredSize() {
int prefW = diameter + 2 * GAP;
int prefH = prefW;
return new Dimension(prefW, prefH);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// smooth out the edge of our circle
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color color = lightOn ? onColor : offColor;
g2.setColor(color);
g2.fillOval(GAP, GAP, diameter, diameter);
}
}

There is no relationship between the color you are changing in the actionListener and the color the panel is using to paint itself.
Taking a look at the JavaDocs you would note that
darker/brighter
Returns: Creates a new Color that is a darker/brighter version
of this Color.
darker/brighter doesn't change the Color object itself, but creates a new Color object which is darker/brighter than the color you are using
What you should probably do is set the color you want to each panel when the actionListener triggered.
Better yet, you should be using some kind of model, changing the model would notify each instance of the panel and it would then respond by updating itself...

Related

how to output a user selected shape & color, whose location and dimensions depend upon the coordinate location of user clicks

As part of a larger project I'm trying to create a basic UI that will allow a user to click a JButton to select a desired color and then allow that user to click another JButton to designate a shape to be displayed, filled in according to the previously selected color. I understand that I must make use of action events.
Please note I am referencing this similar question:
GUI Application that allows the user to choose the shape and color of a drawing
My code thus far is:
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
public class GUI extends JFrame implements ActionListener, WindowListener
{
private final JButton circleButton, rectangleButton, redButton;
private final JButton greenButton, blueButton, exitButton;
private final JTextArea textArea;
private final JLabel label1;
private final JPanel colorPane;
private String shapeColor = "black";
private String actualShape = "rectangle";
private static final int ROWS = 2, COLS = 3;
public GUI (String title)
{
super(title);
//setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
colorPane = new JPanel();
label1 = new JLabel("current date here");
label1.setVerticalAlignment(SwingConstants.BOTTOM);
label1.setHorizontalAlignment(SwingConstants.LEFT);
label1.setPreferredSize(new Dimension(200,0));
getContentPane().add(label1, BorderLayout.WEST);
colorPane.setLayout(new GridLayout(ROWS,COLS));
getContentPane().add(colorPane, BorderLayout.CENTER);
redButton = makeButton("Red");
colorPane.add(redButton);
greenButton = makeButton("Green");
colorPane.add(greenButton);
blueButton = makeButton("Blue");
colorPane.add(blueButton);
rectangleButton = makeButton("Rectangle");
colorPane.add(rectangleButton);
circleButton = makeButton("Circle");
colorPane.add(circleButton);
exitButton = makeButton("Exit");
colorPane.add(exitButton);
textArea = new JTextArea(0,20);
getContentPane().add(textArea, BorderLayout.EAST);
pack();
}
public void paint(Graphics g, String color)
{
if (shapeColor.equalsIgnoreCase("blue") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.BLUE);
g.fillRect(50, 90, 100, 50);
}
else if (shapeColor.equalsIgnoreCase("green") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.GREEN);
g.fillOval(50, 180, 55, 55);
}
else if (shapeColor.equalsIgnoreCase("red") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.RED);
g.fillRect(50, 90, 100, 50);
}
else if (shapeColor.equalsIgnoreCase("green") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.GREEN);
g.fillRect(50,90,100,50);
}
else if (shapeColor.equalsIgnoreCase("blue") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.BLUE);
g.fillOval(50, 180, 55, 55);
}
else if (shapeColor.equalsIgnoreCase("red") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.RED);
g.fillOval(50, 180, 55, 55);
}
}
//method designed to create new JButtons while avoiding code duplication
private JButton makeButton(String text)
{
JButton b = new JButton(text);
b.setHorizontalAlignment(SwingConstants.LEFT);
b.addActionListener(this);
b.setPreferredSize(new Dimension(125,55));
return b;
}
#Override
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
#Override
public void windowClosed(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
}
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println( ( (JButton)e.getSource() ).getText() + " button pressed ");
if ( ( ((JButton) e.getSource()).getText().equalsIgnoreCase("Red")) )
{
setShapeColor("Red");
System.out.println("selected color is: " + shapeColor + " selected shape is: " + actualShape);
//paint(this.getGraphics());
}
else if ( ( ((JButton) e.getSource()).getText().equalsIgnoreCase("Blue")) )
{
setShapeColor("Blue");
System.out.println("selected color is: " + shapeColor + " selected shape is: " + actualShape);
//paint(this.getGraphics());
}
else if ( ( ((JButton) e.getSource()).getText().equalsIgnoreCase("Green")) )
{
setShapeColor("Green");
System.out.println("selected color is: " + shapeColor + " selected shape is: " + actualShape);
//paint(this.getGraphics());
}
if ( ( ((JButton) e.getSource()).getText().equalsIgnoreCase("Rectangle")) )
{
setActualShape("rectangle");
System.out.println("selected shape is: " + actualShape + " selected color is: " + shapeColor);
paint(this.getGraphics(), shapeColor);
}
else if ( ( ((JButton) e.getSource()).getText().equalsIgnoreCase("Circle")) )
{
setActualShape("circle");
System.out.println("selected shape is: " + actualShape + " selected color is: " + shapeColor);
paint(this.getGraphics(), shapeColor);
}
}
public String getShapeColor() {
return shapeColor;
}
public void setShapeColor(String shapeColor) {
this.shapeColor = shapeColor;
}
public String getActualShape() {
return actualShape;
}
public void setActualShape(String actualShape) {
this.actualShape = actualShape;
}
public static void main(String[] args)
{
new GUI("My Gui").setVisible(true);
}
}
What I've thus far achieved is an output that shows both the selected color as well as the selected shape to be rendered in the selected color.
Furthermore, I've been successful in outputting a shape whose position is hard-coded but whose type (either circle or square) and whose color (red, blue or green), is correctly output as a result of the user clicks.
The final phase, upon which I am struggling, is the implementation of the shape's output so that the user's sequence of clicks determines the location and dimensions of the shape output to the screen.
The goal is to achieve the functionality demonstrated here:
https://metrostate.learn.minnstate.edu/content/2020/4560296-20201000539/ICS372%20-%20Assignment%202%20Video.mp4?d2lSessionVal=ARifwbCHriCBrkgxBWpL9g8fL&ou=4560296
I'm relatively certain that the correct solution is similar to the following code:
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* Note: Normally the ButtonPanel and DrawingArea would not be static
classes.
* This was done for the convenience of posting the code in one class and
to
* highlight the differences between the two approaches. All the
differences
* are found in the DrawingArea class.
*/
public class DrawOnComponent
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
DrawingArea drawingArea = new DrawingArea();
ButtonPanel buttonPanel = new ButtonPanel( drawingArea );
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Draw On Component");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add(drawingArea);
frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
frame.setSize(400, 400);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
static class ButtonPanel extends JPanel implements ActionListener
{
private DrawingArea drawingArea;
public ButtonPanel(DrawingArea drawingArea)
{
this.drawingArea = drawingArea;
add( createButton(" ", Color.BLACK) );
add( createButton(" ", Color.RED) );
add( createButton(" ", Color.GREEN) );
add( createButton(" ", Color.BLUE) );
add( createButton(" ", Color.ORANGE) );
add( createButton(" ", Color.YELLOW) );
add( createButton("Clear Drawing", null) );
}
private JButton createButton(String text, Color background)
{
JButton button = new JButton( text );
button.setBackground( background );
button.addActionListener( this );
return button;
}
public void actionPerformed(ActionEvent e)
{
JButton button = (JButton)e.getSource();
if ("Clear Drawing".equals(e.getActionCommand()))
drawingArea.clear();
else
drawingArea.setForeground( button.getBackground() );
}
}
static class DrawingArea extends JPanel
{
private final static int AREA_SIZE = 400;
private ArrayList<ColoredRectangle> coloredRectangles = new ArrayList<ColoredRectangle>();
private Rectangle shape;
public DrawingArea()
{
setBackground(Color.WHITE);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
addMouseMotionListener(ml);
}
#Override
public Dimension getPreferredSize()
{
return isPreferredSizeSet() ?
super.getPreferredSize() : new Dimension(AREA_SIZE, AREA_SIZE);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
// Custom code to paint all the Rectangles from the List
Color foreground = g.getColor();
g.setColor( Color.BLACK );
g.drawString("Add a rectangle by doing mouse press, drag and release!", 40, 15);
for (DrawingArea.ColoredRectangle cr : coloredRectangles)
{
g.setColor( cr.getForeground() );
Rectangle r = cr.getRectangle();
g.drawRect(r.x, r.y, r.width, r.height);
}
// Paint the Rectangle as the mouse is being dragged
if (shape != null)
{
Graphics2D g2d = (Graphics2D)g;
g2d.setColor( foreground );
g2d.draw( shape );
}
}
public void addRectangle(Rectangle rectangle, Color color)
{
// Add the Rectangle to the List so it can be repainted
ColoredRectangle cr = new ColoredRectangle(color, rectangle);
coloredRectangles.add( cr );
repaint();
}
public void clear()
{
coloredRectangles.clear();
repaint();
}
class MyMouseListener extends MouseInputAdapter
{
private Point startPoint;
public void mousePressed(MouseEvent e)
{
startPoint = e.getPoint();
shape = new Rectangle();
}
public void mouseDragged(MouseEvent e)
{
int x = Math.min(startPoint.x, e.getX());
int y = Math.min(startPoint.y, e.getY());
int width = Math.abs(startPoint.x - e.getX());
int height = Math.abs(startPoint.y - e.getY());
shape.setBounds(x, y, width, height);
repaint();
}
public void mouseReleased(MouseEvent e)
{
if (shape.width != 0 || shape.height != 0)
{
addRectangle(shape, e.getComponent().getForeground());
}
shape = null;
}
}
class ColoredRectangle
{
private Color foreground;
private Rectangle rectangle;
public ColoredRectangle(Color foreground, Rectangle rectangle)
{
this.foreground = foreground;
this.rectangle = rectangle;
}
public Color getForeground()
{
return foreground;
}
public void setForeground(Color foreground)
{
this.foreground = foreground;
}
public Rectangle getRectangle()
{
return rectangle;
}
}
}
}
I know that I must override the 'paint' method and as a hard-coded exercise I have included the following in my code:
public void paint(Graphics g, String color)
{
if (shapeColor.equalsIgnoreCase("blue") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.BLUE);
g.fillRect(50, 90, 100, 50);
}
else if (shapeColor.equalsIgnoreCase("green") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.GREEN);
g.fillOval(50, 180, 55, 55);
}
else if (shapeColor.equalsIgnoreCase("red") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.RED);
g.fillRect(50, 90, 100, 50);
}
else if (shapeColor.equalsIgnoreCase("green") && actualShape.equalsIgnoreCase("rectangle"))
{
g.setColor(Color.GREEN);
g.fillRect(50,90,100,50);
}
else if (shapeColor.equalsIgnoreCase("blue") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.BLUE);
g.fillOval(50, 180, 55, 55);
}
else if (shapeColor.equalsIgnoreCase("red") && actualShape.equalsIgnoreCase("circle"))
{
g.setColor(Color.RED);
g.fillOval(50, 180, 55, 55);
}
}
}
I am unsure how to record coordinates of a user's button click and to then pass those coordinates into the constructor of the desired shape.
There are a couple of errors to change in your code:
Don't extend JFrame, see Extends JFrame vs. creating it inside the program, instead create an instance of it inside your class. If you need to extend from a JComponent let it be a more flexible one such as JPanel.
Don't override paint(...) method, you need to override JPanel's paintComponent(...) method and don't forget to call super.paintComponent(g) as the first line in it, otherwise you might break the paint chain and have funny / weird behaviors. Neither pass the getGraphics() object, see the Tutorial on Custom Painting
Use the Shape API rather than drawing directly on the JPanel as it provides more functionality. See this post: Create the square, rectangle, triangle of java in jframe
Don't call setPreferredSize, override the getPreferredSize, see: Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Place your program on the EDT, see point #7 in the linked post in point #3 in this same answer.
So, here's an example that follows the above recommendations:
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.event.*;
public class PaintExample {
private JFrame frame;
private JPanel pane;
private JPanel buttonsPane;
private CustomShape customShape;
private JButton squareButton;
private JButton circleButton;
private JButton purpleButton;
private JButton blueButton;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new PaintExample().createAndShowGUI());
}
private void createAndShowGUI() {
frame = new JFrame(getClass().getSimpleName()); //Create a new JFrame with a title = this class name
pane = new JPanel();
buttonsPane = new JPanel();
buttonsPane.setLayout(new GridLayout(2, 2, 5, 5)); // We generate a grid layout of 2 x 2 for our JButtons
squareButton = new JButton("Square");
circleButton = new JButton("Circle");
purpleButton = new JButton("Purple");
blueButton = new JButton("Blue");
squareButton.addActionListener(listener);
circleButton.addActionListener(listener);
purpleButton.addActionListener(listener);
blueButton.addActionListener(listener);
buttonsPane.add(squareButton);
buttonsPane.add(circleButton);
buttonsPane.add(purpleButton);
buttonsPane.add(blueButton);
customShape = new CustomShape(); // We create an instance of our custom JPanel class
pane.setLayout(new BorderLayout());
pane.add(customShape);
pane.add(buttonsPane, BorderLayout.SOUTH);
frame.add(pane);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
ActionListener listener = e -> { //Java 8+, for Java 7- add the actionPerformed method instead of the lambda expression
// We check which button was clicked and set the shape / color for our custom class
if (e.getSource().equals(squareButton)) {
customShape.setShape(ShapeToDraw.SQUARE);
} else if (e.getSource().equals(circleButton)) {
customShape.setShape(ShapeToDraw.CIRCLE);
} else if (e.getSource().equals(purpleButton)) {
customShape.setColor(Color.MAGENTA);
} else if (e.getSource().equals(blueButton)) {
customShape.setColor(Color.BLUE);
}
};
enum ShapeToDraw {
SQUARE, CIRCLE // You can define here other properties for each enum option
}
class CustomShape extends JPanel {
private Color color;
private ShapeToDraw shape;
public CustomShape() {
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
this.repaint(); // Everytime we set the color we ask the component to repaint itself
}
public ShapeToDraw getShape() {
return shape;
}
public void setShape(ShapeToDraw shape) {
this.shape = shape;
this.repaint(); // Everytime we set the shape we ask the component to repaint itself
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200); // We define the panel's size
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color != null ? color : Color.BLACK); //If we haven't set the Color yet, we default it to black, otherwise we set the color to the one chosen by the user.
if (shape == ShapeToDraw.SQUARE) { //If the shape is a square, we draw a square
g2d.draw(new Rectangle2D.Double(50, 50, 100, 100)); // Change the coordinates that you get by user click using the MouseListener
} else if (shape == ShapeToDraw.CIRCLE) { // Or we draw a circle
g2d.draw(new Ellipse2D.Double(50, 50, 100, 100));
}
}
}
}
This is how the program looks like:
I am unsure how to record coordinates of a user's button click and to then pass those coordinates into the constructor of the desired shape.
To get the coordinates relative to your window see: How to get location of a mouse click relative to a swing window
The following is a suggested implementation, also incorporating the good guidance you got from Frakcool:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class GUI
{
private final ButtonGroup colorGroup; //group buttons
private final ButtonGroup shapeGroup; //so only one can be selected at any given time
private final Map<String, Color> colors; //map colors to it names.
private Color color; // color for painting
private Shape shape; //shape to paint
private JFrame frame;
private JPanel buttonsPane;
private JTextArea textArea;
private static final int ROWS = 2, COLS = 3;
private static final String[] BUTTONS_LABELS = {"Rectangle", "Circle", "Exit"};
public GUI()
{
shapeGroup = new ButtonGroup(); colorGroup = new ButtonGroup();
colors = new HashMap<>();
colors.put("Red", Color.RED); colors.put("Green", Color.GREEN); colors.put("Blue", Color.BLUE);
}
private void createAndShowGUI(String title) {
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//use a GridLayout for the buttons pane
buttonsPane = new JPanel();
buttonsPane.setLayout(new GridLayout(ROWS, COLS));
frame.getContentPane().add(buttonsPane, BorderLayout.CENTER);//each BorderLayout position can hold ONE component
for(String colorName : colors.keySet()){
JToggleButton button = makeButton(colorName);
buttonsPane.add(button);
colorGroup.add(button);
button.addActionListener(changeColorAction());
}
for(String text : BUTTONS_LABELS){
JToggleButton button = makeButton(text);
buttonsPane.add(button);
shapeGroup.add(button);
button.addActionListener(changeShapeAction());
}
setDefaults();
frame.getContentPane().add(new Draw(), BorderLayout.WEST);
textArea = new JTextArea(0,20);
frame.getContentPane().add(textArea, BorderLayout.EAST);
frame.pack();
frame.setVisible(true);
}
private JToggleButton makeButton(String text) {
JToggleButton b = new JToggleButton(text); //use toggle buttons
b.setHorizontalAlignment(SwingConstants.LEFT);
b.setPreferredSize(new Dimension(100, 80)); //set preferred and let Layout manager do its work
return b;
}
private ActionListener changeColorAction() {
return e->{
color = colors.get(((JToggleButton)e.getSource()).getText());
frame.repaint();
};
}
private ActionListener changeShapeAction() {
return e->{
switch (((JToggleButton)e.getSource()).getText()){
case "Circle":
shape = Shape.CIRCLE;
break;
case "Rectangle":
shape = Shape.RECTANGLE;
break;
default: System.exit(0);
}
frame.repaint();
};
}
private void setDefaults() {
colorGroup.getElements().nextElement().setSelected(true);
color = colors.get(colorGroup.getElements().nextElement().getText());
shapeGroup.getElements().nextElement().setSelected(true);
shape = Shape.RECTANGLE;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GUI().createAndShowGUI("My Gui"));
}
class Draw extends JPanel{
private final Point[] points; // an array to hold clicked points
private int mouseClicks = 0;
private static final int POINT_SIZE = 2;
Draw(){
setLayout(new BorderLayout());
setBackground(Color.WHITE);
setPreferredSize(new Dimension(200, 200));
JLabel help = new JLabel("Click 2 points to draw");
help.setHorizontalAlignment(SwingConstants.CENTER);
add(help, BorderLayout.PAGE_START);
JLabel timeLabel = new JLabel("current time here");
timeLabel.setHorizontalAlignment(SwingConstants.LEFT);
add(timeLabel, BorderLayout.PAGE_END);
points = new Point[2];
addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent e) {
addPoint(e.getX(), +e.getY() );
}
});
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(color);
for(Point point : points)
if(point != null){
g.drawOval(point.x, point.y, POINT_SIZE, POINT_SIZE);
}
drawShape((Graphics2D)g);
}
private void addPoint(int x, int y) {
if(mouseClicks ==2){
mouseClicks = 0;
points[1] = null;
}
points[mouseClicks++] = new Point(x, y);
repaint();
}
private void drawShape(Graphics2D g2d) {
if(points[0] == null || points[1] == null) return;
if(shape == Shape.RECTANGLE) {
drawRectangle(g2d);
}else{
drawCircle(g2d);
}
}
private void drawRectangle(Graphics2D g2D) {
int minX = Math.min(points[0].x, points[1].x);
int minY = Math.min(points[0].y, points[1].y);
int maxX = Math.max(points[0].x, points[1].x);
int maxY = Math.max(points[0].y, points[1].y);
int width = maxX - minX;
int height = maxY - minY;
Rectangle2D.Double rectangle = new Rectangle2D.Double(minX, minY, width, height);
g2D.draw(rectangle);
}
private void drawCircle(Graphics2D g2D) {
int minX = Math.min(points[0].x, points[1].x);
int minY = Math.min(points[0].y, points[1].y);
int maxX = Math.max(points[0].x, points[1].x);
int maxY = Math.max(points[0].y, points[1].y);
double dx = maxX - minX;
double dy = maxY - minY;
double radius = Math.sqrt(dx*dx + dy*dy)/2;
double centerX = minX + dx/2;
double centerY = minY + dy/2;
g2D.draw(new Ellipse2D.Double(centerX - radius , centerY - radius, 2* radius, 2* radius));
}
}
enum Shape {
RECTANGLE, CIRCLE
}
}

Can paintComponent() be used in an AbstractAction class?

I am trying to make a program that creates a JPanel, and when the user presses W, A, S, and D, a cube that is drawn will navigate around in the window (by a certain amount every time a key is pressed), I have created the MoveCubeUp class, and I override the paintComponent method in it to repaint the cube when it is called, but it will not work. Could someone explain why?
public MyPanel(){
…
MoveSquareUp m=new MoveSquareUp(squareX, squareY);
getInputMap().put(KeyStroke.getKeyStroke(("W"), "pressed"));
getActionMap().put("pressed", m)
}
class MoveSquareUp extends AbstractAction{
public int squareXX, squareYY;
public moveSquare(){
squareXX=squareX+5;
}
//I define the paintComponent method to draw the rectangle with its set height
//at squareXX, squareYY
//action method is null (I am still trying to figure out binding keys to
//actions but the paintComponent not working is preventing that
}
I apologize if that was poorly formatted. 1st post :/
Does the paint method need to be defined within the class that extends JFrame, and if so, how can I use it with an abstractAction class (or how can I avoid the AbstractAction class altogether)?
The crux of your problem is that you need to learn to separate your model from your view from your control. Here the model is the location of your sprite, the view is the GUI that draws this position, and the control will hold the actions including your AbstractAction, and they all should be separate from each other if possible.
So to answer your direct question -- no paintComponent should definitely not be inside of an AbstractAction, since the former is a key part of the view while the latter is a key part of the control. Instead have your view reflect the state of the model, and the model's state will be changed by the control (the actions).
Regarding your other question, should all painting methods be part of the JFrame: none of the painting methods should be in a class extending JFrame since this class is a complex class that creates a top level window and several sub components to display your GUI, and if you override its painting, you can effect painting of sub components in bad ways. Instead draw in the paintComponent method of a class that extends JPanel, and then display this object in your JFrame.
For example:
package pkg3;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
public class GamePanel extends JPanel {
private static final int ANIMATION_DELAY = 15;
private final int HEIGHT = 400;
private final int WIDTH = 600;
private Square square;
private EnumMap<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);
private Map<Integer, Direction> keyToDir = new HashMap<>();
// !! private Circle circle;
private Timer animationTimer;
public GamePanel() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.FALSE);
}
keyToDir.put(KeyEvent.VK_UP, Direction.UP);
keyToDir.put(KeyEvent.VK_DOWN, Direction.DOWN);
keyToDir.put(KeyEvent.VK_LEFT, Direction.LEFT);
keyToDir.put(KeyEvent.VK_RIGHT, Direction.RIGHT);
setKeyBindings();
setBackground(Color.white);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
square = new Square();
animationTimer = new Timer(ANIMATION_DELAY, new AnimationListener());
animationTimer.start();
}
private void setKeyBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
final InputMap inputMap = getInputMap(condition);
final ActionMap actionMap = getActionMap();
boolean[] keyPressed = { true, false };
for (Integer keyCode : keyToDir.keySet()) {
Direction dir = keyToDir.get(keyCode);
for (boolean onKeyPress : keyPressed) {
boolean onKeyRelease = !onKeyPress; // to make it clear how
// bindings work
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0, onKeyRelease);
Object key = keyStroke.toString();
inputMap.put(keyStroke, key);
actionMap.put(key, new KeyBindingsAction(dir, onKeyPress));
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
square.display(g);
}
private class AnimationListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent evt) {
boolean repaint = false;
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
square.move(dir);
repaint = true;
}
}
if (repaint) {
repaint();
}
}
}
private class KeyBindingsAction extends AbstractAction {
private Direction dir;
boolean pressed;
public KeyBindingsAction(Direction dir, boolean pressed) {
this.dir = dir;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent evt) {
dirMap.put(dir, pressed);
}
}
private static void createAndShowGUI() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame("GamePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
gamePanel.requestFocusInWindow();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
enum Direction {
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
private int incrX;
private int incrY;
private Direction(int incrX, int incrY) {
this.incrX = incrX;
this.incrY = incrY;
}
public int getIncrX() {
return incrX;
}
public int getIncrY() {
return incrY;
}
}
class Square {
private int x = 0;
private int y = 0;
private int w = 20;
private int h = w;
private int step = 1;
private Color color = Color.red;
private Color fillColor = new Color(255, 150, 150);
private Stroke stroke = new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
public void display(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(fillColor);
g2d.fillRect(x, y, w, h);
g2d.setStroke(stroke);
g2d.setColor(color);
g2d.drawRect(x, y, w, h);
g2d.dispose();
}
public void setStep(int step) {
this.step = step;
}
public void move(Direction dir) {
x += step * dir.getIncrX();
y += step * dir.getIncrY();
}
}

Generating specific shapes with random dimensions using a JComboBox to select shapes

I am trying to create a program that uses a JComboBox containing specific shapes (Circle, Square, Oval, Rectangle). After the user clicks on a specified shape, the Panel will display 20 of that shape in random dimensions and locations.
I am having trouble on how to make the shapes have random dimensions and locations. Here is my code so far. Any advice or sources to look at would be appreciated.
Thank you.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.event.*;
public class HW1b extends JFrame
{
public HW1b()
{
super("Shapes");
final ComboPanel comboPanel = new ComboPanel();
String[] shapeItems = {"Circle", "Square", "Oval", "Rectangle"};
JComboBox shapeBox = new JComboBox<String>(shapeItems);
shapeBox.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent ie)
{
if (ie.getStateChange() == ItemEvent.SELECTED)
{
String item = (String)ie.getItem();
if(shapeBox.getSelectedItem().equals("Circle"))
comboPanel.makeCircles();
if(shapeBox.getSelectedItem().equals("Square"))
comboPanel.makeSquares();
if(shapeBox.getSelectedItem().equals("Oval"))
comboPanel.makeOvals();
if(shapeBox.getSelectedItem().equals("Rectangle"))
comboPanel.makeRectangles();
}
}
});
JPanel southPanel = new JPanel();
southPanel.add(shapeBox);
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(comboPanel, "Center");
getContentPane().add(southPanel, "South");
setSize( 400, 400 );
setLocation( 200, 200 );
setVisible( true );
}
private class ComboPanel extends JPanel
{
int w, h;
Random rand;
static final int OVAL = 0;
static final int RECTANGLE = 1;
int shapeType = -1;
public ComboPanel()
{
rand = new Random();
setBackground(Color.white);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth();
int height = getHeight();
int x, y;
Shape s = null;
for (int i = 0; i < 20; i++)
{
x = rand.nextInt(width - w);
y = rand.nextInt(width - h);
switch(shapeType)
{
case OVAL: s = new Ellipse2D.Double(x,y,w,h);
break;
case RECTANGLE: s = new Rectangle2D.Double(x,y,w,h);
break;
}
if (shapeType > -1)
g2d.draw(s);
}
}
public void makeCircles()
{
shapeType = OVAL;
w = 75;
h = 75;
repaint();
}
public void makeSquares()
{
shapeType = RECTANGLE;
w = 50;
h = 50;
repaint();
}
public void makeOvals()
{
shapeType = OVAL;
w = 80;
h = 60;
repaint();
}
public void makeRectangles()
{
shapeType = RECTANGLE;
w = 80;
h = 40;
repaint();
}
}
public static void main(String[] args)
{
new HW1b();
}
}
You're hard-coding w and h in your code, and so there's no way for this to vary among your shapes. Instead of doing this, use your Random variable, rand, to select random w and h values that are within some desired range. Myself, I wouldn't create my shapes within the paintComponent method since painting is not fully under my control and can occur when I don't want it to. For instance, in your code, your shapes will vary tremendously if the GUI is resized. Instead I'd create a collection such as an ArrayList<Shape> and fill it with created Shape objects (i.e., Ellipse2D for my circles) when desired, and then iterate through that collection within your paintComponent method, drawing your shapes.
for example...
import java.awt.BorderLayout;
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.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
public class SomeShapes extends JPanel {
private ShapePanel shapePanel = new ShapePanel();
private JComboBox<MyShape> myShapeCombo = new JComboBox<>(MyShape.values());
public SomeShapes() {
myShapeCombo.setSelectedIndex(-1);
myShapeCombo.addItemListener(new ComboListener());
JPanel bottomPanel = new JPanel();
bottomPanel.add(myShapeCombo);
setLayout(new BorderLayout());
add(shapePanel, BorderLayout.CENTER);
add(bottomPanel, BorderLayout.PAGE_END);
}
private class ComboListener implements ItemListener {
#Override
public void itemStateChanged(ItemEvent e) {
MyShape myShape = (MyShape) e.getItem();
shapePanel.drawShapes(myShape);
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SomeShapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SomeShapes());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum MyShape {
OVAL("Oval"), RECTANGLE("Rectangle"), SQUARE("Square"), CIRCLE("Circle");
private String name;
private MyShape(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return getName();
}
}
class ShapePanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final Color SHAPE_COLOR = Color.BLUE;
private static final int SHAPE_COUNT = 20;
private static int MIN = 5;
private static int MAX = 200;
private List<Shape> shapeList = new ArrayList<>();
private Random random = new Random();
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void drawShapes(MyShape myShape) {
shapeList.clear(); // empty the shapeList
switch (myShape) {
case OVAL:
drawOval();
break;
case RECTANGLE:
drawRectangle();
break;
// etc...
default:
break;
}
repaint();
}
private void drawOval() {
// for loop to do this times SHAPE_COUNT(20) times.
for (int i = 0; i < SHAPE_COUNT; i++) {
// first create random width and height
int w = random.nextInt(MAX - MIN) + MIN;
int h = random.nextInt(MAX - MIN) + MIN;
// then random location, but taking care so that it
// fully fits into our JPanel
int x = random.nextInt(getWidth() - w);
int y = random.nextInt(getHeight() - h);
// then create new Shape and place in our shapeList.
shapeList.add(new Ellipse2D.Double(x, y, w, h));
}
}
private void drawRectangle() {
// .... etc
}
//.. .. etc
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
// set rendering hints for smooth ovals
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(SHAPE_COLOR);
// iterate through the shapeList ArrayList
for (Shape shape : shapeList) {
g2d.draw(shape); // and draw each Shape it holds
}
}
}

JPanel size is not known on start

When I create a Board instance from my Square instance, I try to assign the size of the window to the integers x and y. I fail to do this because it seems like on start the size is 0. In the constructor in Board.java, x and y shouldn't be -50 like they end up now.
Square.java:
package Square;
import javax.swing.*;
public class Square extends JFrame {
public Square(){
add(new Board());
setSize(800, 800);
setVisible(true);
}
public static void main(String[] args){
new Square();
}
}
Board.java
package Square;
import javax.swing.*;
import java.awt.*;
public class Board extends JPanel{
int x,y;
public Board(){
x = width-50;
y = height-50;
}
public int width = (int) getSize().getWidth();
public int height = (int) getSize().getHeight();
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillRect(x,y, 100, 100);
}
}
Full Code for clarification:
Square.java
package Square;
import javax.swing.*;
public class Square extends JFrame {
public Square(){
Board board = new Board();
board.start();
add(board);
setTitle("Square");
setSize(800, 800);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args){
Square square = new Square();
square.setVisible(true);
square.setLocation(2000, 150);
}
}
Board.java
package Square;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Board extends JPanel implements ActionListener{
Timer timer;
int x, y;
int velX = 0;
int velY = 0;
public Board(){
setFocusable(true);
timer = new Timer(1, this);
addKeyListener(new TAdapter());
}
class TAdapter extends KeyAdapter{
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch(keyCode){
case KeyEvent.VK_ESCAPE: x = width()/2-50; y = height()/2-50; break;
case KeyEvent.VK_RIGHT: velX = 1; break;
case KeyEvent.VK_DOWN: velY = 1; break;
case KeyEvent.VK_LEFT: velX = -1; break;
case KeyEvent.VK_UP: velY = -1; break;
}
}
public void keyReleased(KeyEvent e){
velX = 0;
velY = 0;
}
}
public int width(){ return (int) getSize().getWidth();}
public int height(){ return (int) getSize().getHeight();}
public void start(){
timer.setInitialDelay(100);
timer.start();
x = width()/2-50;
y = height()/2-50;
}
#Override
public void actionPerformed(ActionEvent e) {
x += velX;
y += velY;
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(x,y, 100, 100);
}
}
Put the calculation into your paintComponent method. In general you want to avoid having code in paintComponent that will slow it down, but these calls shouldn't give too much penalty. Also, if your program is re-sizable, you'll need to be able to re-calculate the sizes of things on the go like this:
public void paintComponent(Graphics g){
super.paintComponent(g);
int x = getWidth() - 50;
int y = getHeight() - 50;
g.fillRect(x, y, 100, 100);
}
but of course in your real program, you will want to avoid "magic" numbers
Another issue: don't call setSize() on your JFrame. Instead, if you want to specify a hard size, do so in the JPanel by overriding its getPreferredSize() method. This will also then give you the suggested parameters that can be used for your calculations.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawRect extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = PREF_W;
private static final int DELTA = 50;
private static final Color RECT_COLOR = Color.red;
private static final int RECT_WIDTH = 100;
private static final int TIMER_DELAY = 15;
private int rectX = PREF_W - DELTA;
private int rectY = PREF_H - DELTA;
public DrawRect() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(RECT_COLOR);
g.fillRect(rectX, rectY, RECT_WIDTH, RECT_WIDTH);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
rectX--;
rectY--;
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("DrawRect");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new DrawRect());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Also, check out the key bindings animation code from this answer of mine.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
public class GamePanel extends JPanel {
private static final int ANIMATION_DELAY = 15;
private final int HEIGHT = 400;
private final int WIDTH = 600;
private Square square;
private EnumMap<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);
private Map<Integer, Direction> keyToDir = new HashMap<>();
// !! private Circle circle;
private Timer animationTimer;
public GamePanel() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.FALSE);
}
keyToDir.put(KeyEvent.VK_UP, Direction.UP);
keyToDir.put(KeyEvent.VK_DOWN, Direction.DOWN);
keyToDir.put(KeyEvent.VK_LEFT, Direction.LEFT);
keyToDir.put(KeyEvent.VK_RIGHT, Direction.RIGHT);
// !! addKeyListener(new DirectionListener());
setKeyBindings();
setBackground(Color.white);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
square = new Square();
animationTimer = new Timer(ANIMATION_DELAY, new AnimationListener());
animationTimer.start();
}
private void setKeyBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
final InputMap inputMap = getInputMap(condition);
final ActionMap actionMap = getActionMap();
boolean[] keyPressed = { true, false };
for (Integer keyCode : keyToDir.keySet()) {
Direction dir = keyToDir.get(keyCode);
for (boolean onKeyPress : keyPressed) {
boolean onKeyRelease = !onKeyPress; // to make it clear how bindings work
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0,
onKeyRelease);
Object key = keyStroke.toString();
inputMap.put(keyStroke, key);
actionMap.put(key, new KeyBindingsAction(dir, onKeyPress));
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
square.display(g);
}
private class AnimationListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent evt) {
boolean repaint = false;
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
square.move(dir);
repaint = true;
}
}
if (repaint) {
repaint();
}
}
}
private class KeyBindingsAction extends AbstractAction {
private Direction dir;
boolean pressed;
public KeyBindingsAction(Direction dir, boolean pressed) {
this.dir = dir;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent evt) {
dirMap.put(dir, pressed);
}
}
private static void createAndShowGUI() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame("GamePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
gamePanel.requestFocusInWindow();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
enum Direction {
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
private int incrX;
private int incrY;
private Direction(int incrX, int incrY) {
this.incrX = incrX;
this.incrY = incrY;
}
public int getIncrX() {
return incrX;
}
public int getIncrY() {
return incrY;
}
}
class Square {
private int x = 0;
private int y = 0;
private int w = 20;
private int h = w;
private int step = 1;
private Color color = Color.red;
private Color fillColor = new Color(255, 150, 150);
private Stroke stroke = new BasicStroke(3f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
public void display(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(fillColor);
g2d.fillRect(x, y, w, h);
g2d.setStroke(stroke);
g2d.setColor(color);
g2d.drawRect(x, y, w, h);
g2d.dispose();
}
public void setStep(int step) {
this.step = step;
}
public void move(Direction dir) {
x += step * dir.getIncrX();
y += step * dir.getIncrY();
}
}
Hey your codestyle is horrible, but i try to help :). You can't get a size of an undrawn window. First Things first, your Constructor is wrong, you add the Board that actually create the Board Obj. Calling the Constructor of Board, which has no drawn parent yet and no x,y set. Try to initialize your variables in the Constructor. So just use width and height and fill the values in the constructor. Next, just tell your board its creation size by passing its parent size trough constructor variables.
I think you try to learn java and this is much more elegant. Furthermore, try to do all parent modification before adding some to it. So first setSize, add some Layout (Border/Flow/whatuwish) then get the frames ContentPane and add your Board component. To make things clear, you can't get e.g. the parent and parent size in Contructor because your board Obj isn't created and added yet. If you wish to getParent() and its size, create the Object add it to JFrame and than you can call getParent().getSize(). You get 0 because your JPanel isn't drawn at this time (before creation). If you wish to get the Parent Size just pass the JFrame Ref to Constructor or its size. Another Advise, don't create things in things in things, keep in mind with your code you create your JPanel as first Obj... Here is some example code:
Square:
public class Square extends JFrame {
public static void main(String[] args){
Square square = new Square();
square.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension d = new Dimension(800,800);
square.setPreferredSize(d);
square.setSize(d);
//too much, every Jframe has BorderLayout enabled
square.getContentPane().setLayout(new BorderLayout());
square.getContentPane().add(new Board(square), BorderLayout.CENTER);
square.pack();
square.setVisible(true);
}
}
Board:
public class Board extends JPanel{
int x,y;
JFrame parent;
public Board(JFrame parent){
int width = parent.getPreferredSize().width;
int height = parent.getPreferredSize().height;
x = width-50;
y = height-50;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillRect(x,y, 100, 100);
}
}
You can take x and y values after the panel has become visible, in the next EDT cycle, by using SwingUtilities.invokeLater, for example.

Creating custom JComponent with JLayer over JProgressBar

I'm attempting to create a customized JProgressBar that utilizes the JLayer class so that it can be colored differently depending on the situation, a la this solution. The thing is that I want to wrap it up as a JComponent of some sort since it makes it more manageable. I would expose it as a customized JLayer, but that class is sealed, so nothing doing.
I've tried just making it a JComponent, but nothing is drawn on the screen (probably because I have no idea how to make a custom JComponent that contains other components inside of it?). I've tried a JFrame, which works, but the sizing is all wrong, likely because the progress bar is using the layout manager of the JFrame I made instead of the layout manager containing the JFrame. I've tried JProgressBar, which draws, but then I have no way of returning the JLayer and preserving the correct hierarchy without additional method calls after the constructor, which just doesn't seem elegant.
Here's my sample code, based heavily on the code from the question I linked to above:
public class Test {
public JComponent makeUI() {
final BoundedRangeModel model = new DefaultBoundedRangeModel();
model.setValue(20);
final JPanel p = new JPanel(new GridLayout(4, 1, 12, 12));
p.setBorder(BorderFactory.createEmptyBorder(24,24,24,24));
// This does not draw
final ColorProgressBar pb4 = new ColorProgressBar(model);
p.add(pb4);
JPanel panel = new JPanel(new BorderLayout());
panel.add(p, BorderLayout.NORTH);
return panel;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new Test().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ColorProgressBar extends JComponent {
private static final long serialVersionUID = -1265489165072929352L;
private BlockedColorLayerUI colorUI = new BlockedColorLayerUI();
private JLayer<JProgressBar> layer;
private JProgressBar bar;
private PropertyChangeSupport supporter = new PropertyChangeSupport(new WeakReference<ColorProgressBar>(this));
public ColorProgressBar(BoundedRangeModel model) {
bar = new JProgressBar(model);
layer = new JLayer<JProgressBar>(bar, colorUI);
this.add(layer);
}
public Color getColor() {
if (colorUI == null)
return null;
return colorUI.color;
}
public void setColor(Color color) {
Color oldColor = colorUI.color;
colorUI.color = color;
supporter.firePropertyChange("color", oldColor, color);
}
#Override
public void paintComponents(Graphics g) {
layer.paintComponents(g);
}
class BlockedColorLayerUI extends LayerUI<JProgressBar> {
public Color color = null;
private BufferedImage bi;
private int prevw = -1;
private int prevh = -1;
#Override public void paint(Graphics g, JComponent c) {
if(color != null) {
JLayer<?> jlayer = (JLayer<?>)c;
JProgressBar progress = (JProgressBar)jlayer.getView();
int w = progress.getSize().width;
int h = progress.getSize().height;
if(bi==null || w!=prevw || h!=prevh) {
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
prevw = w;
prevh = h;
Graphics2D g2 = bi.createGraphics();
super.paint(g2, c);
g2.dispose();
Image image = c.createImage(
new FilteredImageSource(bi.getSource(),
new ColorFilter(color)));
g.drawImage(image, 0, 0, c);
} else {
super.paint(g, c);
}
}
}
class ColorFilter extends RGBImageFilter {
Color color;
public ColorFilter(Color color) {
this.color = color;
}
#Override public int filterRGB(int x, int y, int argb) {
int r = (int)((argb >> 16) & 0xff);
int g = (int)((argb >> 8) & 0xff);
int b = (int)((argb ) & 0xff);
return (argb & color.getRGB()) | (g<<16) | (r<<8) | (b);
}
}
}
Anyone know where I'm going wrong? Thanks!
EDIT: I've trimmed down the example slightly and modified it to better express my issue. Sorry for the confusion.
You may need to call super(model); and p.add(pb4.layer);
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.beans.*;
import java.lang.ref.WeakReference;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
public class Test2 {
public JComponent makeUI() {
final BoundedRangeModel model = new DefaultBoundedRangeModel();
final JPanel p = new JPanel(new GridLayout(4, 1, 12, 12));
p.setBorder(BorderFactory.createEmptyBorder(24,24,24,24));
final JProgressBar pb1 = new JProgressBar(model);
pb1.setStringPainted(true);
p.add(pb1);
final JProgressBar pb2 = new JProgressBar(model);
pb2.setStringPainted(true);
p.add(pb2);
p.add(new JProgressBar(model));
final ColorProgressBar pb4 = new ColorProgressBar(model);
p.add(pb4.layer);
JPanel box = new JPanel();
box.add(new JButton(new AbstractAction("+10") {
private int i = 0;
#Override public void actionPerformed(ActionEvent e) {
model.setValue(i = (i>=100) ? 0 : i + 10);
}
}));
//http://msdn.microsoft.com/en-us/library/windows/desktop/aa511486.aspx
box.add(new JCheckBox(new AbstractAction(
"<html>Turn the progress bar red<br />"+
" when there is a user recoverable condition<br />"+
" that prevents making further progress.") {
#Override public void actionPerformed(ActionEvent e) {
boolean b = ((JCheckBox)e.getSource()).isSelected();
pb2.setForeground(b? new Color(255,0,0,100) : new Color(255,255,0,100));
if (b)
pb4.setColor(Color.RED);
else
pb4.setColor(Color.YELLOW);
p.repaint();
}
}));
JPanel panel = new JPanel(new BorderLayout());
panel.add(p, BorderLayout.NORTH);
panel.add(box, BorderLayout.SOUTH);
return panel;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new Test2().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ColorProgressBar extends JProgressBar {
private static final long serialVersionUID = -1265489165072929352L;
private BlockedColorLayerUI colorUI = new BlockedColorLayerUI();
public JLayer<JProgressBar> layer;
private PropertyChangeSupport supporter = new PropertyChangeSupport(new WeakReference<ColorProgressBar>(this));
public ColorProgressBar(BoundedRangeModel model) {
super(model);
layer = new JLayer<JProgressBar>(this, colorUI);
}
public Color getColor() {
if (colorUI == null)
return null;
return colorUI.color;
}
public void setColor(Color color) {
Color oldColor = colorUI.color;
colorUI.color = color;
supporter.firePropertyChange("color", oldColor, color);
}
// #Override
// public void paintComponents(Graphics g) {
// layer.paintComponents(g);
// }
class BlockedColorLayerUI extends LayerUI<JProgressBar> {
public Color color = null;
private BufferedImage bi;
private int prevw = -1;
private int prevh = -1;
#Override public void paint(Graphics g, JComponent c) {
if(color != null) {
JLayer<?> jlayer = (JLayer<?>)c;
JProgressBar progress = (JProgressBar)jlayer.getView();
int w = progress.getSize().width;
int h = progress.getSize().height;
if(bi==null || w!=prevw || h!=prevh) {
bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
prevw = w;
prevh = h;
Graphics2D g2 = bi.createGraphics();
super.paint(g2, c);
g2.dispose();
Image image = c.createImage(
new FilteredImageSource(bi.getSource(),
new ColorFilter(color)));
g.drawImage(image, 0, 0, c);
} else {
super.paint(g, c);
}
}
}
class ColorFilter extends RGBImageFilter {
Color color;
public ColorFilter(Color color) {
this.color = color;
}
#Override public int filterRGB(int x, int y, int argb) {
int r = (int)((argb >> 16) & 0xff);
int g = (int)((argb >> 8) & 0xff);
int b = (int)((argb ) & 0xff);
return (argb & color.getRGB()) | (g<<16) | (r<<8) | (b);
}
}
}

Categories