Why is my Component repainted over and over without any changes happening? - java

I have a beginner Swing/AWT question for you.
I am doing custom painting of a BoxPanel Object that extends JPanel.
I have n of these BoxPanels that are then drawn in a JFrame called FormSolutionViewer using a FlowLayout.
Now the issue I am having is that after the JFrame is created and made visible, it is repainted in an infinite loop, without any changes happening to the components. Can someone explain to me why this is and how to fix it so that it only gets repainted when the window is resized or some of the data actually changes?
public class BoxPanel extends JPanel {
private Box box;
private int scaleFactor;
public BoxPanel(Box box, int scaleFactor) {
super();
this.box = box;
this.scaleFactor = scaleFactor;
this.setLayout(null); // Use null layout for absolute positioning
}
/**
* Need to override getPreferredSize() when using a FlowLayout
*/
#Override
public Dimension getPreferredSize() {
return new Dimension(this.box.getLength() * this.scaleFactor, this.box.getLength() * this.scaleFactor);
}
/**
* Paint the box border, background and its rectangles
* #param g
*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
this.setBackground(Color.white);
// Display no. of rectangles contained in tooltip
this.setToolTipText("Box contains " + this.box.getRectangles().size() + " rectangles");
// Draw border of box
this.setBorder(BorderFactory.createLineBorder(Color.black));
/* Draw rectangles contained in box */
int i = 1;
for (Rectangle rect : box.getRectangles()) {
System.out.println("Rectangle " + i + ": " + rect.toString());
g.setColor(Color.BLACK); // Set color for border
g.drawRect(rect.getPos().getX() * this.scaleFactor, rect.getPos().getY() * this.scaleFactor,
rect.getWidth() * this.scaleFactor, rect.getHeight() * this.scaleFactor);
i++;
}
}
}
public class FormSolutionViewer extends JFrame {
private JPanel contentPane;
private FeasibleSolution solution;
int scaleFactor = 40;
int spacing = 5;
/**
* Create the frame.
*/
public FormSolutionViewer(FeasibleSolution solution, int x, int y, int dpi) {
this.solution = solution;
this.scaleFactor = (int) Math.round(dpi / 2.4);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
setBounds(x, y, 800, 600);
setTitle("Initialized Solution of " + solution.getInstance().toString());
this.setBackground(new Color(250, 250, 250));
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(spacing, spacing, spacing, spacing));
FlowLayout layout = new FlowLayout(FlowLayout.LEADING, 5, 5);
contentPane.setLayout(layout);
this.setContentPane(contentPane);
/* Place the boxes */
int boxNo = 0;
int rowCount = 0;
for (Box box : solution.getBoxes()) {
boxNo++;
BoxPanel boxPanel = new BoxPanel(box, scaleFactor);
contentPane.add(boxPanel);
contentPane.setSize(this.getWidth(), 500);
boxPanel.setVisible(true);
}
}
}

this.setBackground(Color.white);
...
this.setToolTipText("Box contains " + this.box.getRectangles().size() + " rectangles");
...
this.setBorder(BorderFactory.createLineBorder(Color.black));
Don't set a property of the component in the painting method. When a property of a component is changed, Swing invokes revalidate()/repaint() on the component to reflect the new state of the component.
The point of a painting method is to paint the component in its current state, not change its state.

Related

Aren't Graphics/JPanel dimensions measured in pixels?

