I am trying to complete the resizing of a shape task I almost got the result I wanted, only dragging after the mouse changes to a resize cursor first draws another shape of the same type and resizes this Second drawn shape. Resulting in such a picture :
This is my related code:
#Override
public void mouseMoved(MouseEvent e) {
if (e.isControlDown()) {
updateShapeUnderMouse(e.getX(), e.getY());
} // deals with drawing shape if control button held
//deals with identifying shape to resize
int x = e.getX();
int y = e.getY();
for (int i = myShapes.size() - 1; i >= 0; i--) {
Shape s = (Shape) myShapes.get(i);
if (s.isedgePoint(x, y)) {
ShapetoResize = s;
setCursor(crnw);
prevDragX = x;
prevDragY = y;
return;
}
}
#Override
public void mouseDragged(MouseEvent event) {
if (event.isControlDown()) {
if (shapeUnderMouse != null) {
shapeUnderMouse.setXPos(event.getX());
shapeUnderMouse.setYPos(event.getY());
repaint();
}
} // deals with moving the shape
//deals with identifying and resizing shape
int x = event.getX();
int y = event.getY();
if (ShapetoResize != null) {
if (ShapetoResize instanceof Square) {
ShapetoResize.resizeSE(x - prevDragX, y - prevDragY);
} else if (ShapetoResize instanceof Rectangle) { // SAME CODE FOR EACH SHAPE
}
repaint();
}
}
Any idea what might be happening?
public ArrayList<Shape> myShapes = new ArrayList();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int length = myShapes.size();
for (int i = 0; i < length; i++) {
myShapes.get(i).paint(g);
}
}
Without seeing this portion of the code, it's just a guess, but I'm thinking you're creating a new shape in your ShapetoResize.resizeSE(int x, int y); method, thus creating two shapes.
I'm assuming it's your own method, because there's no resizeSE available on the Rectangle or the interface Shape (the two built in shapes). Of course there's no paint method on the interface Shape, so you're probably using a custom interface as well (which would be confusing since you didn't provide code for this Shape interface). If you post the code for these methods we can confirm.
Here's an example of putting your code together in a working manner (and a SSCCE). I limited this only to Rectangles (since they're the only built in Shape) and stripped out a lot of stuff not related to resizing. If you're still having trouble, try recreating the problem using this example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.ArrayList;
public class ShapeResizer extends Box{
Dimension preferredSize = new Dimension(400,300);
public ArrayList<Shape> myShapes = new ArrayList();
//Shape that's targeted for editing
Shape currentShape;
public ShapeResizer(){
super(BoxLayout.X_AXIS);
//Shapes (because I don't want to make write code for the user to make shapes)
myShapes.add(new Rectangle(100, 100, 20, 20));
myShapes.add(new Rectangle(200, 200, 30, 30));
addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseMoved(MouseEvent e) {
//deals with identifying shape to resize
int x = e.getX();
int y = e.getY();
boolean foundShape = false;
for (int i = myShapes.size() - 1; i >= 0; i--) {
Shape s = (Shape) myShapes.get(i);
if (s.contains(e.getPoint())) {
//We found a shape to target
currentShape = s;
foundShape = true;
}
}
if(!foundShape){
//Reset the shape and cursor only if needed
if(currentShape != null){
currentShape = null;
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
} else {
setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
}
}
#Override
public void mouseDragged(MouseEvent event) {
if (currentShape != null) {
resizeShape(currentShape, event.getPoint());
}
repaint();
}
});
}
public void resizeShape(Shape s, Point p){
if(s instanceof Rectangle){
Rectangle r = (Rectangle)s;
r.setSize(p.x - r.x, p.y - r.y);
}
}
public void drawShape(Graphics g, Shape s){
if(s instanceof Rectangle){
Rectangle r = (Rectangle)s;
g.drawRect(r.x, r.y, r.width, r.height);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int length = myShapes.size();
for (int i = 0; i < length; i++) {
drawShape(g, myShapes.get(i));
}
}
public Dimension getPreferredSize(){
return preferredSize;
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new ShapeResizer());
frame.validate();
frame.pack();
frame.setVisible(true);
}
}
What is the logic behind adding a shape? Is a shape added by left clicking the drawing canvas? If so, your problem is probably that a shape is being added on click and that's the one that's being resized. You can debug this by breaking execution after you drag an object. Your list would contain two shapes instead of one.
Related
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 was working on a simple "Bouncing Ball"-Animation in Java. The idea is that it initally spawns a single ball moving in a straight line until hitting the panel border, which causes it to bounce off as you would expect. You can then spawn additional balls at position x,y with mouseclicks. So far so good.
My problem is that each ball starts its own thread, and each thread individually draws into the panel at their own intervals, causing the panel to flicker like crazy. I know that such problems can be solved by implementing double buffering, which I've read about, but never quite used myself.
I was wondering about how one would go about using double buffering here and if having many threads painting at the same time can be an issue (or conversely, even the norm)?
Thanks a lot in advance!
Here's the code:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
class MyCanvas extends JPanel
{
MyCanvas()
{
setBackground(Color.white);
setForeground(Color.black);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
}
public Dimension getMinimumSize()
{
return new Dimension(300,300);
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
}
public class BouncingBalls extends JFrame // main class
{
MyCanvas m_gamefield;
public BouncingBalls()
{
setLayout(new BorderLayout());
m_gamefield = new MyCanvas();
add("Center",m_gamefield);
m_gamefield.addMouseListener(new MeinMausAdapter());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void letsgo()
{
Ball first = new Ball(m_gamefield,200,50);
first.start();
}
class MeinMausAdapter extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Ball next = new Ball(m_gamefield,e.getX(),e.getY());
next.start();
}
}
public static void main(String[] args)
{
BouncingBalls test = new BouncingBalls();
test.setVisible(true);
test.pack();
test.letsgo();
}
}
class Ball extends Thread
{
JPanel m_display;
int m_xPos,m_yPos;
int m_dx = 2; // Steps into direction x or y
int m_dy = 2;
Ball(JPanel c,int x,int y)
{
m_display = c;
m_xPos = x;
m_yPos = y;
}
public void run()
{
paintBall(); // Paint at starting position
while(isInterrupted() == false)
{
moveBall();
try
{
sleep(20);
}
catch(InterruptedException e)
{
return;
}
}
}
void paintBall()
{
Graphics g = m_display.getGraphics();
g.fillOval(m_xPos, m_yPos, 20, 20);
g.dispose();
}
void moveBall()
{
int xNew, yNew;
Dimension m;
Graphics g;
g = m_display.getGraphics();
m = m_display.getSize();
xNew = m_xPos + m_dx;
yNew = m_yPos + m_dy;
// Collision detection with borders, "bouncing off":
if(xNew < 0)
{
xNew = 0;
m_dx = -m_dx;
}
if(xNew + 20 >= m.width)
{
xNew = m.width - 20;
m_dx = -m_dx;
}
if(yNew < 0)
{
yNew = 0;
m_dy = -m_dy;
}
if(yNew + 20 >= m.height)
{
yNew = m.height - 20;
m_dy = -m_dy;
}
g.setColor(m_display.getBackground()); // Erases last position by
g.fillRect(m_xPos-2, m_yPos-2, m_xPos+22, m_yPos+22); // painting over it in white
m_xPos = xNew;
m_yPos = yNew;
paintBall(); // paint new position of Ball
g.dispose();
}
}
Don't worry about double buffering when painting with Swing JComponents. They're double buffered by default.
You should, instead of creating each Ball on a different Thread, implement a Swing Timer for the animation. See more at How to Use Swing timers. You can see a good example here where Ball objects are added to a List of Balls and presents at different intervals.
Other Notes
Never use getGraphics of your components. All painting should be done within the Graphics context passed to the paintComponent method. I see you have the method in place. Use it. You can have a draw method in your Ball class that take a Graphics argument, and call that method from within the paintComponent method, passing to it the Graphics context. Example can also be seen in the link above.
You can see more examples here and here and here and here and here and here.
Thanks to peeskillet's excellent references, I've changed the code around a bit by using Swing timers. It's a lot shorter now and forfeits the use of multithreading completely. Also, due to calculating all of the ball positions before actually drawing them (in a single sweeping repaint() as opposed to many smaller ones), the flickering has stopped.
I'm still a bit curious why it is considered bad form to use getGraphics(), though. Does it always lead to flickering (which I had imagined could be removed with an additional layer of of double buffering)? And doesn't paintComponent() become rather bloated in more complex animations if it directs every single act of painting? I'm still fairly new to this, if anybody is wondering.
Here's the new code for those interested:
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class BouncingBalls extends JFrame // main class
{
MyCanvas m_gamefield;
public ArrayList<Ball> balls;
public Timer timer = null;
public BouncingBalls()
{
setLayout(new BorderLayout());
m_gamefield = new MyCanvas();
add("Center",m_gamefield);
balls = new ArrayList<Ball>();
timer = new Timer(30, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
for (Ball b : balls)
{
b.move();
}
repaint();
}
});
m_gamefield.addMouseListener(new MeinMausAdapter());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
class MeinMausAdapter extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
balls.add(new Ball(m_gamefield,e.getX(),e.getY()));
}
}
class MyCanvas extends JPanel
{
MyCanvas()
{
setBackground(Color.white);
setForeground(Color.black);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball b : balls)
{
b.draw(g);
}
}
public Dimension getMinimumSize()
{
return new Dimension(300,300);
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
}
public void letsgo()
{
balls.add(new Ball(m_gamefield,200,50));
timer.start();
}
public static void main(String[] args)
{
BouncingBalls test = new BouncingBalls();
test.setVisible(true);
test.pack();
test.letsgo();
}
}
class Ball
{
JPanel m_display;
int m_xPos,m_yPos;
int m_dx = 2; // Steps into direction x or y
int m_dy = 2;
Ball(JPanel c,int x,int y)
{
m_display = c;
m_xPos = x;
m_yPos = y;
}
void draw(Graphics g)
{
g.fillOval(m_xPos, m_yPos, 20, 20);
}
void move()
{
int xNeu, yNeu;
Dimension m;
m = m_display.getSize();
xNeu = m_xPos + m_dx;
yNeu = m_yPos + m_dy;
// Collision detection with borders, "bouncing off":
if(xNeu < 0)
{
xNeu = 0;
m_dx = -m_dx;
}
if(xNeu + 20 >= m.width)
{
xNeu = m.width - 20;
m_dx = -m_dx;
}
if(yNeu < 0)
{
yNeu = 0;
m_dy = -m_dy;
}
if(yNeu + 20 >= m.height)
{
yNeu = m.height - 20;
m_dy = -m_dy;
}
m_xPos = xNeu;
m_yPos = yNeu;
}
}
I am working on a version of the Squares game. For it I need to detect when my Ellipses are being clicked. But the problem is my method is using one Ellipse object. How can I detect which Ellipse is being clicked? Here is my code.
Main Squares class
public static boolean running = false;
public Squares() {
this.setSize(600, 600);
this.setTitle("Squares");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setContentPane(new SquarePane());
this.setLocationRelativeTo(null);
this.setVisible(true);
}
public static void main(String[] args) {
try {
new Squares();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Crashed");
System.exit(-1);
}
running = true;
}
}
SquaresPanel Class
public static int x = 100;
public static int y = 100;
public static Color randomColor;
public static float r;
public static float g;
public static float b;
public void paintComponent(Graphics gra) {
Graphics2D g2d = (Graphics2D) gra;
gra.setColor(Color.black);
gra.fillRect(0, 0, 600, 600);
Random rand = new Random();
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
Ellipse2D oval = new Ellipse2D.Double(x, y, 10, 10);
r = rand.nextFloat();
g = rand.nextFloat();
b = rand.nextFloat();
randomColor = new Color(r, g, b);
g2d.setColor(randomColor);
g2d.fill(oval);
x += 50;
}
x = 100;
y += 50;
}
}
Thanks guys!
Will
Without looking too much at your code (as I see it's lacking alot) I will just explain how your requirement can be achieve.
1st : You need a MouseListener and implement the mousePressed. From the MouseEvent object, you can obtain the point clicked. See How to Write MouseListener if you are unsure.
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
}
2nd: Keep a List of your ellipses
List<Ellipse2D> ellipses;
3rd: Keep a selectedEllipse variable to hold the select one.
Ellipse2D selectedEllipse;
4th: After clicking the point, you loop through the list, checking if each Ellipse2D.contains the point. Then do something with the selected Ellipse
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
for (Ellipse2D ellipse : ellipses) {
if (ellipse.contains(p) {
selectedEllipse = ellipse;
// do something with selectedEllipse
break;
} else {
selectedEllipse = null;
}
}
}
5th: Loop through your ellipses to paint the in the paintComponent method
protected void paintComponent(Grapchics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for (Ellipse2D ellipse : ellipses) {
g2.fill(ellipse):
}
}
Side Notes
You must call super.paintComponent in your paintComponent method
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
UPDATE
After taking a closer look at your code, I see more of what you are trying to achieve. Looks like you want am 8 by 8 grid of your ellipses. Another option is just to create 64 panels. and paint each of them.
First have a panel class where you can set the color
public class EllipsePanel extends JPanel {
private Color color;
public EllipsePanel(Color color) {
this.color = color;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
Then you can use a panel to hold all those panel and use a GridLayout, but also keeping a JPanel[][] so you can easily refer to each panel. You can also a add a mouselistener to each panel
JPanel gridPanel = new JPanel(new GridLayout(8, 8));
EllipsePanel[][] panels = new EllipsePanel[8][8];
EllipsePanel selectedPanel = null;
int currentRow;
int currentCol;
...
for (int i = 0; i < 8; i++) {
for (int j = 0; i < 8; j++) {
final EllipPanel panel = new EllipsePanel(getRendomColor);
panel.addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e) {
selectedPanel = panel;
// do something with selected panel;
}
});
gridPanel.add(panel);
}
}
You should implement a mouse listener on your JPannel, and then use the position clicked retrieved from the listener to work out which ellipse was clicked
Recently I've been working on a program that paints an area with empty, colored squares. Their locations on the screen are based off of the values 1 and 2 in a text file. 1s are supposed to make red boxes, and 2s are supposed to make green boxes. However, when I run the program, only red boxes are painted. I did some testing and found out that the repaint method is only being called twice(once sometimes for some reason), even though there are close to 300 values in the file, and repaint() should be called once for every value. Here is my code:
public class MAP extends JFrame {
public static void main(String[] args) throws IOException {
MAP map = new MAP();
}
Shape shape;
int x = -32;
int y = 0;
ArrayList<Shape> shapes = new ArrayList<Shape>();
Graphics2D g2;
Color coulor = null;
private class PaintSurface extends JComponent {
public PaintSurface() {
}
public void paint(Graphics g) {
g2 = (Graphics2D) g;
g2.setColor(coulor);
for (Shape s : shapes) {
g2.draw(s);
}
}
}
public MAP() throws FileNotFoundException, IOException {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
frame.add(panel);
frame.setTitle("Grid Maker");
frame.setSize(400, 200);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.add(new PaintSurface(), BorderLayout.CENTER);
frame.setVisible(true);
readNextLine();
}
private void readNextLine() throws IOException {
File file = new File("map.txt");
BufferedReader in = new BufferedReader(new FileReader(file));
String line = in.readLine();
while (line != null) {
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == '1') {
coulor = Color.RED;
x += 32;
int smallX = x / 32;
int smallY = y / 32;
shape = new Rectangle2D.Float(x, y, 32, 32);
shapes.add(shape);
repaint();
} else if (c == '2') {
coulor = Color.GREEN;
x += 32;
int smallX = x / 32;
int smallY = y / 32;
shape = new Rectangle2D.Float(x, y, 32, 32);
shapes.add(shape);
repaint();
}
}
line = in.readLine();
x = -32;
y += 32;
}
}
}
Why isn't this code working properly?
Just to add to other answers, here is a piece of code (based on yours) which looks already a lot better (yet there are still some issues, but you are not there yet):
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class MAP extends JFrame {
public static void main(String[] args) throws IOException {
MAP map = new MAP();
}
public static class ColoredShape {
private Shape shape;
private Color color;
public ColoredShape(Shape shape, Color color) {
super();
this.shape = shape;
this.color = color;
}
public Shape getShape() {
return shape;
}
public Color getColor() {
return color;
}
}
int x = -32;
int y = 0;
List<ColoredShape> shapes = new ArrayList<ColoredShape>();
Graphics2D g2;
private class PaintSurface extends JComponent {
public PaintSurface() {
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g2 = (Graphics2D) g;
for (ColoredShape s : shapes) {
g2.setColor(s.getColor());
g2.draw(s.getShape());
}
}
}
public MAP() throws FileNotFoundException, IOException {
JFrame frame = new JFrame();
frame.setTitle("Grid Maker");
frame.setSize(400, 400);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.add(new PaintSurface(), BorderLayout.CENTER);
frame.setVisible(true);
readNextLine();
}
private void readNextLine() throws IOException {
BufferedReader in = new BufferedReader(new StringReader("11121\n1221\n2212\n221121\n111221\n11221\n222\n2222\n"));
String line = in.readLine();
while (line != null) {
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
Color color = null;
if (c == '1') {
color = Color.RED;
} else if (c == '2') {
color = Color.GREEN;
}
if (color != null) {
shapes.add(new ColoredShape(new Rectangle2D.Float(x, y, 32, 32), color));
x += 32;
repaint();
}
}
line = in.readLine();
x = -32;
y += 32;
}
}
}
Painting is transient, or stateless.
repaint is a "request" made to the repaint manager to tell it that it should, at some time in the future, when it's ready, it should paint some portion of the screen, that it deems to be dirty.
This means that when you call g2.setColor(coulor) in you paint method, it is using the LAST value that it was set to (when paint is called)....which is probably RED.
Raufio is right, you should be providing color information along with the shapes. Personally, I would set up a second List which just contained Color objects, where each index of the Shape list corresponded directly to the Color in the Color List.
Check out Painting in AWT and Swing for more details on how painting in works in Swing.
Now, to the whiny part ;)
It is not recommended to override paint. There are lots of reasons for this, paint is responsible for calling a number of important methods, including paintChildren and paintComponent, which perform very important tasks.
Instead, you should override paintComponent (and make sure you call super.paintComponent)
Check out Performing Custom Painting for more details.
Update with rough example
So this is a rough example of what I'm talking about...
public class TestPainting {
public static void main(String[] args) {
new TestPainting();
}
public TestPainting() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new PaintingPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class PaintingPane extends JPanel {
private static final int WIDTH = 200;
private static final int HEIGHT = 200;
private List<Shape> shapes;
private List<Color> colors;
public PaintingPane() {
shapes = new ArrayList<>(25);
colors = new ArrayList<>(25);
for (int index = 0; index < (int) Math.round(Math.random() * 100); index++) {
int x = (int) Math.round(Math.random() * (WIDTH * 0.75f));
int y = (int) Math.round(Math.random() * (HEIGHT * 0.75f));
int width = (int) Math.round(Math.random() * (WIDTH * 0.25f));
int height = (int) Math.round(Math.random() * (HEIGHT * 0.25f));
if (width < 5) {
width = 5;
}
if (height < 5) {
height = 5;
}
if (x + width > WIDTH) {
x -= width - WIDTH;
}
if (y + height > HEIGHT) {
y -= height - HEIGHT;
}
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
Color color = ((int)Math.round(Math.random() * 2)) == 1 ? Color.RED : Color.GREEN;
shapes.add(new Rectangle(x, y, width, height));
colors.add(color);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (int index = 0; index < shapes.size(); index++) {
g2d.setColor(colors.get(index));
g2d.draw(shapes.get(index));
}
g2d.dispose();
}
}
}
The first thing I see is that you are only coloring shapes one color at a time. So here:
public void paint(Graphics g) {
g2 = (Graphics2D) g;
g2.setColor(coulor); //set the drawing color
for (Shape s : shapes) {
g2.draw(s); //draw in that color
}
}
All your shapes are being drawn in the same color, when you want to color them differently. I think a better way to do things is to add all your shapes into your list, keeping track of their color, and calling repaint() once. Also, I would change the paint method to something to the effect of:
public void paint(Graphics g) {
g2 = (Graphics2D) g;
for (Shape s : shapes) {
g2.setColor(coulor[indexOfShape]); //set the drawing color
g2.draw(s); //draw in that color
}
}
Also, for repaint only being called twice: It is probably throwing an IOException. Try using a try {...} catch(IOException e) {...} block instead of just throwing it up the line. Something like:
private void readNextLine() {
try {
File file = new File("map.txt");
BufferedReader in = new BufferedReader(new FileReader(file));
String line = in.readLine();
...
...
} catch (IOException e) {
e.printStackTrace();
}
}
It should complain about something if it isn't reading right.
Sorry guys, I deleted my APPLES and CATS example :) Here's the updated version of my question!
I'm losing my sanity here. I need someone who can enlighten me. I've tried a couple of times explaining my problem here. Hopefully, this time, my question will be easier to understand.
Basically I have this frame, and there's an image displayed. There is a JList on the right, and there is another panel for JLabels at the bottom. Here's a screencap of my frame.
When I click on the image, a JOptionPane pops out, like so. And I enter my input. My JList is an ArrayList, so everything I input is added to the JList and the JPanel at the bottom.
Now, when I hover on the the part where I clicked, you noticed that the square disappeared). It only appears when I click the image, and when I hover the label at the bottom. My labels, as of now are LOLZ NOSE and INPUT HERE.
What I want to do is when I hover on the label, for example INPUT HERE, it shows the square again, featuring the part where I clicked. My problem now is when I click on NOSE, which is supposed to be showing a square on the nose part and a the name NOSE with black bg, IT IS NOT SHOWING. Also, only the last label's square is shown, disregarding the other labels' position clicked.
How do I get a label to remember the position of the click I make? People said I should use ArrayLists or HashCodes however I have no idea how to implement them. Thank you to anyone who can help.
Edit: I've already done the rectangle, btw. It's showing only for the last label inputted. Here are some of the code snippets requested!
How I'm setting the text on JLabel and updating the JList:
public void updateLabel(){
StringBuilder text = new StringBuilder(); //creates empty builder, capacity 16
for(Object s: tagModel.toArray()) //returns an array containing the elements of the tagModel
text.append(" " + s);
repaint();
hoverLabel.setText(text.toString()); //returns a String
hoverLabel.addMouseMotionListener(this);
hoverPanel.add(hoverLabel);
}
My mouseListener upon click:
#Override
public void mouseClicked(MouseEvent event) {
// TODO Auto-generated method stub
x = event.getX();
y = event.getY();
isRectPresent = true;
repaint();
input = JOptionPane.showInputDialog("Enter tag name:");
if((input != null) && !input.isEmpty()){
tagModel.addElement(input);
}
}
My mouseMotionListener upon hovering:
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
xpos = e.getX(); //gets where the mouse moved
ypos = e.getY();
//checks if the mouse is inside the bounds of the rectangle
if (xpos > x && xpos < x + 100 && ypos > y && ypos < y + 100)
isRectPresent = false;
if(e.getSource() == hoverLabel){
isRectPresent = true;
repaint();
}
repaint();
}
How I'm painting:
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(image, 0, 0, null);
if(image != null && isRectPresent){
Stroke stroke = g2.getStroke();
g2.setStroke(new BasicStroke(4));
g2.setColor(Color.WHITE);
g2.drawRect(x-50, y-50, 100, 100);
g2.setStroke(stroke);
}else{
if(xpos > x && xpos < x + 100 && ypos > y && ypos < y + 100){
g.setColor(Color.BLACK);
g.fillRect(x-50, y-50, 100, 25);
g.setColor(Color.WHITE);
g.setFont(new Font("Tahoma", Font.BOLD, 12));
g.drawString(input, x-30, y-30);
}
}
}
If you want me to add some more snippets, just tell me! :)
You should create a HashMap, say something like:
Map linkSet = new HashMap();
And whenever you click on the drawing and create a label, add the JLabel and the point on the image to the set using the put method with the JLabel as the key and the Point as the value. Then in the JLabel's MouseMotionListener, use your label as a key and obtain the corresponding point from the set using the map's get(...) method.
edit:
Corrected as per alicedimarco's comment. Again, thanks!
edit 2
I think you want again to use a Map. If you have a Map, you can have it retrieve the Point of interest from the JLabel's or the JList's String, and then pass this Point to the class that's drawing the image and let it use the Point to draw a rectangle. For instance you could give the image drawing class a Point field called displayPoint, and a method called setDisplayPoint(Point p). It can be as simple as this:
public void setDisplayPoint(Point p) {
this.displayPoint = p;
repaint();
}
and assuming that the object of interest is centered at that point, use the displayPoint in the paintComponent method:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// draw image
if (img != null) {
g.drawImage(img, X_SHIFT, Y_SHIFT, null);
}
// if displayPoint not null, draw the surrounding rectangle
if (displayPoint != null) {
g.setColor(RECT_COLOR);
int x = displayPoint.x - RECT_WIDTH / 2;
int y = displayPoint.y - RECT_WIDTH / 2;
int width = RECT_WIDTH;
int height = RECT_WIDTH;
g.drawRect(x, y, width, height);
}
}
edit 3:
To get mouse clicks, it's quite easy, simply add a MouseListener to the component that holds the image:
// !! added
imgRect.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
imgMousePressed(e);
}
});
And in your code that is called from this mouse listener, use a JOptionPane to get the user's choice of tag name, and add the resulting String to both the listDataModel so that it is seen in the JList and also in the stringPointMap together with the Point obtained from the MouseEvent so that you can map the String to the Point and be able to retrieve it:
// !! added
private void imgMousePressed(MouseEvent e) {
String result = JOptionPane.showInputDialog(this,
"Please enter name for this point on image:");
if (result != null) {
stringPointMap.put(result, e.getPoint());
listDataModel.addElement(result);
}
}
That's it.
Then putting it all together:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class ImageRectMain extends JPanel {
private ImageRect imgRect;
private DefaultListModel listDataModel = new DefaultListModel();
private JList list = new JList(listDataModel);
private Map<String, Point> stringPointMap = new HashMap<String, Point>();
public ImageRectMain() {
String nose = "Nose";
String ear = "Ear";
String rightEye = "Right Eye";
String leftEye = "Left Eye";
listDataModel.addElement(ear);
listDataModel.addElement(nose);
listDataModel.addElement(rightEye);
listDataModel.addElement(leftEye);
stringPointMap.put(nose, new Point(480, 500));
stringPointMap.put(ear, new Point(270, 230));
stringPointMap.put(rightEye, new Point(380, 390));
stringPointMap.put(leftEye, new Point(662, 440));
MouseAdapter listMouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
listMouseMoved(e);
}
#Override
public void mouseExited(MouseEvent e) {
listMouseExited(e);
}
};
list.addMouseMotionListener(listMouseAdapter);
list.addMouseListener(listMouseAdapter);
try {
imgRect = new ImageRect();
// !! added
imgRect.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
imgMousePressed(e);
}
});
JPanel eastPanel = new JPanel();
eastPanel.setLayout(new BoxLayout(eastPanel, BoxLayout.PAGE_AXIS));
eastPanel.add(new JLabel("You have tagged the following:"));
eastPanel.add(new JScrollPane(list));
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
eastPanel.add(Box.createVerticalGlue());
setLayout(new BorderLayout());
add(imgRect, BorderLayout.CENTER);
add(eastPanel, BorderLayout.EAST);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// !! added
private void imgMousePressed(MouseEvent e) {
String result = JOptionPane.showInputDialog(this,
"Please enter name for this point on image:");
if (result != null) {
stringPointMap.put(result, e.getPoint());
listDataModel.addElement(result);
}
}
private void listMouseExited(MouseEvent e) {
imgRect.setDisplayPoint(null);
}
private void listMouseMoved(MouseEvent e) {
int index = list.locationToIndex(e.getPoint());
Object value = listDataModel.get(index);
if (value != null) {
Point point = stringPointMap.get(value.toString());
if (point != null) {
imgRect.setDisplayPoint(point);
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("ImageRectMain");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageRectMain());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ImageRect extends JPanel {
public static final String IMAGE_PATH = "http://i.stack.imgur.com/7oNzg.jpg";
private static final int DEFAULT_W = 687;
private static final int DEFAULT_H = 636;
private static final int X_SHIFT = -6;
private static final int Y_SHIFT = -26;
private static final Color RECT_COLOR = Color.pink;
private static final int RECT_WIDTH = 40;
private BufferedImage img;
private Point displayPoint = null;
public ImageRect() throws MalformedURLException, IOException {
img = ImageIO.read(new URL(IMAGE_PATH));
}
public void setDisplayPoint(Point p) {
this.displayPoint = p;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, X_SHIFT, Y_SHIFT, null);
}
if (displayPoint != null) {
g.setColor(RECT_COLOR);
int x = displayPoint.x - RECT_WIDTH / 2;
int y = displayPoint.y - RECT_WIDTH / 2;
int width = RECT_WIDTH;
int height = RECT_WIDTH;
g.drawRect(x, y, width, height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(DEFAULT_W, DEFAULT_H);
}
}
One nice feature of a JList is that you can story any object in it. You're not limited to strings. When objects are stored in JLists, swing will call the object's toString() method, and display it in the list.
Knowing this, you can now write your own class that stores the name of your selection label and the coordinates of the box. This object's toString() method will return the name of the label, which will make the right thing appear in the JList.
Then, in the selection event handler for the JList, you can get your custom object out, and retrieve the box coordinates stored in it, and draw them on the screen. No need to fuss with other containers (although knowing how to use them is a good thing to).
Ok, Create a class like this...
public class MyLabel {
private int x;
private int y;
private String text;
public MyLabel (String text, int x, int y) {
this.text = text;
// assign x and y too...
}
#Override
public String toString() {
return label;
}
public int getX() {
return x;
}
// similar function to getY()
}
The class above overrides toString(), so when you put it in a JList, it will use the call to toString() to determine what to display, and since this toString implementation returns the label name you'll see that in the list.
And add those into your JList instead of Strings. Then at some point in your code you'll do something like this...
// this is pseudocode, method names may not be correct...
MyLabel ml = (MyLabel)jList.getSelectedItem();
int x = ml.getX();
int y = ml.getY();
// draw the box...
Hope that helps.