Jcomponent Stops Getting Rendered Once It Goes Off the Screen

JComponent stops getting rendered once it goes off the screen

There are a couple of errors I can find in your program

  1. You're using a null layout, please see Null layout is evil and the answers in this question to see why you should avoid its use. (Probably not in this case, as per @MadProgrammer's comment below), this is just another approach

  2. while (true) { this line might block the Event Dispatch Thread (EDT) along with this line: Thread.sleep(16);, See Lesson: Concurrency in Swing to learn more about and How to use Swing Timers. You should also place your program on the EDT which can be done:

    public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
    @Override
    public void run() {
    //Your constructor here
    }
    });
    }
  3. You're not calling super.paintComponent() on your paintComponent() method of the Wall class which could break the paint chain, always call it first.

  4. You're extending JComponent, it would be better to extend JPanel and do custom painting over it using the Shapes API

With all of the above in mind, you can then have a code like this one:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class SingleAnimation {

private JFrame frame;
private Timer timer;

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new SingleAnimation().createAndShowGui();
}
});
}

public void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());

Wall wall = new Wall(300, 0);

timer = new Timer(16, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
wall.moveWall(-2, 0);
}
});

timer.setInitialDelay(0);
timer.start();

frame.add(wall);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

class Wall extends JPanel {
private int xCoord;
private int yCoord;

public int getxCoord() {
return xCoord;
}

public void setxCoord(int xCoord) {
this.xCoord = xCoord;
}

public int getyCoord() {
return yCoord;
}

public void setyCoord(int yCoord) {
this.yCoord = yCoord;
}

public Wall(int x, int y) {
this.xCoord = x;
this.yCoord = y;
}

public void moveWall(int xUnits, int yUnits) {
xCoord += xUnits;
yCoord += yUnits;
repaint();
}

@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLUE);

g2d.fill(new Rectangle2D.Double(xCoord, yCoord, 100, 20));
}
}

Which will produce a similar output like this one:

Sample Image

Repaint does not update the screen

First things first:

I have used the "Divide and conquer" method to only post the necessary information

Yes, you posted only the necessary information, but you're still using too many classes for a MCVE / MRE, ideally all your code would fit in a single class.

I didn't manage to make it any shorter without omitting code to make it run.

That's the idea about us asking for an MRE, you should create a brand new program to isolate the problem, in your case this is: Move a shape on a certain direction and keep moving on that direction.

Right now, I can see that you've tried creating it, and to improve, so I will post an example of what is expected in future questions of your so that you ask better questions in the future.

That being said, let's go and answer your question.


This is an example with less than 100 lines of code (not including comments) that addresses the following issues in your code:

  1. Removes the while(true) statement in favor of a Swing Timer to prevent blocking the Event Dispatch Thread (EDT)

  2. Places the program on the EDT, see point #2 of this answer

  3. Makes use of JFrame#pack() method instead of manually setting it with setSize(...) for both the JFrame and JPanel

  4. Gets rid of the "magic numbers" for the directions and uses an enum for this matter, and thus the code is more readable as we all know that TOP should move it to top, but we don't know that 1 should move it to top.

  5. Makes use of the Shape API to draw the shapes on the JPanel as shown in this answer

  6. I would also suggest naming your methods using camelCase so that rungame() becomes runGame() as it's easier to read, same for other methods. And give them more meaningful names such as hoofd, I don't know what that is and if I read it alone without context it would be extremely difficult to say what type of object it is.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class SnakeGame {
private JFrame frame;
private Snake snake;
private JPanel buttonsPane;
private JButton[] buttons; // Our array of buttons
private Timer timer;
private Direction currentDirection;

// This enum will be used to determine the direction the snake will take.
private enum Direction {
TOP, LEFT, BOTTOM, RIGHT
}

public static void main(String[] args) {
// We place our program on the EDT using Java 8 lambda expressions.
SwingUtilities.invokeLater(() -> new SnakeGame().createAndShowGUI());
}

private void createAndShowGUI() {
frame = new JFrame(getClass().getSimpleName());
snake = new Snake();
buttonsPane = new JPanel();
buttons = new JButton[Direction.values().length];

for (int i = 0; i < buttons.length; i++) {
buttons[i] = new JButton(Direction.values()[i].toString()); // We create a JButton with the current value of the direction
buttons[i].addActionListener(listener); // We set their ActionListeners
buttonsPane.add(buttons[i]); // And add them to the buttonsPane
}

currentDirection = Direction.RIGHT; // We set a default direction
timer = new Timer(1000, listener); // And start our Swing Timer

// We add our components and then pack the frame, after that we start the timer.
frame.add(snake);
frame.add(buttonsPane, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
timer.start();
}

// Our ActionListener for moving the snake
private ActionListener listener = e -> { // Using Java 8 lambda expressions again
// We set the current direction using a ternary, if the source of the event is
// the timer we leave the current direction as is
// otherwise we set it to the direction from the button clicked
currentDirection = e.getSource().equals(timer) ? currentDirection : Direction.valueOf(e.getActionCommand());
snake.move(currentDirection); // And we call the move method
};

@SuppressWarnings("serial")
class Snake extends JPanel {
private int xPos;
private int yPos;
private static final int SPEED = 10; // We set the speed as a constant (10 pixels at a time) in any direction

// We determine the movement direction
public void move(Direction direction) {
switch (direction) {
case TOP:
yPos -= SPEED;
break;
case LEFT:
xPos -= SPEED;
break;
case BOTTOM:
yPos += SPEED;
break;
case RIGHT:
xPos += SPEED;
break;
}
this.repaint(); // And repaint the snake
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.DARK_GRAY);
g2d.fill(new Rectangle2D.Double(xPos, yPos, 10, 10)); // We draw a rectangle using the Shape API
}

@Override
public Dimension getPreferredSize() {
return new Dimension(300, 300); // We set the preferredSize of the JPanel to 300 x 300
}
}
}

