Unresizable JFrame pack error - java

JFrame's pack() method won't work every time when the window is not resizable it seems. Try it for yourself (it may need a few retries) please:
import javax.swing.*;
import java.awt.*;
public class FramePackBug {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < 20; i++) {
JFrame window = new JFrame("Buggy");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.setBackground(Color.RED);
JPanel jp = new JPanel() {
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillRect(0, 0, 200, 100);
}
};
jp.setPreferredSize(new Dimension(200, 100));
window.add(jp);
window.pack();
window.setLocation((i % 5) * 250, (i / 5) * 150);
window.setVisible(true);
}
}
});
}
}
(You can fix it by making the frame visible directly after making it unresizable.)
Why is that so?

For reference, here's the appearance of a variation on Mac OS X. Note,
A one-pixel disparity suggests the possibility of space intended for a focus indicator; but as #Marco13 comments, the occasional appearance suggests a (non-obvious) threading issue.
As the appearance seems platform dependent, a label displays the OS and version system properties.
The example overrides getPreferredSize() to establish the enclosed panel's geometry, as suggested here.
The call to super.paintComponent() is not strictly necessary if the implementation honors the opacity property by filling every pixel.
The irregular locations seen in #AyCe's Windows screenshot are consisted with a threading issue suggested by #Marco13.
Mac OS X:
Windows 7 (#AyCe):
Ubuntu 14:
import javax.swing.*;
import java.awt.*;
public class FramePackBug {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
for (int i = 0; i < 20; i++) {
JFrame window = new JFrame("Buggy");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.setBackground(Color.RED);
JPanel jp = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 0, getWidth(), getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 100);
}
};
window.add(jp);
window.add(new JLabel(System.getProperty("os.name") + ", "
+ System.getProperty("os.version")), BorderLayout.NORTH);
window.pack();
window.setLocation((i % 5) * (window.getWidth() + 5),
(i / 5) * (window.getHeight() + 5) + 20);
window.setVisible(true);
}
}
});
}
}

