Moving Java Swing rectangle leaves rectangles behind - java

When I finally figured out the repaint method, I came to a problem. I want to move a rectangle across the screen, rather than re-drawing it again. Redrawing is fine, but it leaves the older rectangle behind it! This is my code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tutorial3{
public static void main(String[] agrs){
createAndStartGui();
}
static void createAndStartGui(){
JFrame f = new JFrame("tutorial 3");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setPreferredSize(new Dimension(500, 300));
MyPanel panel = new MyPanel();
f.add(panel);
f.pack();
f.setVisible(true);
for (int i = 0; i < 10; i++){
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Tutorial3.class.getName()).log(Level.SEVERE, null, ex);
}
panel.user.move("right");
panel.repaint();
}
}
}
class MyRectangle{
int x;
int y;
public MyRectangle(int x, int y){
this.x = x;
this.y = y;
}
void move(String direction){
switch (direction){
case "up":
this.y -= 10;
break;
case "down":
this.y += 10;
break;
case "left":
this.x -= 10;
break;
case "right":
this.x += 10;
break;
default:
break;
}
}
}
class MyPanel extends JPanel{
MyRectangle user = new MyRectangle(10, 10);
public MyPanel(){
}
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
}
How do I get the rectangle that is left behind disappear (I DO NOT WANT TO CLEAR THE FULL WINDOW)? Or even better yet, how do I get the rectangle to 'move' (if it is possible)?
My end result:
What I want in the end:
Note: simply drawing the rectangle in that point isn't what I want. I want to see it getting dragged across.

Your problem is that you are only painting the rectangle, rather than the whole panel, so the panel ends up full of rectangles as you call the method. You need to draw the background of the panel too. This will "erase" the previous rectangles so the panel only has whatever you paint in that particular call and not what you did previously. To accomplish this you need to call:
super.paintComponent(g);
at the beginning of your paintComponent method (before drawing anything else). This works because the only thing that paintComponent needs to do in an empty JPanel is painting the background.
So:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
EDIT:
To answer some of your comments:
I want to move a rectangle across the screen, rather than re-drawing it again.
There's no such thing as "moving" a rectangle. You can have things painted on the screen. If you want to see other things you have to paint those other things. There's no inherent "move the color of the pixels to the pixels...", that's not how it works. Do you want things? Draw them. Do you want them to move? Draw them repeatedly in different positions.
(I DO NOT WANT TO CLEAR THE FULL WINDOW)
But you do. You want to repaint the whole panel each time something has to change. If there are other things in the panel that you don't want "erased" then repaint them.
To be clear, it would be possible to only clear the "old" rectangle position and paint a new one, without affecting the rest of the panel. But that is unnecesarily tricky. When you override paintComponent calling super.paintComponent(g) in the first line is the standard procedure. Not putting it has to be a very conscious decision and you better are sure of what are you doing.
If your program is done in a way that part of your code misbehaves when you repaint the background of your panel, I can tell you with confidence that is those parts that aren't well designed and not that calling super.paintComponent(g) is a bad idea.
paintComponent has the responsibility of painting the whole component. The background is part of the component. It's natural, and good design within Swing, to do it when you override it.

Related

Painting a group of objects in a JPanel

I'm pretty new to Java and the GUI world. Right now I'm trying to create a really basic space shooter. To create it I started creating a JFrame, in which I've later on put a personal extension of a JPanel called GamePanel, on which I'm now trying to display all my components. Until here it's all pretty clear, the problem comes now: I have my GamePanel in which I display my player, and on the KeyEvent of pressing S the player should shoot the Bullets. I've managed the bullets as an Array, called Shooter[], of Bullet Objects, created by myself this way:
public class Bullet implements ActionListener{
Timer Time = new Timer(20, this);
private int BulletY = 430;
public int PlayerX;
public Rectangle Bound = new Rectangle();
public Bullet(int playerx) {
this.PlayerX = playerx;
Time.start();
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillRect(PlayerX + 2, BulletY, 3, 10);
g.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
if (Time.isRunning()) {
BulletY = BulletY - 5;
Bound = new Rectangle (PlayerX + 2, BulletY, 3, 10);
}
}
}
I thought that calling the draw method in the GamePanel's paint() method would have allowed me to display both all the bullets shot and the player. What actually happens is that at the start it seems allright, but when I press S the player disappears and just one bullet is shot. Can you explain me why? This is how my paint() method looks like:
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, 500, 500);
for(int i = 0; i < BulletsCounter; i++) {
Shooter[i].draw(g);
}
g.setColor(Color.RED);
g.fillRect(PlayerX, PlayerY, 20, 20);
//System.out.println("Here I should have painted the player...");
g.dispose();
}
BulletsCounter is a counter I've created to avoid any NullPointerExceptions in painting the whole array, it increases when S is pressed and so another bullet of the array is initialized and shot.
Thank you for your patience, I'm new to the site, so warn me for any mistake.
You've several significant problems, the biggest given first:
You're disposing a Graphics object given to you by the JVM. Never do this as this will break the painting chain. Instead, only dispose of a Graphics object that you yourself have created.
You're drawing within paint which is not good for several reasons, but especially bad for animation since you don't have automatic double buffering of the image
You don't call the super painting method within your override and thus don't allow the JPanel to do house-keeping painting.
Recommendations:
Don't dispose of the Graphics object, not unless you, yourself, create it, for example if you extract one from a BufferedImage.
Override the JPanel's paintComponent method, not its paint method to give you double buffering and smoother animation.
And call super.paintComponent(g) first thing in your override to allow for housekeeping painting

