代码之家  ›  专栏  ›  技术社区  ›  Tom Brito

JPanel的子组件绘制/布局问题

  •  4
  • Tom Brito  · 技术社区  · 6 年前

    我有一个问题,当我的框架显示时(登录对话框后),按钮不在正确的位置,然后在一些毫秒内,它们转到正确的位置(带边框布局的面板的中心)。

    ——更新

    在我的机器中,这个sscce在我运行它的10次中有2次显示布局问题:

    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!");
    
            }
        });
    
        }
    
    }
    

    按钮首先出现在左上角,然后转到中间。它是Java错误吗?

    ——更新

    看起来sscce并没有显示出每个人都在尝试的问题。 可能是我的电脑性能问题。我仍然认为JavaSwing正在创建新的线程来制作幕后的布局。但我不确定。

    ——更新

    问题只在f.setextendedstate(jframe.maximized_both)中出现;

    8 回复  |  直到 12 年前
        1
  •  2
  •   staticman    14 年前

    你的问题引起了我的兴趣。经过一些调查,我想我确认了一些我记得的关于设置窗口状态(最大化、还原等)的事情,即设置状态是对操作系统的请求,由操作系统的突发奇想来处理请求。这意味着它是异步的,或者至少是在设置之后完成的。我确认使用了日志记录和添加resize监听器,您可以在其中看到在代码块退出后调整了框架的大小。因此,pack()会将组件布局为其首选大小。所以想象一下框架的大小是800x600,组件的位置是这样的(按钮水平居中在400左右)。然后,操作系统将帧的大小更改为全屏(例如1024x768)-一会儿,您将看到按钮仍为400。然后,帧处理新的大小,重新布局组件,并将按钮居中在512左右。所以在这个过程中你会看到闪烁的转变。也许一个解决方案是不要pack()—它将保持0的大小,用户将看到最小的闪烁。

    请先尝试此更改:

    // pack()
    

    如果这看起来不错,那么你可能会遇到下一个问题……如果用户单击restore按钮,整个帧就会缩小成一个黑洞。因此,请尝试在帧由于最大化而可预测地调整大小后调用pack。像这样的:

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

    因此,如果用户稍后单击还原按钮,框架将有一个很好的包装尺寸准备去。

    ——更新

    好的,最后一次尝试。虽然我认为我对这个问题的描述有些道理,但我提供的解决方案却毫无用处。这是最后一次尝试。remove pack()和setPreferredSize()并替换为将大小设置为屏幕大小。这似乎大大减少了我的系统闪烁。这是因为初始布局和以后完成的最大化布局之间应该没有区别。如果在“还原”和“最大化”之间切换,可以看到这一点。虽然在切换这两个选项时,我仍然可以看到非常轻微的闪烁,但至少在第一次显示时看起来更好。

    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!");
    
            }
        });
    

    -迈克

        2
  •  1
  •   Jack    15 年前

    也许你错过了 frameThatContainsCentralPanel.pack() ?

        3
  •  1
  •   camickr    15 年前

    好吧,如果它与sscce一起工作,那么你已经证明了问题不在于基本逻辑。在sscce和你的真实代码之间一定有些不同。因为我们无法访问您的真实代码,所以您需要自己进行调试,看看有什么不同。

    不过,在这种情况下,更好的解决方案是使用CardLayout,它的设计是让您可以轻松交换面板。阅读swing教程以获得一个工作示例。

    或者另一种方法是使用“登录对话框”。登录成功后,您将显示带有应用程序面板的主框架。

        4
  •  1
  •   L. Cornelius Dol    15 年前

    我猜你需要先调用pack(),然后才能使你的帧可见。

    如果你要调用上面的代码 在事件线程上,然后您有一个竞争条件,并且所有的下注都是关闭的-您只能从edt(事件分派线程)操作gui。


    编辑:我在我的系统上试过你的sscce,但它没有显示你看到的行为。我试了大约50次,还试着通过循环代码创建10个窗口。我在windows xp sp3上运行1.6.0u18。

        5
  •  1
  •   Will P    12 年前

    “然后在几毫秒内”部分听起来像是你需要打电话给 validate() 在你的镜框上。另外,如果你使用 f.pack() ,您的面板需要首选大小,因为 pack() 为父组件提供其首选大小,并根据它们调整大小。

        6
  •  0
  •   Martijn Courteaux    14 年前

    如果我复制了你的代码,我也有同样的问题,但没有那么重。

    我在包装前给你的镜框定了一个合适的尺寸。所以:

    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!");
    

    我在Linux上运行。

    真奇怪…我敢肯定它和秋千树上所有的容器差不多大。

        7
  •  0
  •   Krzysztof    14 年前

    我希望在显示帧之前将其最大化,但在检查之后,我确信在linux上,在显示帧之后将其最大化。在调用setvisible之前,可以使框架大小等于屏幕大小,或者可以使组件不可见,直到知道它具有首选的初始大小。下面是修改后的示例,它显示了框架被激活后的元素(在Linux上,激活的事件来得太晚,以至于没有显示“跳转按钮”):

    import javax.swing.JButton;
    

    导入javax.swing.jframe; 导入javax.swing.jpanel; 导入javax.swing.swinguilities;

    公共类测试{

    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);
            }
        });
    }
    

    }

        8
  •  0
  •   Tom Brito    14 年前

    看起来像是一个Java错误。我已经报告过了(但出于某种原因,它仍然没有显示在bug报告中)。