EDIT: See below for updates
Sorry, this is not (yet?) the final solution, but I'm still investigating this and wanted to share the first insight, maybe it's helpful for others as well, for a further analysis:
The odd behavior is caused by the getInsets() method of the frame. When overriding the method, one can see that it returns insets where left, right and bottom are 3 in most cases, but they are 4 when the error appears:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FramePackBug {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for (int i = 0; i < 20; i++) {
final int ii = i;
JFrame window = new JFrame("Buggy")
{
#Override
public Insets getInsets()
{
Insets insets = super.getInsets();
if (insets.left == 4)
{
System.out.printf(
"Wrong insets in %d : %s\n", ii, insets);
}
return insets;
}
};
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.setBackground(Color.RED);
JPanel jp = new JPanel() {
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillRect(0, 0, 200, 100);
}
};
jp.setPreferredSize(new Dimension(200, 100));
window.add(jp);
window.pack();
window.setLocation((i % 5) * 250, (i / 5) * 150);
window.setVisible(true);
}
}
});
}
}
I've already scanned the underlying implementation, which eventually delegates to the WWindowPeer and some native methods that do some odd computations based on the window style (that is, depending on whether the window is resizable or not), and I have identified some candidate reasons for the error, but still no final conclusion. (The fact that this seems to happen randomly does not make debugging easier, of course...)
A side note: Simply calling the pack() method twice caused the frames to always appear properly for me, but of course, this could at best only be considered as a hacky workaround, as long as the root cause for the odd behavior is not identified.
Update
I dug one level deeper, but still did not find a final solution.
The initialization method of the sun.awt.windows.WFramePeer class is implemented as follows:
void initialize() {
super.initialize();
Frame target = (Frame)this.target;
if (target.getTitle() != null) {
setTitle(target.getTitle());
}
setResizable(target.isResizable());
setState(target.getExtendedState());
}
The super call goes into sun.awt.windows.WWindowPeer, and there it does the following:
void initialize() {
super.initialize();
updateInsets(insets_);
...
}
The updateInsets call ends at a native method. The native implementation of updateInsets does some suspicious calls, querying the "style" of the window, and adjusting the insets based on a WS_THICKFRAME property, e.g.
if (style & WS_THICKFRAME) {
m_insets.left = m_insets.right =
::GetSystemMetrics(SM_CXSIZEFRAME);
What I can say for sure is that the result of these updateInsets calls is wrong. They are too large for the unresizable frames, which eventually causes the errors. What I can not say for sure is: Where the wrong insets are (sometimes) updated (for some frames) to properly reflect the size for the unresizable frame.
A potential fix?
As it can be seen in the first of the updated snippets, the initialization of the WFramePeer eventually calls
setResizable(target.isResizable());
The setResizable once more delegates to a native method eventually, and in the native implementation of the setResizable method, there is this style and the WS_THICKFRAME again.
So changing the sun.awt.windows.WFramePeer#initialize() method to first set the "resizable" property, and then passing the call upwards (where it will cause updateInsets to be called) resolved the issue for me:
void initialize() {
Frame target = (Frame)this.target;
setResizable(target.isResizable());
super.initialize();
if (target.getTitle() != null) {
setTitle(target.getTitle());
}
setState(target.getExtendedState());
}
With this modification, the insets are properly computed, and the frames are always displayed correctly.

I am not sure if this is right but you could try putting the setResizable() method after the pack method since the setResizeable(false) might be disabling the pack function.

Related

Graphics Program Works on Windows but not on Mac

I wrote this program in java using swing. When I run this on my Mac each time repaint() is called fifty new blue points are made and the old blue points are erased. I have been doing a lot of research to try and fix this issue and I have had not luck. Then today in my computer science class I find out that the program works on the windows computers that are in the classroom. My question is why is this the case and how can I fix this so that the program works on my Mac? Also on a side note I am relatively new to using swing in java so I was wondering if I am organizing everything correctly and if I can do anything different?
This is the class where I draw everything within the JPanel.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Canvas;
import java.lang.Math;
import java.awt.event.*;
import javax.swing.*;
public class BarnsleyFern extends JPanel
{
private double newX,x=0;
private double newY,y=0;
public BarnsleyFern()
{
ActionListener action = new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
repaint();
}
};
Timer timer = new Timer(100,action);
//timer.start();
MouseListener mouse = new MouseListener()
{
public void mouseClicked(MouseEvent event)
{
//repaint();
}
public void mousePressed(MouseEvent event)
{
}
public void mouseReleased(MouseEvent event)
{
}
public void mouseEntered(MouseEvent event)
{
}
public void mouseExited(MouseEvent event)
{
}
};
MouseMotionListener mouseMotion = new MouseMotionListener()
{
public void mouseDragged(MouseEvent event)
{
repaint();
}
public void mouseMoved(MouseEvent event)
{
repaint();
}
};
addMouseListener(mouse);
addMouseMotionListener(mouseMotion);
}
public void paintComponent(Graphics window)
{
Graphics2D g2d = (Graphics2D)window;
g2d.translate(360,800);
fern(window);
}
public void fern(Graphics window)
{
Color newColor = new Color((int)(Math.random()*256),(int)(Math.random()*256),(int)(Math.random()*256));
for(int i=0;i<50;i++)
{
window.setColor(Color.BLUE);
int rand = (int)(Math.random()*100);
if(rand<1)
{
newX=0;
newY=0.16*y;
}
else if(rand<86)
{
newX=0.85*x + 0.04*y;
newY=0.85*y - 0.04*x + 1.6;
}
else if(rand<93)
{
newX=0.20*x - 0.26*y;
newY=0.23*x + 0.22*y + 1.6;
}
else
{
newX=0.28*y - 0.15*x;
newY=0.26*x + 0.24*y + 0.44;
}
window.fillOval((int)(newX*165.364),-(int)(newY*80.014),2,2);
x=newX;
y=newY;
}
}
}
This is the class that sets up the JFrame and adds the JPanel.
import javax.swing.*;
import java.awt.*;
public class BarnsleyFernRunner
{
public BarnsleyFernRunner()
{
JFrame frame = new JFrame();
frame.setTitle("Barnsley Fern");
frame.setSize(800,800);
frame.setLocation(300,0);
frame.setResizable(false);
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
BarnsleyFern panel= new BarnsleyFern();
panel.setSize(800,800);
panel.setOpaque(true);
panel.setBackground(Color.BLACK);
frame.add(panel);
frame.setVisible(true);
}
public static void main(String[] args)
{
BarnsleyFernRunner runner = new BarnsleyFernRunner();
}
}
The "major" issue is, you're breaking the paint chain requirements...
public void paintComponent(Graphics window)
{
Graphics2D g2d = (Graphics2D)window;
g2d.translate(360,800);
fern(window);
}
The super implementation of paintComponent does something, something important, unless you're prepared to take over that responsibility, you should make sure you are calling super.paintComponent first
You issue isn't helped by...
frame.setSize(800,800);
frame.setLocation(300,0);
frame.setResizable(false);
frame.setLayout(null);
panel.setSize(800,800);
panel.setOpaque(true);
You should keep the default BorderLayout, it will make your life a lot simpler.
Simply by updating the BarnsleyFern to override the getPreferredSize you gain a much more flexible solution. This means you pack the window and the available content size will be the preferred size of the contents, as apposed to the size of the window MINUS the frame decorations.
public class BarnsleyFern extends JPanel {
//...
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
Based upon...
ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent event) {
repaint();
}
};
Timer timer = new Timer(100, action);
//timer.start();
I suspect you're hoping to get a compounding paint, where the x/y properties are updated on each paint cycle.
Well, that's not going to work, for a number of reasons. You've found one. Graphics is a shared resource, every component painted in a given paint cycle will use the same Graphics instance, this means you could end up with dirty paints (and as a side effect, the result you seem to be looking for), but as you've discovered, it doesn't always work.
Painting can also occur for any number of reasons, many without you control or knowledge.
Painting should paint the current state, it shouldn't modify it. This is what your Timer should be doing.
You need to devise a model which will allow a incremental change each time the Timer ticks, the component will then use the model to simply paint the current state
I would recommend reading:
Performing Custom Painting
Painting in AWT and Swing
Laying Out Components Within a Container
for more details