Image flickering upon being repainted by the mouseDragged method

As the title suggests, my problem is that I want to be able to drag an image.
In this specific case, I want to drag an image from one JPanel (or rather my own subclass) into another (different) subclass of JPanel. Therefore, I added an MouseListener to my JPanel subclass, so that upon clicking a certain area in the panel, an image is chosen to be painted on the JFrame (subclass). Here's some code so you'll understand my problem:
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if (x >= 10 && x < 42 && y >= 10 && y < 42) {
image = barracks; //barracks is a predefined image, created in the constructor
dragBuilding = true;
PixelMain.pixelMain.repaint(); //pixelMain is an instance of the JFrame subclass
}
}
//irrelevant code, e.g mouseMoved, ...
public void mouseDragged(MouseEvent e) {
if (dragBuilding) {
//System.out.println("GPanel mouseDragged");
PixelMain.pixelMain.repaint();
}
}
the JFrame subclass only contains the constructor and the following code:
public void paint(Graphics g) { //i would have used paintComponent, but it seems like JFrame does not have this method ...?
super.paint(g);
if (PixelMain.panelOffense.getDragBuilding()) { //panelOffense is an instance of the JPanel subclass, getDragBuilding returns a boolean that depends on whether the mouse is held down at the moment
Graphics2D g2 = (Graphics2D) g;
Rectangle2D tr = new Rectangle2D.Double((int)getMousePosition().getX(), (int)getMousePosition().getY(), 16, 16); //size of the texture
TexturePaint tp = new TexturePaint(PixelMain.panelOffense.getImg(), tr);
g2.setPaint(tp);
Rectangle2D r = (Rectangle2D) new Rectangle((int)getMousePosition().getX(), (int)getMousePosition().getY(), 16, 16); //area to fill with texture
g2.fill(r);
System.out.println("test");
}
}
Before you ask - I did move some code to other classes so it's called less often, but that's not the problem. Even if the paint method only draws a rectangle (directly on Graphics g, not Graphics2D), the rectangle flickers.
If anyone could help me figure out a solution, I'd be very thankful!
Note: I know it's probably not very elegant to draw on a JFrame or a subclass of JFrame, but I personally don't know an alternative.
Note 2: According to google/stackoverflow results or threads that I read, I should use a JPanel, which seems to be double-buffered (whatever that is, I didn't really understand that. but then again, it's almost 11 pm here). Hence, I could probably move all my components to a JPanel to solve the issue, but I wanted to try to solve the problem without doing that.
Note 3: Yes, the code belongs to a (strategy) game I'm writing, but considering that the problem is not really related to game development exclusively, I decided to post it here and not at game development stack exchange.

Weird output for Graphics code