When I draw [drawRect(x, y, width, height)] a rectangle on an JPanel inside a JFrame, that has a width of e.g. 500, it is actually wider than 500 Pixels on my Screen. How is this measured?
Whilst messing around with a drawing Rectangles on an JPanel and the size of the JFrame around this, i recognized, that 500 "width" are different things, when it comes to JFrame and JPanel.
A JFrame that is created with a width of 1920 Pixels is exactly 1920 Pixels wide, means, as wide as my screen (1920x1080).
If i draw a rectangle with a width of 1920 on a JPanel, that is inside the JFrame, it extends my screen by exactly 385 Pixels. Respectively: a drawn rectangle as wide as my screen needs a width of 1535.
import javax.swing.*;
import java.awt.*
public class Main {
public static void main(String[] args){
JFrame window = new JFrame();
window.setSize(1920,1080); //Window as wide as the screen
window.add(new Canvas());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
public class Canvas extends JPanel {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(0, 0, 1920, 500); //Paints a rectangle on the JPanel
}
}
The window that opens is exactly as wide as my screen, but the rectangle inside extends it.
If I change the width of the rectangle to 1535 [drawRect(0, 0, 1535, 500)], it is as wide as the JFrame/screen. Why is that?
Edit: Since the Windows 10 Frame has no decorations at the side, just the standard menu-bar on top, I don't think this is the problem (as far as I understand decorations).
The short answer: Yes they are.
The explanation: Let us look deeper!
Running Java Swing on MacOS (tested with Metal LAF), the JFrame has insets of zero for left and right. This is similar to rendering for certain themes on Windows 10. I have included code below; the gap between the content pane and the panel's fill rectangle should remain 8 pixels. When the program is running, resize it, and check for yourself. Feel free to comment if this is not the behaviour you experience.
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
final String name;
name = javax.swing.plaf.metal.MetalLookAndFeel.class.getName();
try {
UIManager.setLookAndFeel(name);
}
catch (Throwable e) {
e.printStackTrace();
}
createAndShowWindow();
});
}
private static void createAndShowWindow() {
final int width = 1920;
final int height = 800;
final int padding = 8;
JFrame window = new JFrame();
window.setTitle("Hello World.");
window.setPreferredSize(new Dimension(width, height));
window.setSize(width, height); //Window as wide as the screen
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Canvas canvas = new Canvas(padding);
window.add(canvas, BorderLayout.CENTER);
window.pack();
window.setVisible(true);
System.out.println("w: " + window.getSize());
System.out.println("c: " + window.getContentPane().getSize());
System.out.println("p: " + canvas.getSize());
System.out.println("i: " + window.getInsets());
}
public static class Canvas extends JPanel {
private final int padding;
public Canvas(int padding) {
this.padding = padding;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(180, 120, 16));
//Paints a rectangle on the JPanel
int x = padding;
int y = padding;
int w = getWidth() - 2 * padding;
int h = getHeight() - 2 * padding;
g.fillRect(x, y, w, h);
}
}

Layering two active JPanels on top of each other