The Animation is not showing

I'm trying this very simple code. It runs but doesn't show the animation. I'm new to animations, so I don't know what I'm missing.
package sample;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Sample extends JPanel implements ActionListener {
Timer tm = new Timer(5, this);
int x = 0, Velx = 5;
public void paint(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 50);
tm.start();
}
public void actionPerformed(ActionEvent e) {
x = x + Velx;
repaint();
}
public static void main(String[] args) {
Sample X = new Sample();
JFrame a = new JFrame();
a.setTitle("Rectangle RED");
a.setSize(500,500);
a.setVisible(true);
}
}
Sample X = new Sample();
X is never added to the frame. See first tip (the bold part) for how to add X to the frame.
Other tips:
Sample should #Override the getPreferredSize() method to return a sensible size for the canvas. Then we can dispense with a.setSize(500,500); and instead a.add(X); a.pack(); to get the frame to be the exact right size to display the rendering.
The Timer should be started in some place other than the paint methods! I'd go for the constructor.
Custom painting in any JComponent should be done in the paintComponent(Graphics) method.
In all custom painting, we should immediately call the super method to ensure that previous drawings are erased by painting the BG and border of the container.
Please learn common Java nomenclature (naming conventions - e.g. EachWordUpperCaseClass, firstWordLowerCaseMethod(), firstWordLowerCaseAttribute unless it is an UPPER_CASE_CONSTANT) and use it consistently.
JFrame a = new JFrame(); a.setTitle("Rectangle RED"); could be shortened to JFrame a = new JFrame("Rectangle RED");

Swing Component (re)paint mechanism fails to properly draw, when requests are too fast

