Repeating controlChanges on GUI - java

I'm working on learning about using events to change things on the GUI. I am currently using Controller Events passed from another method and I think I may be missing some key information about how variables work in these methods. Here is my source code (this is currently an inner class).
class MyDrawPanel extends JPanel implements ControllerEventListener{
static boolean isWriting = false;
public void controlChange(ShortMessage event) {
isWriting = true;
repaint();
}
public void paintComponent(Graphics g){
if (isWriting){
int red = (int) (Math.random() * 250);
int green = (int) (Math.random() * 250);
int blue = (int) (Math.random() * 250);
g.setColor(new Color(red, green, blue));
int xpos = (int) (Math.random() * 190 + 10);
int ypos = (int) (Math.random() * 190 + 10);
int width = (int) (Math.random() * 50 + 10);
int height = (int) (Math.random() * 50 + 10);
g.fillRect(xpos, ypos, width, height);
//isWriting = false;
}
What I'm trying to do is draw a new rectangle every time the listener receives an event but I can only get one of two things to happen. If I try to set "isWriting" back to false at the end of the if statement the if statement seems to continuously evaluate to false and not draw any rectangles. If I comment out that code to set isWriting back to false the drawing works for a single rectangle but there is nothing to change the variable back and call the repaint method so I only get a single rectangle.
I know that my events are getting to the listener as I've used a sout to increment a count every time it gets an event so it seems the problem is with the state of the isWriting variable. Thanks for the help!

You want to avoid changing the state of your class from within a painting method. This is not a method that you have full control over, and so doing this can lead to unexpected and difficult to debug side effects.
I'm not 100% certain of what you're trying to do, but if you're trying to draw a new rectangle each time a message is received, and you want that rectangle to persist (and in this way, perhaps show multiple rectangles), then,
Either draw the rectangles onto a BufferedImage, and then display the BufferedImage into your JPanel's paintComponent method, or
Create an ArrayList of an object that contains both a Rectangle and a Color, and then with each event, create a new one of these objects, add it to the list, and call repaint(). Then within paintComponent, iterate through the list drawing the rectangles.
Don't forget that you should always call the super's paintComponent method within your override.
Otherwise if you want the rectangle to display only for a period of time then use a Swing Timer. Or if when user acknowledges receipt of the message, then use an other listener. Again, please clarify your question.
Tutorials:
Lesson: Performing Custom Painting: introductory tutorial to Swing graphics
Painting in AWT and Swing: advanced tutorial on Swing graphics

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

Graphical Bug with Java Swing - Drawing from a list makes elements jump around

So I was messing with Swing for the first time in a while and I came across a strange issue. So I am adding "shapes" to a list every so often, and then in the paintComponent method of a JPanel I am looping through the list and drawing the shapes. I also draw a shape outside of the for loop for testing purposes.
What happens is the shapes in the for loop will jump around the screen randomly. This only happens when the shapes are drawn in this loop.
What I have tried already:
Updating graphics drivers for both the integrated GPU and discrete GPU
Using java.util.Timer instead of Swing Timer
Using Thread/Runnable
Using things other than ArrayList, such as LinkedList, Vector, and a normal Array.
Trimmed literally everything out of my code except the basics, which is what we're left with here. I was drawing more complex things before when I noticed it.
Changed the timing (PERIOD variable, in millis). It will get worse if I increase or decrease it.
Changed from using System time in milliseconds to the System time in nanoseconds, converted to milliseconds. I know this should be the same but I was running out of ideas.
Here is a gif of the problem (15 seconds long):
image
You'll notice that the small squares will jump around at random intervals. This should not occur. I'm just trying to "spawn" a square at random coords.
Here is the code in a pastebin:
code
I have included all 3 classes in this order: the JPanel class, the Main class (extends JFrame), and the shape class
If any of the links don't work, inform me and I will promptly post other links.
Thanks.
This setup ...
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (this.startTime == -1L) {
this.startTime = Main.timeMillis();
}
final long timeDiff = Main.timeMillis() - this.startTime;
if (this.circlesIndex <= 19 && timeDiff > 2000) {
final int randX = this.rand.nextInt(this.WIDTH);
final int randY = this.rand.nextInt(this.HEIGHT);
this.testShapes.add(new TestShape(randX, randY));
this.startTime = Main.timeMillis();
}
for (TestShape ts : this.testShapes) {
ts.draw(g);
}
g2.setColor(Color.gray);
g2.fill3DRect(350, 400, 100, 60, true);
}
#Override
public final void actionPerformed(final ActionEvent event) {
x++;
if (x > WIDTH) {
x = -50;
}
repaint();
}
is wrong.
Paint is for painting - you should not modify the state of the UI from inside any paint method, do this within your ActionListener. The problem is, your component can be painted for any number of reasons, many of which you don't control or know about

How to move the location of the graphic in JPanel

So I'm creating a game using Javax.swing library for my uni coursework.
I have created a window and I have successfully written code to procedurally generate a game map.
However, I am unable to change the focus of the map. What I mean is that the map is always stuck in one corner of the screen. (IE: Location is set to 0,0, hence the Graphics g (the map) is put in that location going outwards.)
I would like to be able to move the "camera" so that different areas of the map can be viewed by the player.
Bellow I have pasted my method that draws the map onto the screen. Could anyone tell me what I could do to have the camera move at runtime. AKA: to shift the map left or right.
I thought of having a Graphics object that will hold the map, and then I'd only draw a subImage of that Graphics object, but considering how the map will be redrawn every frame (For animation purposes) that just means that I'll have even more graphics to redraw.
The map is 6,400 * 6,400 Pixels
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
try {
for(int x = 0; x < OverworldMap.MAP_X_SIZE; x++){
for(int y = 0; y < OverworldMap.MAP_Y_SIZE; y++){
for(int layer = 0; layer < OverworldMap.MAP_LAYER_SIZE; layer++) {
g.drawImage(OverworldMap.getTileAt(x, y, layer).getSprite(), x * SPRITE_SIZE, y * SPRITE_SIZE, null);
}
}
}
} catch (Exception e) {
LauncherClass.printErrorLog(e);
}
}
The best / easiest way to solve this is to put a JScrollPane around your JPanel, and make the JPanel the size of your image. You don't need to worry about only repainting the right part of your image - Java is pretty smart about only drawing the parts that are on screen. Note that you can show or hide the ScrollBars, but if you hide them you need to add logic to activate scrolling through some other mechanism
You cannot store a Graphics object and use it later. It is only valid for the duration of the paint method to which it is passed.
You can, however, simply offset your painting:
Image sprite = OverworldMap.getTileAt(x, y, layer).getSprite();
g.drawImage(sprite, x * SPRITE_SIZE - playerX, y * SPRITE_SIZE - playerY, this);
(Notice that the last argument to drawImage should be this.)

