I am writing a program which involves creating a JFrame and drawing a circle inside it using drawOval() from the Graphics class. I have reached a problem where I am trying to create a point at the centre of the JFrame, and then draw my circle with this pont being the x and y coordinates of the circle. Here is my code so far:
import java.awt.Graphics;
import javax.swing.JFrame;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.Point;
class MouseJFrameMotion extends JFrame implements MouseMotionListener{
int circleXcenter;
int circleYcenter;
int circleRadius = 50;
boolean show = false;
public MouseJFrameMotion(){
addMouseMotionListener(this);
}
public void paint(Graphics g){
super.paint(g);
if(show){
g.drawOval(circleXcenter,circleYcenter, circleRadius*2,circleRadius*2);
}
}
public void mouseDragged(MouseEvent e){
}
Point frameCenter = new Point((this.getWidth()/2), (this.getHeight()/2));
public void mouseMoved(MouseEvent e){
int xLocation = e.getX();
int yLocation = e.getY();
show = true;
circleXcenter = (int) frameCenter.getX();
circleYcenter = (int) frameCenter.getY();
repaint();
}
}
public class GrowingCircle {
public static void main(String[] args) {
MouseJFrameMotion myMouseJFrame = new MouseJFrameMotion();
myMouseJFrame.setSize(500, 500);
myMouseJFrame.setVisible(true);
}
}
As you can see in the main() function, I set the size of the JFrame to 500x500. However, when the circle is drawn, it's x and y coordinates are (0,0) when I expect them to be (250, 250) based on Point frameCenter after repaint() is called. Where am I going wrong?
So two things...
Don't override paint of JFrame, there a JRootPane, contentPane and other components between the user and the frames surface which can interfere with the painting. Instead, use a JPanel and override its paintComponent method
At the time Point frameCenter = new Point((this.getWidth()/2), (this.getHeight()/2)); is evaluated, the frame's size is 0x0, you need to reevaluate the frameCenter before you paint the circle. When you do this will depend on how dynamic you want the change to be
I think you need both repaint() and revalidate() method
When you are constructing the class MouseJFrameMotion, the variable frameCenter is defined and set width and height 0 and it will never change. So what you can do is to calculate the frame center when you are drawing.
public void mouseMoved(MouseEvent e) {
int xLocation = e.getX();
int yLocation = e.getY();
show = true;
Point frameCenter = new Point((this.getWidth() / 2), (this.getHeight() / 2));
circleXcenter = (int) frameCenter.getX();
circleYcenter = (int) frameCenter.getY();
repaint();
}
Related
BACKGROUND
I'm currently working on a project where I want to be able to draw a triangle, circle and a rectangle on a JPanel and move them around.
When a figure is moved, it should end up "at the top" so that it covers other figures that are in an overlapping position and when a figure is moved, the "top" is selected; if several figures cover the position of the mouse pointer, the top one is selected.
MY PROBLEM
I cannot figure out how to fix the issue of my shapes not ending up on top of each other if I drag them to the same position. They just stay in the same layers and if i stack them all on top of each other I still pick up the rectangle first.
They just stay in the same layers...
That's because of the order in which you are drawing them:
drawRec.paintComponent(g);
drawCirc.paintComponent(g);
drawTri.paintComponent(g);
Which will result in the drawTri to always be painted on top of the others (because you always paint it last). In a similar way, drawRec will be painted on the bottom of the others (since you are painting it always first). Then drawCirc will be painted in the middle. There is no dynamic order (let me put it) of the painting here. It doesn't matter what you drag or not, they are always going to be painted in that sequence.
A solution might be to have them in a list or array of some sort where when you drag one shape, you put it last in the list, and move the others before it. If you combine that with painting all shapes sequentialy from the list, then you will have your desired result.
...if i stack them all on top of each other I still pick up the rectangle first.
That's because of how your mousePressed in the ClickListener class works: it first of all checks if the rectangle is clicked and then the others. That means that if the rectangle overlaps another shape, then the rectangle is always going to be prioritized.
One solution might again be putting all your shapes in a data structure where you can modify their selection order. For example a list or array where let's say that the closer to the top is a shape then the later it will appear in the list. Then, when the user clicks somewhere, you will check the shapes starting from the last one in the list going to first one. If you find something, you immediately break the loop and select what you found. If you don't find anything, then the user clicked the panel somewhere where there are no shapes currently.
I am almost certain there must be more efficient data structures than a list or array for this problem (because you have to iterate in linear time all shapes to find the one clicked), but I am not an expert on collision detection, so, in order to keep things simple, I am going to stick with it. But there is also one other operation we want to do: change the painting and the selection order of the clicked shape. Long story short, we can use a LinkedHashSet for the job, because:
It maintains an order of the shapes.
We can quickly and efficiently change the order by first removing the clicked shape from its current postion (constant time) and then adding it back to the LinkedHashSet (also constant time), essentialy placing it last in the insertion order. So that automatically means that we have to use the last element in the set as the top most one. And that's good, because when painting we can iterate over all shapes in the set in the order in which they are found in it, so the last shape will be painted last (which means on top of all others). The same stands for the selection of a shape when clicking: we iterate over all elements, and select the last one which we found containing the user's click point. If LinkedHashSet had a descending iterator (by insertion order) then we could also optimize a little the searching of shapes at a given click point, but it doesn't (at least for Java 8 in which the following demonstration/example code applies), so I will stick with iterating from the start, checking all shapes every time and keeping the last one found containing the click point.
Finally, I would advise you to use the API class java.awt.Shape for the shapes since this enables you to create arbitrary shapes and get the contains method to check if a point lies inside them, the drawing/filling capability, the bounds, the path iterator, and so on...
Summarizing all the above, with an example code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class NonComponentMain {
public static Shape createRectangle(final double width,
final double height) {
return new Rectangle2D.Double(0, 0, width, height);
}
public static Shape createEllipse(final double width,
final double height) {
return new Ellipse2D.Double(0, 0, width, height);
}
public static Shape createCircle(final double radius) {
return createEllipse(radius + radius, radius + radius);
}
public static class MoveableShapeHolder {
private final Shape shape;
private final Paint paint;
private final Rectangle2D originalBounds;
private double offsetX, offsetY;
public MoveableShapeHolder(final Shape shape,
final Paint paint) {
this.shape = Objects.requireNonNull(shape);
this.paint = paint;
offsetX = offsetY = 0;
originalBounds = shape.getBounds2D();
}
public void paint(final Graphics2D g2d) {
final AffineTransform originalAffineTransform = g2d.getTransform();
final Paint originalPaint = g2d.getPaint();
g2d.translate(offsetX, offsetY);
if (paint != null)
g2d.setPaint(paint);
g2d.fill(shape);
g2d.setPaint(originalPaint);
g2d.setTransform(originalAffineTransform);
}
public void moveTo(final double newBoundsCenterX,
final double newBoundsCenterY) {
offsetX = newBoundsCenterX - originalBounds.getCenterX();
offsetY = newBoundsCenterY - originalBounds.getCenterY();
}
public void moveBy(final double dx,
final double dy) {
offsetX += dx;
offsetY += dy;
}
public boolean contains(final Point2D pt) {
return shape.contains(pt.getX() - offsetX, pt.getY() - offsetY);
}
public Point2D getTopLeft() {
return new Point2D.Double(offsetX + originalBounds.getX(), offsetY + originalBounds.getY());
}
public Point2D getCenter() {
return new Point2D.Double(offsetX + originalBounds.getCenterX(), offsetY + originalBounds.getCenterY()); //Like 'getTopLeft' but with adding half the size.
}
public Point2D getBottomRight() {
return new Point2D.Double(offsetX + originalBounds.getMaxX(), offsetY + originalBounds.getMaxY()); //Like 'getTopLeft' but with adding the size of the bounds.
}
}
public static class DrawPanel extends JPanel {
private class MouseDrag extends MouseAdapter {
private MoveableShapeHolder current;
private Point origin;
private Point2D center;
#Override
public void mousePressed(final MouseEvent e) {
current = null;
center = null;
final Point evtLoc = e.getPoint();
for (final MoveableShapeHolder moveable: moveables)
if (moveable.contains(evtLoc))
current = moveable; //Keep the last moveable found to contain the click point! It's important to be the last one, because the later the moveable appears in the collection, the closer to top its layer.
if (current != null) { //If a shape was clicked...
//Initialize MouseDrag's state:
origin = e.getPoint();
center = current.getCenter();
//Move to topmost layer:
moveables.remove(current); //Remove from its current position.
moveables.add(current); //Move to last (topmost layer).
//Rapaint panel:
repaint();
}
}
#Override
public void mouseDragged(final MouseEvent e) {
if (current != null) { //If we are dragging something (and not empty space), then:
current.moveTo(center.getX() + e.getX() - origin.x, center.getY() + e.getY() - origin.y);
repaint();
}
}
#Override
public void mouseReleased(final MouseEvent e) {
current = null;
origin = null;
center = null;
}
}
private final LinkedHashSet<MoveableShapeHolder> moveables;
public DrawPanel() {
moveables = new LinkedHashSet<>();
final MouseAdapter ma = new MouseDrag();
super.addMouseMotionListener(ma);
super.addMouseListener(ma);
}
/**
* Warning: all operations on the returned value must be made on the EDT.
* #return
*/
public Collection<MoveableShapeHolder> getMoveables() {
return moveables;
}
#Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
moveables.forEach(moveable -> moveable.paint((Graphics2D) g)); //Topmost moveable is painted last.
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet())
return super.getPreferredSize();
final Dimension preferredSize = new Dimension();
moveables.forEach(moveable -> {
final Point2D max = moveable.getBottomRight();
preferredSize.width = Math.max(preferredSize.width, (int) Math.ceil(max.getX()));
preferredSize.height = Math.max(preferredSize.height, (int) Math.ceil(max.getY()));
});
return preferredSize;
}
}
private static void createAndShowGUI() {
final DrawPanel drawPanel = new DrawPanel();
final Collection<MoveableShapeHolder> moveables = drawPanel.getMoveables();
MoveableShapeHolder moveable = new MoveableShapeHolder(createRectangle(100, 50), Color.RED);
moveable.moveTo(100, 75);
moveables.add(moveable);
moveable = new MoveableShapeHolder(createCircle(40), Color.GREEN);
moveable.moveTo(125, 100);
moveables.add(moveable);
moveable = new MoveableShapeHolder(createRectangle(25, 75), Color.BLUE);
moveable.moveTo(125, 75);
moveables.add(moveable);
final JFrame frame = new JFrame("Click to drag");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(NonComponentMain::createAndShowGUI);
}
}
All the above apply in the case you want to work with a single custom component painting all shapes. If instead you want, you can create a custom component for each shape and use a JLayeredPane for positioning them on top of each other. But then you would probably need a custom LayoutManager too (in order to handle the location of each component).
I created the following GUI.
I created a Shape class. I used a java.awt.Point to hold the center point and a java.awt.Polygon to hold the actual shape. I was able to use the Polygon contains method to see if I was mouse clicking inside the polygon.
I created a ShapeModel class to hold a java.util.List of Shape instances. Creating the proper application model is so important when creating a Swing GUI.
I created a JFrame and a drawing JPanel. The drawing JPanel draws the List of Shape instances. Period. The MouseAdapter will take care of recalculating the polygon and repainting the JPanel.
The trick is in the MouseAdapter mousePressed method. I delete the selected Shape instance and add the selected Shape instance back to the List. This moves the selected shape to the top of the Z-order.
Here's the complete runnable code. I made all the classes inner classes so I could post this code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MoveShapes implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MoveShapes());
}
private final DrawingPanel drawingPanel;
private final ShapeModel shapeModel;
public MoveShapes() {
this.shapeModel = new ShapeModel();
this.drawingPanel = new DrawingPanel();
}
#Override
public void run() {
JFrame frame = new JFrame("Move Shapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void repaint() {
drawingPanel.repaint();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(600, 500));
MoveListener listener = new MoveListener();
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Shape shape : shapeModel.getShapes()) {
g2d.setColor(shape.getColor());
g2d.fillPolygon(shape.getShape());
}
}
}
public class MoveListener extends MouseAdapter {
private Point pressedPoint;
private Shape selectedShape;
#Override
public void mousePressed(MouseEvent event) {
this.pressedPoint = event.getPoint();
this.selectedShape = null;
List<Shape> shapes = shapeModel.getShapes();
for (int i = shapes.size() - 1; i >= 0; i--) {
Shape shape = shapes.get(i);
if (shape.getShape().contains(pressedPoint)) {
selectedShape = shape;
break;
}
}
if (selectedShape != null) {
shapes.remove(selectedShape);
shapes.add(selectedShape);
}
}
#Override
public void mouseReleased(MouseEvent event) {
moveShape(event.getPoint());
}
#Override
public void mouseDragged(MouseEvent event) {
moveShape(event.getPoint());
}
private void moveShape(Point point) {
if (selectedShape != null) {
int x = point.x - pressedPoint.x;
int y = point.y - pressedPoint.y;
selectedShape.incrementCenterPoint(x, y);
drawingPanel.repaint();
pressedPoint = point;
}
}
}
public class ShapeModel {
private final List<Shape> shapes;
public ShapeModel() {
this.shapes = new ArrayList<>();
this.shapes.add(new Shape(100, 250, Color.BLUE, ShapeType.TRIANGLE));
this.shapes.add(new Shape(300, 250, Color.RED, ShapeType.RECTANGLE));
this.shapes.add(new Shape(500, 250, Color.BLACK, ShapeType.CIRCLE));
}
public List<Shape> getShapes() {
return shapes;
}
}
public class Shape {
private final Color color;
private Point centerPoint;
private Polygon shape;
private final ShapeType shapeType;
public Shape(int x, int y, Color color, ShapeType shapeType) {
this.centerPoint = new Point(x, y);
this.color = color;
this.shapeType = shapeType;
createPolygon(shapeType);
}
public void incrementCenterPoint(int x, int y) {
centerPoint.x += x;
centerPoint.y += y;
createPolygon(shapeType);
}
private void createPolygon(ShapeType shapeType) {
this.shape = new Polygon();
switch (shapeType) {
case TRIANGLE:
int angle = 30;
int radius = 100;
for (int i = 0; i < 3; i++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
angle += 120;
}
break;
case RECTANGLE:
angle = 45;
radius = 100;
for (int i = 0; i < 4; i++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
angle += 90;
}
break;
case CIRCLE:
radius = 75;
for (angle = 0; angle < 360; angle++) {
Point point = toCartesianCoordinates(angle, radius);
shape.addPoint(point.x, point.y);
}
break;
}
}
private Point toCartesianCoordinates(int angle, int radius) {
double theta = Math.toRadians(angle);
int x = (int) Math.round(Math.cos(theta) * radius) + centerPoint.x;
int y = (int) Math.round(Math.sin(theta) * radius) + centerPoint.y;
return new Point(x, y);
}
public Color getColor() {
return color;
}
public Point getCenterPoint() {
return centerPoint;
}
public Polygon getShape() {
return shape;
}
}
public enum ShapeType {
TRIANGLE, RECTANGLE, CIRCLE
}
}
I cannot figure out how to fix the issue of my shapes not ending up on top of each other if I drag them to the same position
Components on a panel are painted based on the components ZOrder. The component with the lowest ZOrder is painted last.
So in the mousePressed metthod of your MouseListener when you select a component to drag you can change its ZOrder:
Component child = e.getComponent();
child.getParent().setComponentZOrder(child, 0);
Edit:
drawRec.paintComponent(g);
drawCirc.paintComponent(g);
drawTri.paintComponent(g);
I didn't notice that code before.
Never invoke paintComponent() directly.
Swing has a parent/child relationship. You just need to add each of the 3 shape panels to the parent panel. The parent panel will then paint the child components based on the ZOrder I described above. Get rid of those 3 lines of code.
I'm drawing Circles on JFrame using JComponent (AWT/SWING) and I want to make sure that when the user resizes the frame, that certain calculations are made and circles are drawn on screen dynamically (whether if it's bigger, smaller, moved to left or right etc.). I implemented the ComponentAdapter event and componentResized method however I'm struggling with coming up with something that is dynamic. Here's my code:
CircleViewer Class
import javax.swing.JFrame;
import java.awt.event.*;
public class CircleViewer
{
public static void main(String[] args)
{
final JFrame frame = new JFrame("Circle Shapes");
final CirclePanel panel = new CirclePanel();
// Class for Mouse Listener which implements the necessary interfaces
class MousePressListener implements MouseListener, MouseMotionListener
{
public void mouseClicked(MouseEvent event) { }
public void mouseEntered(MouseEvent event) { }
public void mouseExited(MouseEvent event) { }
public void mouseWheelMoved(MouseWheelEvent event) { }
public void mouseMoved(MouseEvent event) { }
public void mousePressed(MouseEvent event) { }
#Override
public void mouseDragged(MouseEvent event)
{
var x = event.getX();
var y = event.getY();
panel.moveTo(x, y);
}
#Override
public void mouseReleased(MouseEvent event)
{
panel.finalMove();
}
}
panel.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent event)
{
panel.frameResizeCalculation(frame.getWidth(), frame.getHeight());
}
});
MousePressListener listener = new MousePressListener();
panel.addMouseListener(listener);
panel.addMouseMotionListener(listener);
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setVisible(true);
}
public static final int FRAME_WIDTH = 700;
public static final int FRAME_HEIGHT = 500;
}
CirclePanel Class
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.util.ArrayList;
import java.awt.Color;
import java.lang.Math;
import java.awt.BasicStroke;
import java.awt.Stroke;
public class CirclePanel extends JComponent
{
private int mouseX;
private int mouseY;
private ArrayList<Circle> circleList;
private final BasicStroke dashLine = new BasicStroke(1,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL,
0, new float[]{6}, 0);
private Circle newCircle;
private final Color newCircleColor = Color.RED;
private final Color finalCircleColor = Color.BLUE;
public CirclePanel()
{
this.circleList = new ArrayList<Circle>();
this.mouseX = 0;
this.mouseY = 0;
}
public void moveTo(int x, int y)
{
mouseX = x;
mouseY = y;
if (newCircle == null)
{
newCircle = new Circle(x,y,0);
}
else
{
int dX = newCircle.get(0) - mouseX;
int dY = newCircle.get(1) - mouseY;
newCircle.set(2, (int)Math.sqrt(dX*dX + dY*dY));
}
repaint();
}
public void finalMove()
{
if (newCircle != null)
{
circleList.add(newCircle);
newCircle = null;
repaint();
}
}
// Do something here and change X-Y coordinates and radius of the circles and finally call repaint() method of Graphics2D
public void frameResizeCalculation(int width, int height)
{
var dX = CircleViewer.FRAME_WIDTH - width;
var dY = CircleViewer.FRAME_HEIGHT - height;
}
public void paintComponent(Graphics g)
{
g.setColor(finalCircleColor);
for (Circle circle : circleList)
{
drawCircle(g, circle);
}
Circle c = newCircle;
if (c != null)
{
g.setColor(newCircleColor);
drawCircle(g, c);
Graphics2D g2 = (Graphics2D)g.create();
g2.setStroke(dashLine);
g2.drawLine(c.get(0), c.get(1), mouseX, mouseY);
g2.dispose();
}
}
public void drawCircle(Graphics g, Circle c)
{
g.drawOval(c.get(0) - c.get(2), c.get(1) - c.get(2), c.get(2) * 2, c.get(2) * 2);
}
}
and lastly, the Circle
public class Circle
{
private int x;
private int y;
private int radius;
public Circle(int x, int y, int radius)
{
this.x = x;
this.y = y;
this.radius = radius;
}
public int get(int option)
{
switch (option)
{
case 0:
return this.x;
case 1:
return this.y;
case 2:
return this.radius;
}
return 0;
}
public void set(int option, int value)
{
switch (option)
{
case 0: //set x
this.x = value;
break;
case 1:
this.y = value;
break;
case 2:
this.radius = value;
break;
}
}
}
that certain calculations are made and circles are drawn on screen dynamically (whether if it's bigger, smaller, moved to left or right etc.).
Well you need to define what YOU want to happen when the frame is resized. We can't tell you what to do.
However, before you worry about that you need to restructure your classes to make it possible to have the dynamic painting.
I see the following issues with the basic code:
Forget about the frame size. That size is irrelevant to the custom painting that will be done in your CirclePanel. That is the size of the CirclePanel is NOT the same as the size of your frame, since the frame size includes the frame borders and title bar. Your logic should be based on the size of the panel, not the frame.
When doing custom painting it is also the responsibility of the component to override the getPreferredSize() method to return the preferred size of the component. Then in your code you add the panel to the frame and then invoke the pack() method on the frame.
You call your class CirclePanel, but you extend JComponent. Why? Give your class a proper name. If you want to extend JComponent then call your class CircleComponent. If you want to call your class CirclePanel, then extend JPanel. But you also need to understand the difference between extending JComponent and JPanel. All Swing components are responsible for clearing the background of the component BEFORE doing any painting. A JPanel does this for you automatically, you just invoke super.paintComponent(...) at the start. A JComponent does NOT clear the background so you must clear it by setting the color of the Graphics object and then invoke fillRect(0, 0, getWidth(), getHeight()) to paint the background.
The Circle object should contain all the information needed to paint itself. You already have the x, y, radius values. I would suggest you also need a Color property so each Circle can be a different color.
The Circle object should then know how to paint itself using its properties. Therefore your Circle class should have a method, lets say draw(Graphics grapics). You then use the properties of the class to draw the oval. So this means the paintComponent() method would invoke the draw(...) method of the Circle class and you would remove the drawOval(...) method you currently have.
A "getter" method does not take parameters. If you feel other classes need to know the x, y, radius properties then create getX(), getY() and getRadiout() methods. I would suggest you don't need the get()/set() methods.
I suggest you first need to implement the above suggestions before making the painting dynamic.
Next, you don't need a ComponentListener added to the panel. Instead you need to add logic to the paintComponent(...) method of your CirclePanel class. The paintComponent() method will be invoked automatically every time the size of the panel changes. The basic logic would be to determine a "multiplier" to be used when painting each Circle.
So you can use the getPreferredSize() method to get the preferred width and you can use the getWidth() method of the panel to get the current size. So your multiplier would be:
double multiplierX = getWidth() / getPreferredSize().x;
Now this information needs to be passed to the Circle objects draw(...) method, so the method signature would become draw(Graphics g, double multiplierX). When you invoke the drawOval(...) method you apply the multiplier to the "x" parameter. This should cause the Circles to shift in the horizontal direction as the frame is resized.
You would then repeat the above step for the multiplierY to have the Circles shift in a vertical direction.
You would then need to decide how you want to affect the radius?
So I've been stuck on this problem for a while now and I'm desperate for help. Please help me. I've got 3 classes:
Circle is just suppose to draw a circle in the frame created by Frame with random starting position (and defind the radius).
Frame is the mainclass with methods such as addCircle(), bounce(), start(), stop(), run() (moves the circles) and quit(). This class also creates the frame in which the circles are added to.
Interfa is just for now a inteface frame where I define the radius, number of circles and Frame size.
No matter what I try I cannot add more than two circle (one is colored and one is not):
The "recursive way":
private static void addCircle(int n){
Circle[] circles = new Circle[n+10];
if (n > 0){
circles[circleAdd] = new Circle();
frame.add(circles[circleAdd]);
circleAdd = circleAdd + 1;
addCircle(n-1);
}
}
Normal itterative way
private static void addCircles(int n){
ArrayList<Circle> circles = new ArrayList<Circle>();
for(int i = 0; i<=n;i++){
circles.add(new Circle());
frame.add(circles.get(i));
}
}
This is how I create my Frame:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public Class Frame{
private static JFrame frame;
private static int circleAdd = 0;
private static JPanel fra;
public static void mainFrame(){
frame = new JFrame();
frame.setSize(500,500);
frame.setVisible(true);
fra = new JPanel();
frame.add(fra);
...
//addCircle and addCircles
...
public static void main..
}
}
This is my circle:
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class Circle extends JPanel{
private Random random = new Random();
public void paint(Graphics g){
int randX = random.nextInt(250)+50;
int randY = random.nextInt(250)+50;
g.drawOval(randX,randY,50,50);
g.setColor(Color.ORANGE);
g.fillOval(100,100,50,50);
}
}
I would suggest that your general approach is wrong. Instead of using a JPanel as the element, you should have a JPanel capable of painting any number of "circles". The Graphics2D API is capable of drawing complex shapes (including ovals).
The main issues I can see are:
JFrame by default is using a BorderLayout, this only allows a single component to be placed in each of the five available positions
Layout managers rely on the preferred/minimum/maximumSize hints to make determinations about the size of the components. They are also responsible for deciding on where the component should be placed. In your current implementation, this would mean that it's possible for you to paint beyond the visible range of the component
Overriding paint is not recommend, and failing to call super.paint could cause a number of unexpected and difficult to diagnose issues
Painting can occur at any time, so using random values in the paint method will cause the UI to constantly change
Instead, you could define your own Circle class which takes the location and size you want and simply acts as a container
public class Circle {
private int x;
private int y;
private int radius;
private Ellipse2D shape;
public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.shape = new Ellipse2D.Double(x, y, radius * 2, radius * 2);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getRadius() {
return radius;
}
public Rectangle getBounds() {
return shape.getBounds();
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.ORANGE);
g2d.fill(shape);
}
}
This is simply a container class, it represents the information need to generate the desired outcome. It has a convince method which is capable of then painting the shape itself.
You would then need to create a List of these shapes and paint them to your component
public class TestPane extends JPanel {
private List<Circle> circles = new ArrayList<>(10);
private Dimension size;
public TestPane() {
Random random = new Random();
int maxX = 0;
int maxY = 0;
for (int index = 0; index < 10; index++) {
int randX = random.nextInt(250) + 50;
int randY = random.nextInt(250) + 50;
circles.add(new Circle(randX, randY, 25));
maxX = Math.max(maxX, randX + 50);
maxY = Math.max(maxY, randY + 50);
}
size = new Dimension(maxX, maxY);
}
#Override
public Dimension getPreferredSize() {
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Circle circle : circles) {
Graphics2D g2d = (Graphics2D) g.create();
circle.paint(g2d);
g2d.dispose();
}
}
}
One of the things you seem to lack understanding in is how painting actually works in Swing.
Start by having a look at Performing Custom Painting and Painting in AWT and Swing for more details.
A deeper understanding of how layout managers and the component hierarchy work also wouldn't hurt
I need to create a colorPicker tool for my little ms paint app.
I originally asked how to switch from my Graphics 2D implementation to a Graphics2D-->BufferedImage one, (and then it would be easy to get the pixels) but I have instead been suggested to get the pixel colors thought the robot class.
First of all, here's my MCEVE: NB. It cannot be a single class, it won't save.
import java.awt.*;
import javax.swing.*;
public class Runner {
public static void main(String[] args){
JFrame Maiframe = new JFrame("Paint");
Canvas DrawingBoard = new Canvas();
Maiframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Maiframe.setSize(700, 500);
Maiframe.add(DrawingBoard);
Maiframe.setVisible(true);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
public class Canvas extends JPanel {
public int xp; //present x
public int yp; //present y
public int xo; //old x
public int yo; //old y (Drawing from starting to new point as mouse drags)
public Canvas(){
super();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println("RGB : Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
addMouseMotionListener(new MouseMotionAdapter() { //get coords as mouse drags
#Override
public void mouseDragged(MouseEvent e) {
xp = e.getX();
yp = e.getY();
if(SwingUtilities.isLeftMouseButton(e))
repaint(); //call paintcomponent
}
public void mouseMoved(MouseEvent e1){ //keep trak of coords when mouse is not dragging
xo = e1.getX();
yo = e1.getY();
}
});
}
public void draw(int x, int y, Graphics g){ //draw the line
if(xo != 0)
g.drawLine(xo, yo, x, y);
xo = x; //old x is now present x and so on
yo = y;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(xp, yp, g);
}
}
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
And it works just fine.
I needed to implement something to get the color from any pixel, when the mouse clicks.
I did this: (adding this method to Canvas, and calling it from the mouse clicker listener)
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
Example of call:
//replace old mouseListener with this
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println(" da tela Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
Unfortunately, from this implementation I get undefined behaviour. The color read is always 255, 255, 255. Exept if I color the hole panel, then it gets it right 9 times out of 10, but in some areas it's still missing it.
I have also tried wrapping the whole thing into a bufferedImage with robot#screenCap but that doesn't even remotely work.
What am I doing wrong here?
Thank you very much.
EDIT1:
There are doubts as to how a line can remain on screen after a second one has been drawn. I'll provide a screenshot:
NBB. This works because an instance of Canvas is create inside of a JFrame into Runnable, so the changes are saved, avoiding the need for shapes and arrayLists.
I'll also add a full version of the code which prints wrong RGB results, remember that this does not save the lines as it stands. Please refer to the two separate classes above for testing.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
public class Canvas extends JPanel{
public int xp; //present x
public int yp; //present y
public int xo; //old x
public int yo; //old y (Drawing from starting to new point as mouse drags)
public Robot robot;
public static void main(String[] args){
JFrame Maiframe = new JFrame("Paint");
Canvas DrawingBoard = new Canvas();
Maiframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Maiframe.setSize(700, 500);
Maiframe.add(DrawingBoard);
Maiframe.setVisible(true);
}
public Canvas(){
super();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println("RGB --> Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
addMouseMotionListener(new MouseMotionAdapter() { //get coords as mouse drags
#Override
public void mouseDragged(MouseEvent e) {
xp = e.getX();
yp = e.getY();
if(SwingUtilities.isLeftMouseButton(e))
repaint(); //call paintcomponent
}
public void mouseMoved(MouseEvent e1){ //keep trak of coords when mouse is not dragging
xo = e1.getX();
yo = e1.getY();
}
});
}
public void draw(int x, int y, Graphics g){ //draw the line
if(xo != 0)
g.drawLine(xo, yo, x, y);
xo = x; //old x is now present x and so on
yo = y;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(xp, yp, g);
}
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
}
my little ms paint app
Well its not much of a paint app. All it does is draw a single line. Whenever you attempt to draw the second line the first will be removed.
So the first step you need to do is decide how you want the painting to work. There are two common approaches:
Store Objects you want to paint in an ArrayList and then the paintComponent(...) method will paint each Object in the List.
Paint directly to a BufferedImage and then the paintComponent(...) method can just paint the BufferedImage.
Check out Custom Painting Approaches for working examples of both of these approaches and give the pros/cons of using each approach.
I have instead been suggested to get the pixel colors thought the robot class
It depends which painting approach you want to use.
If you use the Draw on Component approach then you would use the MouseInfo and Robot to get the pixel color:
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
System.out.println( robot.getPixelColor(p.x, p.y) );
If you use the Draw on Image approach then you would get the pixel color from the BufferedImage:
int rgb = bufferedImage.getRGB(mouseEvent.getX(), mouseEvent.getY());
Color color = new Color( rgb );
System.out.println( color );
Final Update
You still have not posted a SSCCE. The code you posted does NOT draw a line. Even if it did draw a line, how do you expect us to click (with accuracy) a single pixel line.
The point of a SSCCE is to demonstrate the concept you are asking about. You are asking how to get the Color of a pixel on your panel. How the drawing gets on the panel is irrelevant to the question so the painting code should be as simple as possible.
Following is a proper SSCCE. Note:
The createAndShowGUI() and main() methods will be the same for all SSCCE. The difference is the `DrawingPanel code/class will change to demonstrate your problem.
The custom painting is hardcoded. There is no need for mouseMoved/mouseDragged logic (unless that is the problem) you are trying to solve. All you care about is having different colors to click on.
Just use a simple System.out.println(...) to display the value of the Color object.
All I did was copy the relevant pieces of code from you class and remove the irrelevant code to keep the code simple and straight forward. Anybody can do that.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DrawingCanvas extends JPanel
{
private Robot robot;
public DrawingCanvas()
{
addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
try
{
robot = new Robot();
}
catch(Exception re) { System.out.println(re); }
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
System.out.println(pixelColor);
}
});
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor( Color.RED );
g.fillRect(0, 0, 40, 40);
g.setColor( Color.GREEN );
g.fillRect(40, 40, 40, 40);
g.setColor( Color.BLUE );
g.fillRect(80, 80, 40, 40);
}
private static void createAndShowGUI()
{
JPanel panel = new DrawingCanvas();
JFrame frame = new JFrame("DrawingCanvas");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() ); // Java 8 only
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Just copy/paste/compile/execute to see that the code works.
I'll let you figure out why your code doesn't appear to work.
NBB. This works because an instance of Canvas is create inside of a JFrame into Runnable, so the changes are saved, avoiding the need for shapes and arrayLists.
That statement is completely wrong. Painting done with the Graphics Object in the paintComponent() method is only temporary until the next time the paintComponent() method is invoked.
For example add the following code after the frame.setVisible() statement:
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
Graphics g = panel.getGraphics();
g.setColor( Color.YELLOW );
g.fillRect(120, 120, 40, 40);
}
});
The yellow square will disappear when the frame is resized. For permanent painting you need to use one of the two approaches I suggested in my original answer.
Edit 2:
This is the output I get when I click on the red, green, blue, background respectively:
C:\Java>java DrawingCanvas
java.awt.Color[r=255,g=0,b=0]
java.awt.Color[r=0,g=255,b=0]
java.awt.Color[r=0,g=0,b=255]
java.awt.Color[r=238,g=238,b=238]
I've been trying to use mousePressed and mouseReleased but to no avail. The purpose of this program is to obtain an initial coordinate for the center of a circle from the mousepressed and to use the mousereleased to determine the radius of this circle. For some reason, I can't get the ball to repaint such that its center is the same position as the mousePressed(). I know that the first two parameters of the Ellipse2D object determine top-left corner of the ellipse, so if the radius length is subtracted from the x coordinate and the radius length is added to the y coordinate, shouldn't the ball appear at the first mouse click? I'm having difficulty understanding why it won't construct where I want it to.
Edit 1: Reformatted program for readability, made program compilable.
Here is the relevant portion of my program...
Main Class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main{
public static void main(String[] args){
CircleComponent component = new CircleComponent();
JFrame frame = new JFrame("Bouncing Ball");
class mousePressedListener implements MouseListener
{
int x1, y1, x2, y2;
public void mouseClicked(MouseEvent e) { }
#Override
public void mouseEntered(MouseEvent e) { }
#Override
public void mouseExited(MouseEvent e) { }
public void mousePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
System.out.println(x1+ "|x1");
System.out.println(y1+ "|y1");
}
public void mouseReleased(MouseEvent e){
x2 = e.getX();
y2 = e.getY();
System.out.println(x2 + "|x2");
System.out.println(y2 + "|y2");
frame.getHeight();
frame.getWidth();
component.moveBall(frame.getHeight(), frame.getWidth(), x1, y1, x2, y2);
}
}
class timeListener implements ActionListener{
public void actionPerformed(ActionEvent event)
{
frame.getHeight();
frame.getWidth();
component.moveBall(frame.getWidth(), frame.getHeight());
}
}
frame.add(component); //adds the ball to frame
frame.setVisible(true);
frame.setSize(500,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//creates square panel with specific size and the default exit
ActionListener listener = new timeListener();
Timer timer = new Timer(500, listener);
timer.start();
frame.addMouseListener(new mousePressedListener());
}
}
CircleComponent Class
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JComponent;
import java.awt.Color;
public class CircleComponent extends JComponent{
private int x, y, a, b;
int radius = 50;
private Color color = Color.WHITE;
private int dx = 1, dy = 1;//initializes the speed of the ball
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setColor(color);
Ellipse2D ball = new Ellipse2D.Double(x, y, 2*radius, 2*radius);
g2.fill(ball);
}
public void moveBall(int inWidth, int inHeight){
if(x<0 || x>inWidth-65){
dx = -dx;
}
if(y<0 || y>inHeight-150) {
dy = -dy;
}
x = x + dx;
y = y + dy;
repaint();
}
public void moveBall(int inWidth, int inHeight, int x1, int y1, int x2, int y2){
double r = (double) (Math.pow((x1-x2),2) + Math.pow((y1-y2),2));
radius = (int) Math.sqrt(r);
System.out.println(radius+"|radius");
if(x<0 || x>inWidth-65){
dx = -dx;
}
if(y<0 || y>inHeight-150) {
dy = -dy;
}
x = x1-radius;
y = y1+radius;
x = x + dx;
y = y + dy;
System.out.println(x+"X"+y+"Y");
repaint();
}
}
A MouseListener needs to be added to a viable GUI component for its magic to work, and you never seem to add your MouseListener to anything. You need to call .addMouseListener(...) on your CircleComponent object and pass in your created MouseListener.
As an aside, your code as formatted is very difficult to read. Please consider editing your post and fixing your indentation style so that it is uniform and consistent. I usually avoid using tabs for indenting (forum software often doesn't play well with tabs) and indent each code block 4 spaces.
Edit
Other suggestions:
Again, add the MouseListener to the CircleComponent instance, what you name "component".
I'd not make the MouseListener an inner class, but rather make it its own stand alone class.
You don't need a reference to the JFrame in the MouseListener, only the CircleComponent instance, which you can get by passing in a reference into the MouseListener's constructor, or by calling (CircleComponent) e.getSource()
In the MouseListener get the CircleComponent's width and height.
You will need to call the super's method inside of your paintComponent override.
Your calculations are off and you will need to debug these.