From Java's documentation it says that the repaint mechanism of a Component is optimized and cached, so that even if it is called a lot of times, it won't clog the drawing pipeline.
It seems though that under really heavy invocation, sometimes it fails to draw the latest frames. Consider the following example:
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class TestRepaint extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setContentPane(new TestRepaint());
frame.setSize(300, 100);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private long start;
{
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
start = System.currentTimeMillis();
System.out.println("Repaint");
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
long now = System.currentTimeMillis();
int where = (int) ((now - start) / 5);
if (where >= 200)
g.drawString("DONE", 200, 30);
else {
g.drawString("Working...", where, 30);
repaint();
}
}
}
When mouse clicked on the window, then a "Working..." text will scroll to the right. On the last frame it is supposed to change into "DONE". It seems that (randomly) it fails to draw the last frame and the previous "Working..." text remains. If for example we force redraw the display (i.e. change window size), then "DONE" appears.
It seems that, since the repaint is requested too fast, the last repaint of the component fails to happen. So, is there any guaranteed way to do this?

Why does this this Swing bug occur when using repaint() and not with getParent().repaint()?

This question is based on a problem I had a while back with a simple Swing dice program. The original question I posted is here and has an accepted answer, but I'd like to know exactly what is happening, why the problem occurs, and why the solution works.
I managed to whittle down the original code to get to the core of the problem and it now looks very different:
I have two ColorPanels that each draw a coloured square
when you click on a panel the box should change colour in this order: start at black, then >red>green>blue>red>green>blue> etc
once a box has changed colour it should never be black again
However when I just call repaint() in the MouseListener, the program behaves very strangely:
I click on one panel and the square's colour changes
I then click on the other and it's square changes colour, but the first square also changes, back to black
you can see this behaviour in the gif below:
If you use getParent().repaint() instead this behaviour goes away and the program behaves as expected:
The problem only seems to occur if the panels/squares start off 'overlapping'.
If you use a layout that stops this or don't set the size small then the problem does not seem to occur.
the problem doesn't happen every time which initially made me think that concurrency problems might be involved.
The code that I had problems with in my original question did not seem to cause problems for everybody and so my IDE, jdk etc might be relevant as well: Windows 7, Eclipse Kepler, jdk1.7.0_03
The code minus imports etc is as follows:
public class ColorPanelsWindow extends JFrame{
static class ColorPanel extends JPanel {
//color starts off black
//once it is changed should never be
//black again
private Color color = Color.BLACK;
ColorPanel(){
//add listener
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent arg0) {
color = rotateColor();
repaint();
//using getParent().repaint() instead of repaint() solves the problem
//getParent().repaint();
}
});
}
//rotates the color black/blue > red > green > blue
private Color rotateColor(){
if (color==Color.BLACK || color == Color.BLUE)
return Color.RED;
if (color==Color.RED)
return Color.GREEN;
else return Color.BLUE;
}
#Override
public void paintComponent(Graphics g){
g.setColor(color);
g.fillRect(0, 0, 100, 100);
}
}
ColorPanelsWindow(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(1,0));
add(new ColorPanel());
add(new ColorPanel());
//the size must be set so that the window is too small
// and the two ColorPanels are overlapping
setSize(40, 40);
// setSize(300, 200);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
new ColorPanelsWindow();
}
});
}
}
So my question is, what on earth is going on here?
but I'd like to know exactly what is happening,
I see the same problems using JDK7u60 on Windows 7. Definitely seems like a bug to me.
My best guess is that it is a problem with the double buffering.
I added so debug code to the paintComponent() method.
1) When you click on the right component only its paintComponent() method is called and the panel is painted the proper color.
2) When you click on the left component only its paintComponent() method is called and the panel is painted the proper color, however the panel on the right reverts back to the black color, without invoking the paintComonent() method on the right panel. This leads me to believe that somehow an old buffer is being used (this would be the bug and I have no idea how to fix it).
The reason that getParent().repaint() works is because this forces both components to be repainted no matter which panel you click on.
I'm not sure the cause of your problem, since I cannot reproduce the misbehavior with your code, but your paintComponent override neglects to call the super's paintComponent method. Put that in and see what happens.
#Override // method should be protected, not public
protected void paintComponent(Graphics g) {
// ******* add
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, 100, 100);
}
Note that if this were my program, and this were the only painting behavior I desired, I'd simplify my program my not overriding paintComponent but rather simply calling setBackground(color); I'd also create a Color array or List and iterate through it:
public static final Color[] COLORS = {Color.RED, Color.Blue, Color.Green};
private int colorIndex = 0;
// .....
#Override
public void mousePressed(MouseEvent mEvt) {
colorIndex++;
colorIndex %= COLORS.length;
color = COLORS[colorIndex];
setBackground(color);
}
I would also override the getPreferredSize method.
e.g.,
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyColorsPanelDemo extends JPanel {
private static final int GAP = 20;
public MyColorsPanelDemo() {
setLayout(new GridLayout(1, 0, GAP, GAP));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
add(new ColorPanel());
add(new ColorPanel());
}
private static class ColorPanel extends JPanel {
public static final Color[] COLORS = {Color.red, Color.green, Color.blue};
private static final int PREF_W = 100;
private static final int PREF_H = PREF_W;
private static final Color INIT_BACKGROUND = Color.black;
private int colorIndex = 0;
public ColorPanel() {
setBackground(INIT_BACKGROUND);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
setBackground(COLORS[colorIndex]);
colorIndex++;
colorIndex %= COLORS.length;
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
private static void createAndShowGui() {
MyColorsPanelDemo mainPanel = new MyColorsPanelDemo();
JFrame frame = new JFrame("MyColorsPanelDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Java GUI: Making coordinates align properly

I am making a simple Java Swing GUI chessboard where the player can drag and drop pieces. The problem is that, because of the border around the frame (with the title on top, maximize/minimize/close buttons, etc), the coordinates are skewed off - (0, 0) is the upper-left-hand corner of the frame, that is, a little above the X button, but the GUI starts building itself right below the title bar, so the GUI doesn't align with the coordinates, and things do not end up working the way they should. Additionally, when I set the size of the frame to, for instance, 100 x 100, the lower part and some of the right-hand part of my GUI is cut off because the frame doesn't compensate for its border. When I run it as an applet, I don't have this problem, but I don't want to do that. How can I either get rid of that border around my frame window so I can just have the plain GUI, or have the coordinates set themselves up properly?
sscce:
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
public class class1 extends JFrame{
public class1(){
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent evt){
System.out.print(evt.getPoint());
}
});
}
public static void main(String[] args){
class1 c = new class1();
c.setTitle("Test");
c.setSize(320, 320);
c.setLocationRelativeTo(null);
c.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
c.setVisible(true);
}
}
It's hard to know what is wrong with your code without the code, but I do know that if you go the easy way by using various layout managers, and let these managers do the laying out of components for you and the sizing of things as well, including calling pack() on the JFrame, usually things fall easily and well into place. So again, don't set the size of anything, but rather let the components' preferred sizes and the layout managers do this for you.
If this advice doesn't help, please give us more information and code, preferably an sscce, a small compilable and runnable program that doesn't do anything other than demonstrate your problem.
Edit: I am assuming that this is a Swing GUI. Please verify if this is so.
Edit 2: One problem you're having is that you're setting the size of a JFrame not taking into account its "decorations" including the menu bar, the resize/maximize/close icon. Again, you shouldn't be setting sizes directly, but if you must better override the getPreferredSize() method of the JPanel that holds your grid.
Edit 3: For example:
import java.awt.*;
import javax.swing.*;
public class Grid extends JPanel {
public static final Color DARK_COLOR = Color.red.darker().darker().darker();
public static final Color LIGHT_COLOR = Color.lightGray.brighter();
public static final int SQUARE_SIDE = 60;
private static final int ROW_COUNT = 8;
#Override
public Dimension getPreferredSize() {
return new Dimension(ROW_COUNT * SQUARE_SIDE, ROW_COUNT * SQUARE_SIDE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < ROW_COUNT; i++) {
for (int j = 0; j < ROW_COUNT; j++) {
Color c = (i % 2 == j % 2) ? LIGHT_COLOR : DARK_COLOR;
g.setColor(c);
int x = i * SQUARE_SIDE;
int y = j * SQUARE_SIDE;
g.fillRect(x, y, SQUARE_SIDE, SQUARE_SIDE);
}
}
}
public Grid() {
// TODO Auto-generated constructor stub
}
private static void createAndShowGui() {
Grid mainPanel = new Grid();
JFrame frame = new JFrame("Grid");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Categories

Resources