shooting a bullet from tank java

I'm writing a tank game . I want to have a method called shoot that when I press Space the tank have to shoot . my problem is that when the program calls this method it goes through the while loop and after that it prints the end location of the ball . I need to implement something in the while loop that every time it calculates dx and dy it goes to the paint method and paint the new location of the ball. I tried adding paintImmediately() but it throws stackoverflow error. thanks for helping me.
actually I'm changing dx and dy and I want the paint method to draw the ball at that place...
public void shoot(Image img, double fromx, double fromy, double ydestination, int speed) {
int time = 0;
double speedy, speedx;
while (dy!=ydestination) {
time++;
speedy = speed * Math.sin(Math.toRadians(angle));
speedx = speed * Math.cos(Math.toRadians(angle));
dy = (int) ((-5) * time * time + speedy * time + fromy);
dx = (int) (speedx * time + fromx);
// paintImmediately((int)dx,(int) dy, 10, 10);
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
and here is my overrided paint method the last line is for the bullet that is my question :
#Override
public void paint(Graphics g) {
System.out.println("paint");
super.paint(g);
render(bufferedGraphics);
g.drawImage(bufferedScreen, 0, 0, null);
// System.out.println(x1);
BufferedImage buff = rotateImage(mile1, angle);
BufferedImage buf = rotateImage(mile2, angle);
g.drawImage(buff, mx1 - 40, my1, null);
g.drawImage(buf, mx2 , my2, null);
g.drawImage(bullet, (int) dx, (int) dy, null);
//setVisible(true);
}
You are using the wrong approach. You are tying 3 events together: user input (click to shoot), game state update (bullet moves) and draw refresh rate (paint).
In general trying to make these work at the same speed is a nightmare and you'll never achieve it. The most common, easy and robust approach is to have an event loop. User input events trigger changes to the game state, the game state is updated periodically either by turns or by some elapsed time (and state update will depend on how much time has elapsed), the state is drawn every time it is needed, which is periodically but also for some other events like minimizing the windows, etc etc...
For Java, you can find a good library for this here. With a sample hello world that shows the different parts here.
P.S: Also, be very careful when manually sending threads to sleep. That might make your entire program unresponsive.
If your drawing code has sleep() in it to introduce waits, you are doing things very wrong indeed. You are trying to sleep there because you want the screen to keep on updating with new positions... but you are actually making the screen freeze completely, because there is only 1 thread drawing things in Java Swing; and if you sleep that thread, nothing gets drawn (and you can't even press keys or use the mouse).
What you should do instead is to update the position of your bullet over several calls to your paint() method. In pseudocode:
paint(Graphics g) {
// calls paint() on all objects in the game world
}
// you should call this once per frame
update(World w) {
// call update() on each game-world object
}
// in Tank
fire(Tank t, Point target) {
// creates a bullet at the tanks' position, and adds it to the World
}
// within Bullet
update() {
// moves this bullet along its path to its target;
// if target reached, add an explosion there and destroy the bullet
}
// within Explosion
update() {
// advance to next explosion frame;
// or if finished, destroy the explosion object
}
You can read more on game event loops here and here.

When is paint called in this program? Also, why does it extend canvas?

import java.awt.*;
import javax.swing.JFrame;
public class GraphicsDemo1 extends Canvas
{
public void paint( Graphics g )
{
g.setColor(Color.green);
g.drawRect(50,20,100,200); // draw a rectangle
g.fillOval(160,20,100,200); // draw a filled-in oval
g.setColor(Color.blue);
g.fillRect(200,400,200,20); // a filled-in rectangle
g.drawOval(200,430,200,100);
g.setColor(Color.black);
g.drawString("Graphics are pretty neat.", 500, 100);
int x = getWidth() / 2;
int y = getHeight() / 2;
g.drawString("The first letter of this string is at (" + x + "," + y + ")", x, y);
}
public static void main( String[] args )
{
// You can change the title or size here if you want.
JFrame win = new JFrame("GraphicsDemo1");
win.setSize(800,600);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsDemo1 canvas = new GraphicsDemo1();
win.add( canvas );
win.setVisible(true);
}
}
Thanks. awt and swing are very confusing to me.
why does it extend canvas?
Because who ever wrote it chose to do so. Only those classes that extend from Component can actually be painted to the screen and only when they are attached to a valid, visible window
When is paint called in this program?
Painting is the responsibility of the RepaintManager. It will decide when components need to be repainted and schedule a repaint event on the Event Dispatching Thread. This in turn calls (in your case update which calls) paint on your behalf.
You might like to have a read through Painting in AWT and Swing for more details on the subject
paint() is called whenever the control is invalidated and needs to repaint itself. Think of moving the app partially off screen and then back. Paint would get called to redraw...
I suppose a number of different controls could be extended to achieve the same goal which is basically to create a custom-drawn control. An existing control is extended to get the ability to draw on its surface, be placed in a JFrame, get repainted automatically etc.

Categories

Resources