I'm using MouseMotion Listeners to add shapes to a HashSet, and then filling them in using Graphics2D. However when I move my mouse too fast the points no longer make a coherent line.
I've tried googling, but haven't found a relevant answer.
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
//points.add(new Point(e.getX(), e.getY()));
shapes.add(new ShapeInfo(circle, new Point(e.getX(), e.getY()), color));
repaint();
}
});
for(ShapeInfo info : shapes) {
Point location = info.point.getLocation();
g2d.translate(location.x, location.y);
g2d.setColor(info.color);
g2d.fill(info.shape);
g2d.translate(-location.x, -location.y);
}
I hope to get a nice, smooth line made of circles, but end up with sacttered circles. https://imgur.com/a/KLOyPcn <- Here is what happends when I drag the mouse too fast while painting.
Your mouse works at a certain frequency (normal mouse works around 100Hz) so it will pick a certain number of points while you move it.
If you cover 1000 px in half second (which is not really fast) it will pick 50 points, they will be spaced every 20 pixel.
If your circle has a radius of less than that you will see a dotted line.
Even using very a fast mouse could not lead you to have a continuous line.
You could draw a line between points instead of drawing a circle if you want, or interpolate coordinate between last circle and current one and create other circles in between the 2.
Here try this.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
public class FillOvals extends JPanel {
private JFrame frame = new JFrame();
private List<Point> points = new ArrayList<>();
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new FillOvals().start());
}
public FillOvals() {
setPreferredSize(new Dimension(500, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null); // center on screen
frame.setVisible(true);
}
public void start() {
MyMouseListener ml = new MyMouseListener();
addMouseMotionListener(ml);
addMouseListener(ml);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (points.size() < 1) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
// keep the line smooth on the edges
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLUE);
Point p = points.get(0);
int lastx = p.x;
int lasty = p.y;
g2d.setStroke(new BasicStroke(10.f));
for (int i = 1; i < points.size(); i++) {
p = points.get(i);
g2d.drawLine(lastx, lasty, p.x, p.y);
lastx = p.x;
lasty = p.y;
}
g2d.dispose();
}
private class MyMouseListener extends MouseAdapter {
public void mouseDragged(MouseEvent e) {
points.add(e.getPoint());
repaint();
}
public void mousePressed(MouseEvent e) {
points.add(e.getPoint());
repaint();
}
}
}
The JVM/mouse can't keep up with drawing circles that fast. The example I provided draws a line between two points on a continuous basis.
Related
I am trying to make a simple game which displays circles on a frame and when clicked the circle should disappear. I am learning how Java Swing works and managed to draw a circle (Wow such an achievement) and figured out how events work. I added an mouseListener to the circle and when clicked for now I want a to get a console log that it has been clicked but the end result is not as expected. No matter where I click I always get the "click" console log. When I try to add a listener to a JButton for example I get the end result. Are events different for graphics?
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.event.*;
import java.sql.SQLOutput;
public class CirclePop {
JFrame frame;
Circle circle;
public static void main(String[] args) {
CirclePop circlePop = new CirclePop();
circlePop.drawFrame();
}
public void drawFrame() {
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
}
class Click implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("Pressed");
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
}
import java.awt.*;
import javax.swing.*;
class Circle extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillOval(150, 140, 30, 30);
}
}
First of all, you may want to extend MouseAdapter instead of implementing MouseListener. This way you don't have "implement" all these empty methods.
Then, in your mousePressed method you just have to calculate if the click happened inside the circle. This is basically just Pythagoras:
static class ClickListener extends MouseAdapter {
private final Circle circle;
public ClickListener(Circle circle) {
this.circle = circle;
}
#Override
public void mousePressed(MouseEvent e) {
int centerX = circle.getCenterX();
int centerY = circle.getCenterY();
int radius = circle.getRadius();
int clickX = e.getX();
int clickY = e.getY();
// inside circle: (clickX - centerX)^2 + (clickY - centerY)^2 < radius^2
double xSquare = Math.pow(clickX - centerX, 2);
double ySquare = Math.pow(clickY - centerY, 2);
if (xSquare + ySquare < radius * radius) {
System.out.println("pressed");
}
}
}
I've added some fields to Circle class to get access to the properties you need for the calculation:
class Circle extends JPanel {
private final int radius = 30;
private final int centerX = 150;
private final int centerY = 140;
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillOval(centerX, centerY, radius, radius);
}
// getter, etc.
}
You have to implement the MouseListener interface indeed, and after a mouse click, you have to check whether the mouse position is contained in the region of your circle. You could do this manually, by comparing coordinates, but this could be a bit too much work. I think it's easier to rather create a Shape object(Infact this is a good time to learn about it since you're just starting out) that you fill with the respective color, and then just check whether the circle contains the mouse position.
Also, check out the Shape class docs when you've got some spare time.
I've gone ahead and made changes to your code, it now uses an instance of Shape class to create a circle.
Also, instead of implementing the MouseListener interface, I recommend extending MouseAdapter since you're not actually providing any meaningful implementation to any method of the interface except the mousePressed() method.
Lastly, notice the shape.contains(event.getPoint()) in the mousePressed() method, that is what does the trick for checking the coordinates.
The rest of the code should be familiar.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class CirclePop {
JFrame frame;
Circle circle;
public static void main(String[] args) {
CirclePop circlePop = new CirclePop();
circlePop.drawFrame();
}
public void drawFrame() {
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
}
class Click extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
if (circle.shape.contains(e.getPoint())) {
System.out.println("Pressed");
}
}
}
}
class Circle extends JPanel {
Shape shape;
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
shape = new Ellipse2D.Double(150, 140, 30, 30);
g2.setColor(Color.red);
g2.fill(shape);
}
}
Okay, so, this isn't going to be short
Let's start with ....
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
Okay, seems simple enough, but, one thing you've missed is the fact that JFrame, by default, uses a BorderLayout - this means, it will make the child component (and the centre/default position) fill all the available space of the frames viewable space
You can see this if you do something like...
frame = new JFrame();
circle = new Circle();
circle.setBackground(Color.RED);
You will now see that the Circle component occupies the entire frame, so when you click on it, you're clicking the Circle component itself.
This isn't bad, but, you might want to change tact a little. Instead of adding the MouseListener independently of the Circle, have the Circle component make use of its own MouseListener, for example...
class Circle extends JPanel {
public Circle() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
// More to come...
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillOval(150, 140, 30, 30);
}
}
This means you get to control much of the logic internally to the class, makes it easier to access some of the more critical information without needing to make a bunch of, potentially, dangerous casts.
So, now we just need to add the logic in to determine if the mouse was clicked within the desirable location or not...
public void mouseClicked(MouseEvent e) {
Point point = e.getPoint();
if (point.x >= 150 && point.x <= 150 + 30 && point.y >= 140 && point.y <= 140 + 30) {
System.out.println("You clicked me :(");
}
}
Okay, that's ... basic
We can simplify it a little and make use of the available functionality within the wider API by making use of the "shapes" API, for example...
class Circle extends JPanel {
private Ellipse2D dot = new Ellipse2D.Double(150, 140, 30, 30);
public Circle() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Point point = e.getPoint();
if (dot.contains(point)) {
System.out.println("You clicked me :(");
}
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.red);
g2d.fill(dot);
g2d.dispose();
}
}
The benefit of this, apart from contains, is we can change the position of the shape relatively easily and our if statement contains to work 🎉
I do, highly, recommend also having a look at
Performing Custom Painting
Painting in AWT and Swing
2D Graphics Trail
Working with Geometry
I want to create a simple drawing programm in java which currently only draws a line using Graphics.fillOval() and a mouseMotionListener(). The problem is, that if you move the mouse quickly the line gets less precise and the ovals (circles in this case) spread apart.
Here is the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Drawing
{
private JFrame window;
private Graphics g;
public Drawing()
{
window=new JFrame();
window.setTitle("Paint_window");
window.setSize(1000,700);
window.setVisible(true);
window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
g=window.getGraphics();
window.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent e)
{
if(SwingUtilities.isLeftMouseButton(e)
{
g.fillOval((int)e.getX(),(int)e.getY(),10,10);
}
}
});
}
}
Is there a way of improving this or a better way to this?
g=window.getGraphics();
First of all you should not be using getGraphics() of a component. Any painting you do will only be temporary and will be erased the first time Swing determines the component needs to be repainted. In you above example just try resizing the frame to see this.
The proper way to do custom painting is to override the paintComponent(...) method of a JPanel and add the panel to the frame. See Custom Painting for more information.
The problem is, that if you move the mouse quickly the line gets less precise and the ovals (circles in this case) spread apart
You will not be able to have an event generated for every pixel the mouse moves.
Instead you need to be able to "draw a line" between consecutive points generated as you drag the mouse.
So you need to store each point in an ArrayList and in the custom painting code iterate through all the points and draw a line.
A basic example to get you started:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
class DrawingPanel extends JPanel
{
private ArrayList<ArrayList<Point>> previous = new ArrayList<ArrayList<Point>>();
private ArrayList<Point> current = new ArrayList<Point>();
private BasicStroke basicStroke;
public DrawingPanel(int strokeSize)
{
basicStroke = new BasicStroke(strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
MouseAdapter ma = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
current.add( new Point(e.getX(), e.getY()) );
}
#Override
public void mouseDragged(MouseEvent e)
{
current.add( new Point(e.getX(), e.getY()) );
repaint();
}
#Override
public void mouseReleased(MouseEvent e)
{
if (current.size() > 1)
{
previous.add( current );
}
current = new ArrayList<Point>();
}
};
addMouseMotionListener( ma );
addMouseListener( ma );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke( basicStroke );
// Paint lines from previous drags
for (int i = 0; i < previous.size(); i++)
{
drawLines(g, previous.get(i));
}
// Paint line from current drag
drawLines(g, current);
}
private void drawLines(Graphics g, ArrayList<Point> points)
{
for (int i = 0; i < points.size() - 1; i++)
{
int x = (int) points.get(i).getX();
int y = (int) points.get(i).getY();
int x2 = (int) points.get(i + 1).getX();
int y2 = (int) points.get(i + 1).getY();
g.drawLine(x, y, x2, y2);
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingPanel(15));
frame.setSize(400, 400);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Using the above approach you will redraw the lines every time the component is repainted.
Another approach is to draw to a BufferedImage and then paint the BufferedImage on the panel. You can check out Custom Painting Approaches for an example of this approach.
I am making a game where a user has to draw lines so as to make a ball bounce into a target. I'm having trouble getting both the ball and the line to show up concurrently, and I can get only one or the other to appear. It seems to me that the panels block each other out, even though I made them transparent. I would like for them both to appear on the same frame. As of this post, the line panel covers the ball panel.
import javax.swing.Timer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class Game
{
public static void main(String args[]) throws Exception
{
JFrame f = new JFrame("Let's Play");
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setSize(1280, 720);
f.setLocation(300, 300);
f.setResizable(false);
//this part draws a ball that bounces around the screen
BallPanel ballPanel = new BallPanel()
{
// draw rectangles and arcs
public void paintComponent(Graphics g)
{
super.paintComponent(g); // call superclass's paintComponent
g.setColor(Color.red);
// check for boundaries
if (x < radius) dx = Math.abs(dx);
if (x > getWidth() - radius) dx = -Math.abs(dx);
if (y < radius) dy = Math.abs(dy);
if (y > getHeight() - radius) dy = -Math.abs(dy);
// adjust ball position
x += dx;
y += dy;
g.fillOval(x - radius, y - radius, radius*2, radius*2);
}
};
ballPanel.setOpaque(false);
f.add(ballPanel);
//this part allows you to draw lines on the frame with your mouse
JPanel lineP = new JPanel()
{
Point pointStart = null;
Point pointEnd = null;
{
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent me)
{
pointStart = me.getPoint();
}
public void mouseReleased(MouseEvent me)
{
pointStart = null;
}
});
addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseMoved(MouseEvent me)
{
pointEnd = me.getPoint();
}
public void mouseDragged(MouseEvent me)
{
pointEnd = me.getPoint();
repaint();
}
});
}
public void paint(Graphics dline)
{
super.paint(dline);
if (pointStart != null)
{
dline.setColor(Color.RED);
dline.drawLine(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y);
}
}
};
lineP.setOpaque(false); //attempted to enable to see ball panel here
f.add(lineP);
f.setVisible(true);
}
}
class BallPanel extends JPanel implements ActionListener
{
private int delay = 10;
protected Timer timer;
public int x = 30; // x position
public int y = 30; // y position
public int radius = 15; // ball radius
public int dx = 10; // increment amount (x coord)
public int dy = 10; // increment amount (y coord)
public BallPanel()
{
timer = new Timer(delay, this);
timer.start(); // start the timer
}
public void actionPerformed(ActionEvent e)
// will run when the timer fires
{
repaint();
}
}
You've got several issues, but the main one is that you're over-using GUI components. You should have just one single component JPanel that does the drawing, a DrawingPanel, and not a ball panel and a line panel. Rather Ball and Line should be logical classes, not GUI classes, and their display should be in the same single DrawingPanel.
Other issues include:
A main method that has way too much code. Most of that code should be off-loaded into the OOP world where it belongs.
GUI component classes that also implement listener interfaces. This is giving the class too much responsibility making debugging and upgrading difficult. Separate these concerns.
One of your classes overrides the paint method, and this should be avoided. Override paintComponent.
The other class that overrides paintComponent has program logic within paintComponent, and this should be avoided since you have limited control over when or if this method gets called. Get the logic out of that class and into either the mouse listener code or the game loop code (Swing Timer).
I have the following two pieces of code which currently allow me to highlight the circles around the various points of a polygon. THe problem is after my mouse leaves the circle they remain filled. Is there something simple im missing here? Thanks!
public void mouseMoved(MouseEvent e)
{
Graphics2D g2d = (Graphics2D) getGraphics();
if (mode == MODIFY)
// in modify mode only
{
x1 = e.getX();
y1 = e.getY();
shapeList.get(selindex).fillPoint(x1,y1,g2d);
x2 = e.getX();
y2 = e.getY();
if (x1 == x2 && y1 == y2)
{}
else
repaint();
}
}
public void fillPoint(int x, int y, Graphics2D g)
{
for (int t =0; t < npoints;t++)
{
if (thePoints.get(t).contains(x,y))
g.fill(thePoints.get(t));
}
}
public void draw(Graphics2D g)
{
// Implement this method to draw the MyPoly onto the Graphics2D argument g.
// See MyRectangle2D.java for a simple example of doing this. In the case of
// this MyPoly class the method is more complex, since you must handle the
// special cases of 1 point (draw only the point circle), 2 points (draw the
// line) and the case where the MyPoly is selected. You must also use the
// color of the MyPoly in this method.
/*if(highlighted) // this method fills all the circles when selected - a backup piece of code if I couldnt get the proper implimentation to work
{
for (int t =0; t < thePoints.size(); t++)
{
g.fill(thePoints.get(t));
}
}*/
if (thePoints.size() <=2)
{
g.draw(this);
for (int i =0; i <thePoints.size();i++ )
{
g.draw(thePoints.get(i));
}
}
g.setColor(myColor);
if (highlighted)
{
g.draw(this);
for (int i =0; i <thePoints.size();i++ )
{
g.draw(thePoints.get(i));
}
}
else if (!highlighted)
{
if (thePoints.size()>2)
g.fill(this);
else
g.draw(this);
g.fill(this);
}
}
public void paintComponent (Graphics g) // Method to paint contents of panel
{
super.paintComponent(g); // super call needed here
Graphics2D g2d = (Graphics2D) g;
for (int i = 0; i < shapeList.size(); i++)
{
shapeList.get(i).draw(g2d); // IMPLEMENT: draw(). This method will utilize
// the predefined Graphics2D methods draw() (for the outline only,
// when the object is first being drawn or it is selected by the user)
// and fill() (for the filled in shape) for the "basic" Polygon
// but will require additional code to draw the enhancements added
// in MyPoly (ex: the circles indicating the points in the polygon
// and the color). Also special cases for MyPoly objects with only
// 1 or 2 points must be handled as well. For some help with this see
// handout MyRectangle2D
}
}
Suggestions:
Get the Graphics out of the MouseMotionListener.
Instead all that you want to do within the MouseMotionListener will be to:
Un-highlight all the points
Then mark as highlighted (not sure based on your code how you'll do that) any selected point, or point that contains the mouse Point.
Then call repaint(). -- ALWAYS call repaint within the mouselistener.
I would recommend that you have several lists present, including thePoints which can hold your ellipses, as well as lines to hold your lines. Also you'll need a Shape variable to hold the highlighted oval, say called highlightedOval:
private List<Shape> thePoints = new ArrayList<>();
private List<Shape> lines = new ArrayList<>();
private Shape highlightedOval = null;
Then in the MouseMotionListener, you'd keep things simple, and "un-select" the highlighted oval first of all, then in a for loop select it if the oval contains the MouseEvent's Point. Then call repaint():
#Override
public void mouseMoved(MouseEvent e) {
highlightedOval = null;
for (Shape oval : thePoints) {
if (oval.contains(e.getPoint())) {
highlightedOval = oval;
}
}
repaint();
}
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class HighlightPolygon extends JPanel {
private static final Color LINE_COLOR = Color.green;
private static final double OVAL_RAD = 12;
private static final Color HIGHLIGHTED_OVAL_COLOR = Color.RED;
private static final Color OVAL_COLOR = Color.PINK;
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private List<Shape> thePoints = new ArrayList<>();
private List<Shape> lines = new ArrayList<>();
private Shape highlightedOval = null;
public HighlightPolygon(List<Point> points) {
double w = 2 * OVAL_RAD;
double h = w;
for (int i = 0; i < points.size(); i++) {
int x1 = points.get(i).x;
int y1 = points.get(i).y;
double x = x1 - OVAL_RAD;
double y = y1 - OVAL_RAD;
thePoints.add(new Ellipse2D.Double(x, y, w, h));
int i2 = i + 1;
i2 %= points.size();
int x2 = points.get(i2).x;
int y2 = points.get(i2).y;
lines.add(new Line2D.Double(x1, y1, x2, y2));
}
MyMouse myMouse = new MyMouse();
addMouseMotionListener(myMouse);
// addMouseListener(myMouse);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to give smooth graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw all the ovals (if we want them under the lines
for (Shape oval : thePoints) {
// if our oval is the selected one, fill it with the highlighted color,
// otherwise the regular
Color c = oval == highlightedOval ? HIGHLIGHTED_OVAL_COLOR : OVAL_COLOR;
g2.setColor(c);
g2.fill(oval);
}
g2.setColor(LINE_COLOR);
for (Shape line : lines) {
g2.draw(line);
}
}
private class MyMouse extends MouseAdapter {
#Override
public void mouseMoved(MouseEvent e) {
highlightedOval = null;
for (Shape oval : thePoints) {
if (oval.contains(e.getPoint())) {
highlightedOval = oval;
}
}
repaint();
}
}
private static void createAndShowGui() {
List<Point> points = new ArrayList<>();
points.add(new Point(100, 100));
points.add(new Point(300, 200));
points.add(new Point(500, 100));
points.add(new Point(400, 300));
points.add(new Point(500, 500));
points.add(new Point(300, 400));
points.add(new Point(100, 500));
points.add(new Point(200, 300));
JFrame frame = new JFrame("HighlightPolygon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new HighlightPolygon(points));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I am trying to draw a line from a series of mouse clicks.
For example, when i click from one point on the screen to the next, it connects up the lines, and then i can keep clicking to next points where it continues to draw a continuous drawing line.
An example is here: http://oneslime.net/java/Tutorial_2 under Exercise 2
I believe there is some error in my logic (does not draw lines, just a point), but I just can't seem to find it!
Can anybody help me out?
Here is my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class RoadCreator extends JPanel {
private MouseHandler mouseHandler = new MouseHandler();
private Point previousPoint = new Point();
private Point nextPoint = new Point();
private boolean drawing;
public RoadCreator() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(previousPoint.x, previousPoint.y, nextPoint.x, nextPoint.y);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
drawing = true;
nextPoint = e.getPoint();
repaint();
previousPoint = e.getPoint();
}
}
public void display() {
JFrame f = new JFrame("Road Creator");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
}
}
Any help would be GREATLY appreciated, thank you!
Use a GeneralPath or Path2D instead.
The GeneralPath class represents a geometric path constructed from straight lines, and quadratic and cubic (Bézier) curves. It can contain multiple subpaths.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.GeneralPath;
public class RoadCreator extends JPanel {
private MouseHandler mouseHandler = new MouseHandler();
GeneralPath path = null;
private boolean drawing = false;
public RoadCreator() {
this.setPreferredSize(new Dimension(320, 200));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
if (path!=null) {
g2d.draw(path);
}
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
if (!drawing) {
path = new GeneralPath();
path.moveTo(p.x, p.y);
drawing = true;
} else {
path.lineTo(p.x, p.y);
}
repaint();
}
}
public void display() {
JFrame f = new JFrame("Road Creator");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
RoadCreator rc = new RoadCreator();
rc.display();
}
}
This won't work.
public void mousePressed(MouseEvent e) {
drawing = true;
nextPoint = e.getPoint();
repaint();
previousPoint = e.getPoint();
}
You're assuming that repaint is a inline call (ie it paints before returning). It doesn't, repaint will queue a request to the repaint manager that will update at some time in the future.
public void mousePressed(MouseEvent e) {
drawing = true;
previousPoint = nextPoint
nextPoint = e.getPoint();
repaint();
}
Should work, just be aware, previousPoint will be null until the user clicks a second time.
I have found a way to accomplish this task with as little effort as possible. However this does not equate to a "good" way of creating the application. You would need to account for future scalability such as the ability to draw other objects...etc. So let's just get down to how to make it work.
1) Let's omit the paintComponent method...I believe this should be paint().
//#Override
/*protected void paintComponent(Graphics g) {
//Do Something...
}*/
2) Let's add this method:
public void drawLineHelper(Point prev, Point next){
Graphics g = getGraphics();
g.setColor(Color.blue);
g.drawLine(previousPoint.x, previousPoint.y, nextPoint.x, nextPoint.y);
}
3) Lastly, we need to make a few changes to the mouseHandler class:
private class MouseHandler extends MouseAdapter {
//twoPoints make sure we have two points.
boolean twoPoints=false;
#Override
public void mousePressed(MouseEvent e) {
if(twoPoints==false){
nextPoint = e.getPoint();
twoPoints = true;
}
else{
//Set previous to next from now on.
previousPoint = nextPoint;
//Get a new next point.
nextPoint = e.getPoint();
//Helper method will draw the line each time.
drawLineHelper(previousPoint,nextPoint);
//repaint() no longer necessary.
}
}
}
Here is a quick reference to another example with the same problem. The last post on the page explains this same method: http://www.ozzu.com/programming-forum/java-repainting-problem-t49362.html