views:

337

answers:

8

I'm having a problem that when my frame is shown (after a login dialog) the buttons are not on correct position, then in some miliseconds they go to the right position (the center of the panel with border layout).

-- update

In my machine, this SSCCE shows the layout problem in 2 of 10 times I run it:

import java.awt.BorderLayout;
import java.awt.Dimension;

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

public class TEST {

    public static void main(String[] args) throws Exception {

    SwingUtilities.invokeAndWait(new Runnable() {

        public void run() {

        System.out.println("Debug test...");

        JPanel btnPnl = new JPanel();
        btnPnl.add(new JButton("TEST"));

        JFrame f = new JFrame("TEST");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(btnPnl);
        f.setPreferredSize(new Dimension(800, 600));
        f.pack();
        f.setExtendedState(JFrame.MAXIMIZED_BOTH);
        f.setVisible(true);

        System.out.println("End debug test!");

        }
    });

    }

}

The button first appers in the up-left, and then it goes to the center. Is it a java bug?

--update

Looks like the SSCCE don't show the problem for everyone that is trying. Maybe it's my computer performance problem. I still think Java Swing is creating new threads for make the layout behind the scenes. But I'm not sure.

--update

The problem only occur with the f.setExtendedState(JFrame.MAXIMIZED_BOTH);

+1  A: 

I would guess that you need to call pack() before making your frame visible.

If you are calling the above code not on the event thread then you have a race condition and all bets are off - you can only manipulate the GUI from the EDT (event dispatch thread).


EDIT: I tried your SSCCE on my system and it is not exhibiting the behavior you are seeing. I tried it about 50 times, and also tried creating 10 windows by looping your code. I am running 1.6.0_18 on Windows XP SP3.

Software Monkey
The pack() didn't resolve, and I'm calling everything from the EDT.Please, see my update.
Tom Brito
Question updated. Thanks for post your results on running the SSCCE.
Tom Brito
+1  A: 

Maybe you are missing a frameThatContainsCentralPanel.pack()?

Jack
No, it didn't solve.And please, see my update.
Tom Brito
Just ran it like 40 times and I've got no bugs, my java version is **1.6.0 update 17**.Did you try specifying where you want your button? I mean **getContentPane().add(button, BorderLayout.NORTH);** for example. Without any specifier it should be placed as **BorderLayout.CENTER** but who knows..
Jack
Question updated. Thanks for post your results on running the SSCCE.
Tom Brito
+1  A: 

Well, if it works with a SSCCE, then you've proven the problem isn't with the basic logic. There must be something different between the SSCCE and your real code. Since we don't have access to your real code you need to do the debugging yourself to see what the difference is.

However, in this case a better solution is to use a CardLayout, which is designed to let you swap panels easily. Read the Swing tutorial for a working example.

Or anther approach is to use a "login dialog". Once the login is successfull, you display your main frame with the panel for your application.

camickr
Please, see my update. 2 in 10 times I run this SSCCE, the fail is shown. Even if the CardLayout solves the problem, is this a BorderLayout bug? It occurs with GridBagLayout either.
Tom Brito
I'm more interested in know what is happening then in fix it (its not a important bug to fix).
Tom Brito
Which is why you where asked for the SSCCE. Since its take you 3 hours to post a SSCCE how do you exect us to suggest a fix, confirm it we have the same problem.... Thats why a proper SSCCE should be incleded with every question. I see no problem. I'm using JDK6_07 on XP.
camickr
Question updated. Thanks for post your results on running the SSCCE.
Tom Brito
A: 

Hi,

If I copied your code, I had the same problem, but not so heavy.

I solved it by setting a preferred size for your frame before packing. So:

import java.awt.Dimension;

System.out.println("Debug test...");

JPanel btnPnl = new JPanel();
btnPnl.add(new JButton("TEST"));

JFrame f = new JFrame("TEST");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(btnPnl);
f.setPreferredSize(new Dimension(800, 600));
f.pack();
f.setVisible(true);
System.out.println("End debug test!");

I'm running on Linux.

It is indeed strange... I'm sure it is something about the size of all the containers in the swing tree.

Martijn Courteaux
works nice on the example code, but not on my main app.. I'm wondering why..? :(
Tom Brito
I'm trying to update the example code to behave like my app (or vice versa, would be even better). I back here if I have news.
Tom Brito
question updated:now the problem only occur with the f.setExtendedState(JFrame.MAXIMIZED_BOTH);
Tom Brito
+2  A: 