I am attempting to make a Connect Four game to improve my ability with Java Graphics and as a school project. The background for the game will be a blue JPanel and the game board will be a separate JPanel that will be placed on top of the background. See my classes below:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class gameBoard extends JPanel {
private Board bored;
public gameBoard(){
setLayout(new BorderLayout());
bored = new Board();//does not appear in Center Region of gameBoard
add(bored, BorderLayout.CENTER);
}
public void paint(Graphics g){//This line is the one that is acting weird.
//blue rectangle board is here, but when repaint called
//JFrame turns blue and does not add new JPanel called above
g.setColor(Color.BLUE);
g.fillRect(0, 0, 1456, 916);
}
}
AND
import java.awt.BasicStroke;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class Board extends JPanel {
/*
* 2d array will represent board and take 1's(red) and 2's(black) the nums
* represent pieces, with each redraw of the board, a check will be done to
* compare a sum against blackWin and redWin. Sum will be created by
* summing a continuous line of 1's or 2's going left -> right
*/
public int[][] boardEvalMatrix = new int[6][7];
private final int blackWin = 8, redWin = 4;
public Board() {//1200 x 764
BoardMethods a = new BoardMethods();
a.printBoard(getBoard());
JPanel panelLORDY = new JPanel(new FlowLayout());
repaint();
}
public int[][] getBoard(){
return boardEvalMatrix;
}
public void paint(Graphics g){
g.setColor(Color.BLUE);//Drawing background with actual board as a Test
g.fillRect(0, 0, 1456, 916);//will not remain like this
Graphics2D newG = (Graphics2D) g;
newG.setStroke(new BasicStroke(15));
g.setColor(Color.YELLOW);
for(int a = 0; a < 6; a++)//rows for board --- rowHeight is 127
g.drawRect(128, 68+ (a*127), 1200, 127);
//g.setColor(Color.BLACK);
//newG.setStroke(new BasicStroke(8));
//for(int a = 0; a < 7; a++)//columns for board --- columnWidth is 171
// g.drawRect(208, 152, 70, 10);
//g.drawLine(50,0, 1456, 916); //width 1456 length 916 - school computer monitors
}
}
So what happened is this:
PROBLEM 1:
When I include the public void paint(Graphics g) line in the gameBoard class, the display that appears when I run the driver is just a gray JFrame, even though there is no call to repaint() and the paint() method is empty. However, when I deleted the line creating the paint method, the problem disappeared and the proper display appeared.
PROBLEM 2:
Even when I placed the code to draw a blue rectangle in the paint method in the gameBoard class and called repaint() the JFrame was blue, which is partly right. I know that Java executes commands from top to bottom so I made sure that the code adding the actual game board to the gameBoard JPanelcame after drawing a blue rectangle, but it didnt work.
QUESTION:
What did I do wrong and how do I fix it?
To change the background color of a panel you just use:
setBackground( Color.BLUE );
on the panel. Then is no need for custom painting.
When you override paint() and forget the super.paint(), then you really mess up the painting process. The paint() method is responsible for painting the child components on the panel. Since you don't invoke super.paint() the children never get painted.
If you do need custom painting for some reason then you should override the paintComponent() method and don't forget to invoke super.paintComponent(). Don't override paint().
Read the Swing tutorial on 'Custom Painting`, especially the section on A Closer Look at the Paint Mechanism for more information and examples.

Drawing in java with JPanel