The above code produces the following result, for Java 7 and below or if you don't want to use lambda expressions or are too difficult for you, check the point #2 on this answer, it shows how to place the program on the EDT without lambda expressions and this answer shows how to write the ActionListener without them as well.

There you go, a simple runnable example, it's complete, it's verifiable, it's minimal and that's what we expect from you in future questions.

Sample Image

JScrollPane components are invisible until I click them

I see a couple of issues in your code:

  1. public class main {, classes should start with a capital letter, follow Java naming conventions, this will make your code easier to read and follow by you and others, also main is the name of the entry point method for all Java classes, so this might be confusing
  • FirstWordUpperCasedClass
  • firstWordLowerCasedVariable
  • firstWordLowerCasedMethod()
  • ALL_WORDS_UPPER_CASED_CONSTANT

  1. .setBounds(...), in your case you're using the default Layout Managers, so these calls are simply ignored, unless you have a good reason (and in this program you don't) for not using a layout manager, stick to those managers, combine them if needed.

  2. Don't call setSize(...) directly on your JFrame, this will take the window decorations into this size, rather, set a preferredSize for a JPanel and add that JPanel to your JFrame, then call pack() on your JFrame, see: Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? for a more in-depth explanation.

  3. Complementing @camickr in his answer in this same question, and the comment you made there:

But now the button occupies the whole pane.

You're trying to add a JTextField and a JButton, and from the
setBounds(...) calls I see that you're trying to add the +
button to the right of the JTextField, so in this case, simply add
both the JTextField and the JButton to a brand new JPanel and then set the JScrollPane's viewport to this new JPanel.

What @camickr suggested was to set the viewport to the JButton, and that's why you had the JButton taking the whole space.


  1. Create the JTextField with a number param, which will tell Swing the number of columns (or characters) that you want to display on it, rather than an empty one.

  2. And last but not least, place your program on the Event Dispatch Thread (EDT), see point #2 in this answer for more of this.

And after following all those suggestions above, you'd have a better code like this one:

import java.awt.Dimension;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class ScrollPaneSample {
private JFrame frame;
private JPanel pane;
private JScrollPane scrollPane;
private JTextField text;
private JButton add;

public static void main(String[] args) {
SwingUtilities.invokeLater(new ScrollPaneSample()::createAndShowGUI);
}

private void createAndShowGUI() {
frame = new JFrame("Bookmark");
pane = new JPanel() {
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
};
scrollPane = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

text = new JTextField(10);
pane.add(text);

add = new JButton("+");
pane.add(add);

scrollPane.setViewportView(pane);

frame.add(scrollPane);

frame.pack();
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

I made getPreferredSize(...) return a smaller size right now, because it was a window too big for this example, you can resize it to your own needs.

Sample Image

Java Swing: GUI is not updating some properties

Ok, first things first:

  1. Don't update your GUI using Thread.sleep() it will block the EDT
  2. You're not placing your program on the EDT, see point number 2 in this answer
  3. Don't extend JFrame, instead create an instance of it, see: Extends JFrame vs. creating it inside the program
  4. Don't make your program visible (i.e. call setVisible(...)) before adding all the components to it. It may cause your program to behave in a wrong manner.
  5. Try not creating your own threads, instead use a Swing Timer or a Swing Worker (links in the question's comments)

So, taking all of that into consideration, I decided to create a new program that follows all the above rules and makes the cells blue for 3 seconds or white after that time has passed, and also updates the text in the JButton as well as disabling to prevent multiple timers executing at once.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Test {
private JFrame frame;
private JPanel pane;
private JPanel cellsPane;
private MyCell[][] cells;
private JButton button;
private Timer timer;

private int counter = 3;
private boolean isFinished = false;

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Test().createAndShowGui());
}

private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());

pane = new JPanel();
cellsPane = new JPanel();

pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
cellsPane.setLayout(new GridLayout(4, 4, 5, 5));

cells = new MyCell[4][4];

for (int i = 0; i < cells.length; i++) {
for (int j = 0; j < cells[i].length; j++) {
cells[i][j] = new MyCell(Color.WHITE);
cellsPane.add(cells[i][j]);
}
}

button = new JButton("Press me!");
timer = new Timer(1000, listener);

button.addActionListener(e -> {
button.setEnabled(false);
isFinished = false;
updateCellsColors();
timer.start();
});

pane.add(cellsPane);
pane.add(button);

frame.add(pane);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}

private void updateCellsColors() {
for (int i = 0; i < cells.length; i++) {
for (int j = 0; j < cells[i].length; j++) {
cells[i][j].setCellColor(isFinished ? Color.WHITE : Color.BLUE);
}
}
}

private ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (counter == 0) {
timer.stop();
counter = 3;
isFinished = true;
button.setEnabled(true);
updateCellsColors();
}
if (isFinished) {
button.setText("Press me!");
} else {
button.setText("You have " + counter + " seconds remaining");
}
counter--;
}
};
}

@SuppressWarnings("serial")
class MyCell extends JPanel {
private Color cellColor;

public Color getCellColor() {
return cellColor;
}

public void setCellColor(Color cellColor) {
this.cellColor = cellColor;
this.setBackground(cellColor);
}

public MyCell(Color cellColor) {
this.cellColor = cellColor;
this.setOpaque(true);
this.setBackground(cellColor);
}

@Override
public Dimension getPreferredSize() {
// TODO Auto-generated method stub
return new Dimension(30, 30);
}
}

You can copy-paste it and see the same result as me:

Sample Image Sample Image



Related Topics



Leave a reply



Submit