I'm trying to create a simple animation whereby a swimmer follows a path underwater. I have two JPanels - one for the swimmer and one for the background. Both are active, the swimmer is moving its limbs and the background has fish/obstacles that need to be updated.
I am trying to put the swimmer on top of the background. I'm trying to use a JLayeredPane, so I create the two layers and add them:
JLayeredPane lp = frame.getLayeredPane();
lp.setPreferredSize(new Dimension(650, 550));
underwaterScene.setSize(lp.getPreferredSize());
underwaterScene.setLocation(0,0);
swimmer.setSize(lp.getPreferredSize());
swimmer.setLocation(0,0);
swimmer.setOpaque(false);
lp.add(underwaterScene, Integer.valueOf(1));
lp.add(swimmer, Integer.valueOf(2));
The swimmer's paintComponent method simply draws the swimmer in the correct place:
g.drawImage(swimmer.sprite, swimmer.x, swimmer.y, 150, 100, null);
Currently, I only see the swimmer layer. If I remove the lp.add(swimmer ... call then I can see the background layer, so I know that it's being added.
My question is: why isn't the swimmer appearing on top of the background without completely blocking it?
Thanks in advance for any help!
Here is a working example.
Also, you can find out its context here:
https://github.com/fzoli/RemoteControlCar/blob/master/desktop/BrowserTest/src/ConstrainVisibility.java#L58
public class ConstrainVisibility extends JPanel {
protected static final String LS = System.getProperty("line.separator");
protected static final int OFFSET_X = 150;
protected static final int OFFSET_Y = 120;
protected static final int WIDTH = 200;
protected static final int HEIGHT = 200;
public ConstrainVisibility() {
super(new BorderLayout());
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setOpaque(true);
layeredPane.setBackground(new Color(200, 200, 255));
int layerIndex = 0;
// A flash player
// A swing panel
JPanel swingPanel = new JPanel();
swingPanel.setBorder(BorderFactory.createTitledBorder("Swing JPanel"));
swingPanel.setBackground(Color.GREEN);
swingPanel.setBounds(OFFSET_X * layerIndex, OFFSET_Y * layerIndex, WIDTH, HEIGHT);
layeredPane.setLayer(swingPanel, layerIndex++);
layeredPane.add(swingPanel);
// A web browser
JWebBrowser webBrowser = new JWebBrowser(JWebBrowser.constrainVisibility());
webBrowser.setBarsVisible(false);
webBrowser.setStatusBarVisible(true);
webBrowser.setHTMLContent(
"<html>" + LS +
" <body>" + LS +
" <h1>A web page</h1>" + LS +
" <p>A paragraph with a link.</p>" + LS +
" </body>" + LS +
"</html>");
webBrowser.setBounds(OFFSET_X * layerIndex, OFFSET_Y * layerIndex, WIDTH, HEIGHT);
layeredPane.setLayer(webBrowser, layerIndex++);
// A swing button
JButton swingButton = new JButton("Swing JButton");
swingButton.setBounds(OFFSET_X * layerIndex, OFFSET_Y * layerIndex, WIDTH, HEIGHT);
layeredPane.setLayer(swingButton, layerIndex++);
layeredPane.add(swingButton);
layeredPane.add(webBrowser);
layeredPane.setPreferredSize(new Dimension(WIDTH + OFFSET_X * (layerIndex - 1), HEIGHT + OFFSET_Y * (layerIndex - 1)));
add(new JScrollPane(layeredPane), BorderLayout.CENTER);
}
/* Standard main method to try that test as a standalone application. */
public static void main(String[] args) {
UIUtils.setPreferredLookAndFeel();
NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("DJ Native Swing Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ConstrainVisibility(), BorderLayout.CENTER);
frame.setSize(800, 600);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
NativeInterface.runEventPump();
}
}

Resizing JPanel when JFrame is maximized

Alright, so the following code shows a JPanel within a JFrame when the program is first run. If the window is re-sized by dragging one of the sides or corners of the frame, the JPanel re-sizes itself and maintains the aspect ratio of the monitor.
NOTE: The JPanel is set to remain within the bounds of the window on a 1920x1080 resolution monitor only. On any other monitor size, the JPanel may get cut off. See my comment above setPreferredSize() in the updatePanelSize() method.
public class Frame extends JFrame {
Panel panel = new Panel();
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Frame();
}
});
}
// Setup the window, add the panel, and initialize a "window" listener.
public Frame() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(1280, 720);
setLocationRelativeTo(null);
setVisible(true);
setTitle("Frame");
setLayout(new GridBagLayout());
add(panel);
initListeners();
}
public void initListeners() {
/** When the window is resized, the panel size is updated. */
addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
panel.updatePanelSize();
}
#Override
public void componentHidden(ComponentEvent evt) {}
#Override
public void componentShown(ComponentEvent evt) {}
#Override
public void componentMoved(ComponentEvent evt) {}
});
}
}
public class Panel extends JPanel {
public Panel() {
setBackground(new Color(100, 0, 0));
setPreferredSize(new Dimension(1052, 592));
}
// Resizes the JPanel while maintaining the same aspect ratio
// of the monitor.
public void updatePanelSize() {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
float monitorWidth = gd.getDisplayMode().getWidth();
float monitorHeight = gd.getDisplayMode().getHeight();
// Aspect ratio of the monitor in decimal form.
float monitorRatio = monitorWidth / monitorHeight;
JComponent parent = (JComponent) getParent();
float width = parent.getWidth();
float height = parent.getHeight();
width = Math.min(width, height * monitorRatio);
height = width / monitorRatio;
// I am subtracting the width and height by their respected aspect ratio
// coefficients (1920x1080 -> 16:9 (width:height)) and multiplying them
// by some scale (in this case 10) to add a "padding" to the JPanel.
// The ratio coefficients and scale will need to be edited based upon the
// resolution of your monitor.
setPreferredSize(new Dimension((int)width - (16 * 10), (int)height - (9 * 10)));
System.out.println("PanelRes: " + ((int)width - (16 * 10)) + "x" + ((int)height - (9 * 10)));
System.out.println("PanelRatio: " + getWidth() / getHeight());
}
}
The problem I am having is that if I maximize the window by double clicking the window toolbar (or whatever the correct term for the top of the window would be) or by clicking the maximize button, the JPanel does not re-size like it ought to. The Overridden componentResized() method is called when the window is maximized, but the JPanel doesn't resize. Any help on solving this issue would be great.
On resize the panel is immediately accepting the new preferred dimensions in updatePanelSize(), but on maximize/restore the panel is apparently ignoring the new preferred dimensions.
I've added a call to revalidate(), to force the panel to update in those cases where it hasn't applied the new preferred dimensions.
public void updatePanelSize() {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
float monitorWidth = gd.getDisplayMode().getWidth();
float monitorHeight = gd.getDisplayMode().getHeight();
// Aspect ratio of the monitor in decimal form.
float monitorRatio = monitorWidth / monitorHeight;
JComponent parent = (JComponent) getParent();
float width = parent.getWidth();
float height = parent.getHeight();
width = Math.min(width, height * monitorRatio);
height = width / monitorRatio;
// I am subtracting the width and height by their respective aspect ratio...
int paddedWidth = (int) width - (16 * 10);
int paddedHeight = (int) height - (9 * 10);
setPreferredSize(new Dimension(paddedWidth, paddedHeight));
int resultWidth = getWidth();
int resultHeight = getHeight();
if (paddedWidth != resultWidth && paddedHeight != resultHeight) {
revalidate(); // preferred dimensions not applied, so force them
}
System.out.println("PreferredSize: " + paddedWidth + "x" + paddedHeight);
System.out.println("PanelRes: " + resultWidth + "x" + resultHeight);
System.out.println("PanelRatio: " + (float)resultWidth / resultHeight);
}