Your problem intrigued me. After some investigation I think I confirmed something that I recall about setting the window state (maximized, restored, etc) which is that setting the state is a request to the operating system and is left to the whim of the OS to process the request. This means it is asynchronous, or at least done later, after you set it. I confirmed using logging and adding resize listeners where you can see that the frame is resized after your block of code exits. Because of this, the pack() will layout components to their preferred size. So imagine the frame being sized to 800x600 and components positioned as such (button centered horizontally around 400). Then later, the OS changes the size of the frame to full screen (e.g. 1024x768) - for a moment, you'll see the button still at 400. Then the frame processes the new size and re-lays out components and centers the button at around 512. So you'll see the flicker as it transitions during this process. Perhaps a solution is to NOT pack() - it will remain at a size of zero and user will see minimum flicker.

Try this change first:

// pack()

If that looks good then you might have the next problem...if the user clicks the restore button, the whole frame shrinks into a black hole. So try calling pack AFTER the frame has been predictably resized due to the maximize. Something like this:

f.addComponentListener( new ComponentAdapter( ComponentEvent e ) {
  public void componentResized( Component) {
     if( f.getSize().getWidth() > 0 ) {
        e.getComponent().removeComponentListener( this );
        ((JFrame)e.getComponent()).pack();
     }
  }
}

So if the user later clicks restore button the frame will have a nicely packed size ready to go.

--Update

OK, one last attempt. While I think my description of the problem has some truth, my solutions offered did nothing. Here's one last attempt. Remove pack() and setPreferredSize() and replace with setting the size to the screen size. This seems to reduce the flicker greatly on my system. This is because there should be no difference between the initial layout and the maximized layout done later. You can see this if you switch between restore and maximized. Although I still see a very slight flicker when switching the two, at least it seems to look better when first displayed.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;

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

public class TEST {

    public static void main(String[] args) throws Exception {

    SwingUtilities.invokeAndWait(new Runnable() {

        public void run() {

        System.out.println("Debug test...");

        JPanel btnPnl = new JPanel();
        btnPnl.add(new JButton("TEST"));

        JFrame f = new JFrame("TEST");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(btnPnl);
//        f.setPreferredSize(new Dimension(800, 600));
//        f.pack();
        f.setSize( Toolkit.getDefaultToolkit().getScreenSize() );
        f.setExtendedState(JFrame.MAXIMIZED_BOTH);
        f.setVisible(true);

        System.out.println("End debug test!");

        }
    });

-Mike

staticman
Thanks for the interest!Just removing the pack() makes the frame be a restored size (zero width and high if I don't set it). And the component listener does not change the current behavior. I'm thinking it is a more deep java bug.
Tom Brito
A: 

I would expect the frame to be maximised both before it is shown, but after checking this I'm sure that on linux frame is maximised after it has been displayed. You can make the frame size equal to a Screen size before calling setVisible, or you can make the components invisible until you know that the it's got preferred initial size. Here is modified sample which shows the elements after the frame has been activated (on linux activated event comes late enough to not show the "jumping button"):

import javax.swing.JButton;

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

public class TEST {

public static void main(String[] args) throws Exception {

    SwingUtilities.invokeAndWait(new Runnable() {

        public void run() {

            final JPanel btnPnl = new JPanel();
            btnPnl.add(new JButton("TEST"));

            final JFrame f = new JFrame("TEST");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setContentPane(btnPnl);
            // calculate preferred size for TEST frame
            // f.isDisplayable() will become true
            f.pack();

            // extended state, if can be applied, needs to be called after f.isDisplayable()
            WindowListener maxBoth = new WindowAdapter() {

                @Override
                public void windowOpened(WindowEvent e) {
                    f.setExtendedState(Frame.MAXIMIZED_BOTH);
                }
            };

            // after windows has been opened - maximize both
            f.addWindowListener(maxBoth);

            // initially hide the elements
            // after maximized state has been applied show them
            f.getContentPane().setVisible(false);
            f.addWindowListener(new WindowAdapter() {

                @Override
                public void windowActivated(WindowEvent e) {
                    f.getContentPane().setVisible(true);
                    // remove this listener
                    f.removeWindowStateListener(this);
                }
            });

            // set the frame visible
            f.setVisible(true);
        }
    });
}

}

Krzysztof
Looks better, but still not as clean as a frame should be. I'm still seeing the button give a little jump.
Tom Brito
A: 

The "then in some milliseconds" part sounds to me like you need to call validate() on your frame. Also, if you use f.pack(), your panel needs a preferred size, because pack() gives the parent's components their preferred sizes and resizes based on them.

Will P
A: 

Looks like a java bug. I've reported it (but for some reason it still not show on the bugs reports).

Tom Brito