I'm making a program where you have a server and a client, and the idea is that you draw on the client jpanel, and the coordinates will then be sent to the server, which will sort of mimic the drawing. I've done that, but the problem is now, that my drawing mechanism is pretty bad. Right now I'm just using an oval that gets drawn over and over again on the coordinate of the mouse, which sometimes leaves spaces between the ovals if you move the mouse too fast.
To better illustrate, here's an SS: http://gyazo.com/6ed1017e9efd6beaa4b5d56052fda260
As you can see, it's only consistent when you move the mouse relatively slow, but as soon as you move it a bit fast, it leaves spaces.
How do I prevent this from happening?
Right now the client just sends x and y coordinates, so here's the server side code:
package com.company;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server extends JPanel{
static MouseData mouseReceive;
static Draw draw;
static int x;
static int y;
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Server server = new Server();
JFrame frame = new JFrame("Server");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(server);
frame.setSize(1024, 600);
frame.setVisible(true);
draw = new Draw(x,y);
ServerSocket serverSock = new ServerSocket(1234);
Socket s = serverSock.accept();
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
while(true) {
mouseReceive = (MouseData) in.readObject();
draw = new Draw(mouseReceive.mouseX,mouseReceive.mouseY);
}
}
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
draw.display(g);
repaint();
}
}
And here's my draw class:
package com.company;
import java.awt.*;
/**
* Created by John on 21/04/2015.
*/
public class Draw {
int xLoc;
int yLoc;
Draw(int x, int y){
xLoc = x;
yLoc = y;
}
public void display(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.fillOval(xLoc,yLoc,7,7);
}
}
I tried finding someone else having the same problem on this site through the search function, but I had no luck in doing so :( If I missed it however, please direct me to that topic!
If anyone could help me out, I'd appreciate it a whole lot! Have a nice day :)
Presuming the Client is using a MouseListener (or MouseMotionListener): the MouseListener can only fire as fast as a certain interval. For example when the mouse is constantly moved your listener will receive a MouseEvent for every interval rather than every pixel. As a result, moving the mouse fast may result in drawing items that are not adjacent to each other. AFAIK, you cannot increase the speed, but you can draw lines between two sequential points making them look continuous (eg by using a List of each event location and using g.drawLine on each two adjacent points in the List).
Other notes:
You should override paintComponent rather than the paint method.
I would recommend calling super.paintComponent in this method. This will clear the component (hence your code will then only draw the last point - see (3))
I would recommend keeping a List of locations to use for drawing, which you can iterate over and draw each circle (or draw a line between adjacent points)
Do NOT call repaint within your painting methods. The idea here is that when a new item is received from the Client, add it to the List in (3) and then call repaint.

Issues with drawing multiple circles (drawOval / Java)

My issues is the following: My actual project (of which the code below is a simplified version of) involves many concentric circles (each with a different colour) and animation utilising a Timer. The circles are drawn using the drawOval method.
My problem is that when these concentric circles are drawn, there appears to be loads of gaps in the outline of these circles, which I'm guessing is something to do with the fact that a circle is composed of pixels and lines as is any shape so the appearance of roundness is an illusion. I say this because when I swap the drawOval method for drawRect the painting looks as you would expect.
When messing around with other people's codes I saw that using RenderingHints somehow solved this problem however slowed down the animation beyond a point that I felt was acceptable.
Below is a screenshot of what is painted. Rather than seeing a solid opaque circle (as all of the circles drawn have the same colour in this example) we see this:
Here is my simplified code:
Test10
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Test10 extends JPanel {
Circle[] circles;
public static void main(String[] args) {
new Test10().go();
}
void go() {
JFrame frame = new JFrame("Circle Test");
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
circles = new Circle[200];
for (int i = 0; i < 200; i++) {
circles[i] = new Circle(i, ((2 * ( 200 - i) + 1)));
}
repaint();
frame.setPreferredSize(new Dimension(500,500));
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
for (Circle circle : circles ) {
circle.draw(g);
}
}
}
Circle
import java.awt.Graphics;
public class Circle {
int topLeft;
int diameter;
public Circle(int topLeft, int diameter) {
this.topLeft = topLeft;
this.diameter = diameter;
}
void draw(Graphics g) {
g.drawOval(topLeft, topLeft, diameter, diameter);
}
}
Could anyone explain to me a) Why this is happening and b) How to overcome this problem.
UPDATE
Having tried various methods including starting with the outermost circle and using fillOval instead of drawOval, and using a higher stroke value, I still find I have a problem with certain artefacts appearing similar to the screenshot Pavel posted. Here is a screenshot from my full application running the animation, if you look carefully you can see inconsistencies in the colour of mostly any given circle, resulting in these strange results. Their distribution actually follows the same pattern as the screenshot posted above so clearly something fundamental isn't being addressed by these options. Here is my screen shot:
It is impossible to draw perfect circle.
Try using the following method
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
int i = 0;
for (Circle circle : circles ) {
Shape circle2 = new Ellipse2D.Double(i++, i, circle.diameter, circle.diameter);
g2d.draw(circle2);
}
}
You said you tried with RenderingHints, and it slowed your animation, but you haven't give us any code with animation, so maybe try my code (it would be good to see animation implementation). It looked better, but still not what you wanted. Setting stroke to another value will solve this (set to at least 2). Another one is to use .fill() instead of .draw(). I know that it is not perfect, but you may try it.
ANOTHER IDEA
I thought, that maybe you could add some blur to your image, so those artifacts are not visible?
I haven't done it before, but I found this (found HERE):
private class BlurGlass extends JComponent {
private JFrame f;
public BlurGlass(JFrame f) {
this.f = f;
setOpaque(false);
setFocusable(false);
}
public void paintComponent(Graphics g) {
int w = f.getWidth();
int h = f.getHeight();
setLocation(0, 0);
setSize(w, h);
g.setColor(new Color(0, 0, 0, 0.3f));
g.fillRect(0, 0, w, h);
}
}
now somwhere in go() method:
frame.setGlassPane(new BlurGlass(frame));
frame.getGlassPane().setVisible(true);
It looks a lot better for me. Play a bit with this GlassPane color (try changing .3f to some other value).
You might want to make the Stroke bigger. I've had luck with this in situations similar to yours
You can try by adding this line in your Circle class inside draw function:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//and draw the Oval on g2
Also another solution might be to fill the circles:
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.fill(circle);
That happens because a computer cannot draw a perfect circle.
A computer uses square pixels to approximate a real circle but its just not possible to achieve perfection and that results in some pixels not being shown
Drawing a filled circle will help you
a detailed explanation
Can you please try fillOval method instead of drawOval.
g.fillOval(topLeft, topLeft, diameter, diameter);
Reverse your idea. Start with the outermost circle, then draw the inner circle and so on, finishing with the smallest circle. You should use fillOval in the process.
The other rendering hint that is often useful for circles/ovals is
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
See my other answer with more details and example.

Categories

Resources