JPanel does not refresh Graphics component on resize

This program is supposed to draw a grid of Rectangles in a JPanel. I drew the grid onto the JPanel by overriding its paintComponent method so that every time the JPanel resizes, the size of the grid would change to fit the height of the JPanel. But when I change the size of the JFrame, the grid only resizes at certain intervals. Is there a better way to change the size of the grid?
import java.awt.*;
import java.awt.geom.Rectangle2D;
import javax.swing.*;
class TextFrame extends JPanel
{
int numOfCells = 99;
int cellSize, xOffSet;
Rectangle2D.Float[][] square = new Rectangle2D.Float[numOfCells][numOfCells];
public TextFrame()
{
setSize(400, 400);
}
public void paintComponent(Graphics comp)
{
Graphics2D comp2d = (Graphics2D)comp;
cellSize = (int)getHeight()/numOfCells;
if(getWidth()<=cellSize*numOfCells)
cellSize = getWidth()/numOfCells;
xOffSet = (getWidth()-(cellSize*numOfCells))/2;
Color black = new Color(0,0,0);
Color grey = new Color(128,128,128);
boolean col = true;
for(int i=0; i<square.length; i++)
{
for(int j=0; j<square[i].length; j++)
{
if(col)
comp2d.setPaint(black);
else
comp2d.setPaint(grey);
col = !col;
square[i][j] = new Rectangle2D.Float(xOffSet+j*cellSize, i*cellSize, cellSize, cellSize);
comp2d.fill(square[i][j]);
}
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame("Conway's Game Of Life");
TextFrame life = new TextFrame();
frame.add(life);
frame.setSize(life.getHeight(), life.getWidth());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
The reason for intervals is integer arithmetic. Change:
int cellSize, xOffSet;
to:
float cellSize, xOffSet;
Also, change:
cellSize = (int)getHeight()/numOfCells;
to:
cellSize = getHeight()/(float)numOfCells;
Some other side notes:
Do not change visibility of paintComponent, it is defined as protected.
Don't forget to add super.paintComponent(comp); in paintComponent()
Do not call setSize(), override panel's getPreferredSize() and pack() the frame. For example:
public Dimension getPreferredSize(){return new Dimension(400, 400);}
Then, add frame.pack(); before making the frame visible.
Try to make paintComponent as fast as possible for better performance and user experience. You can move some things out of it, leaving only painting logic.

Java How to redraw JPanel (re-create object)?

I have a problem that, I need to only redraw/rebuild the drawing area if the "c" key is pressed.
The way I'm doing using repaint(), turns out to be causing the draw area to be of position.
I also notice that, whenever I re-size the frame, the keylistener is no longer working.
Problems:
unable to repaint correctly.
keylistener is not working after frame is re-sized.
Love to attach the display, but seems like it is blocked because I am newbie.
The following code is the main function that call the class "newZone".
frame.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e){
System.out.println("component Rebuild");
frame.getContentPane().removeAll();
frame.getContentPane().invalidate();
JComponent newContentPane = new newZone(frame.getSize());
newContentPane.setOpaque(true);
frame.getContentPane().add(newContentPane);
frame.getContentPane().revalidate();
frame.setContentPane(newContentPane);
}
});
The following is the class of newZone, which contains Paint & keylistener:
public class newZone extends JComponent implements MouseListener, MouseMotionListener, KeyListener {
JPanel panel1;
JTextArea textArea;
JScrollPane scrollPane;
MyDrawingTool Drawing;
static int firsttimer = 0;
static int preposX = 0;
static int preposY = 0;
static int widthPercentage = 80 , heightPercentage = 93;
static int numberOfYboxes,numberOfXboxes;
static Dimension currentPanelSize;
static final String NEWLINE = System.getProperty("line.separator");
static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public newZone(Dimension currentPanelSize1) {
currentPanelSize = currentPanelSize1;
Drawing = new MyDrawingTool();
Drawing.setBackground(Color.WHITE);
Drawing.setBounds( 10, 10,
(int) currentPanelSize.getWidth()*(widthPercentage)/100,
(int) currentPanelSize.getHeight()*(heightPercentage)/100 );
Drawing.setPreferredSize(new Dimension( (int) currentPanelSize.getWidth()*(widthPercentage)/100,
(int) currentPanelSize.getHeight()*(heightPercentage)/100));
Drawing.addMouseListener(this);
Drawing.addMouseMotionListener(this);
add(Drawing);
addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e){
System.out.println( "Key type: "+e.getKeyChar());
if(e.getKeyChar() == 'c'){
Drawing.redraw();
}
}
});
setFocusable(true);
}
class MyDrawingTool extends JPanel{
void redraw(){
repaint();
}
#Override
public void paint(Graphics q){
//super.paint(q);
int j,k, width, height;
int startX = 10, startY = 10;
int boxSize = 50;
width = (int)currentPanelSize.getWidth()*(widthPercentage)/100;
height = (int)currentPanelSize.getHeight()*(heightPercentage)/100;
numberOfYboxes = (height-20)/50;
numberOfXboxes = (width-20)/50;
for ( j = 0; j < numberOfYboxes; j++)
{
startX = 10;
for ( k = 0; k < numberOfXboxes; k++)
{
q.setColor(Color.WHITE);
q.fillRect(startX, startY, boxSize, boxSize);
q.setColor(Color.BLUE); //Set line color
q.drawRect(startX, startY, boxSize, boxSize);
startX+=boxSize;
}
startY+=boxSize;
}
}
}
}
I don't know why you are using a ComponentListener. I don't see any reason to remove/add/invalidate/revalidat and do all the other stuff.
All you need to do is add the panel to the CENTER of a content pane of the frame. The panel will automatically increase/decrease in size as the frame resizes. There is no need for the ComponentListener.
Custom painting should be done in the paintComponent() method and don't forget to invoke super.paintComponent(...) at the start.
The KeyListener doesn't work because focus is now on the JFrame (not the panel) after you resize the frame. You should NOT be using a KeyListener for this. Instead you should be Key Bindings which work even when the panel doesn't have focus.
It seems, you do not need to switch content pane at all.
If you use some layout on default content pane, as #camickr suggested, you won't need to handle resize and other stuff manually.
Good luck there.

Categories