【问题标题】:JPanel's child components paint/layout problemJPanel 的子组件绘制/布局问题
【发布时间】:2010-03-10 17:15:18
【问题描述】:

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

--更新

在我的机器上,这个 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的bug吗?

--更新

看起来 SSCCE 并没有向所有正在尝试的人显示问题。 可能是我的电脑性能问题。我仍然认为 Java Swing 正在创建新线程以在幕后进行布局。但我不确定。

--更新

这个问题只发生在 f.setExtendedState(JFrame.MAXIMIZED_BOTH);

【问题讨论】:

  • 顺便说一下,Swing 教程中的所有示例都使用了 invokeLater,而不是 invokeAndWait。虽然我怀疑这是问题所在,但我只是想知道为什么您不遵循示例中的代码,您不太可能遇到问题。
  • @camickr:有趣的是,invokeAndWait 似乎没有在等待。我修改了他的 SSCCE 以循环整个主窗口,它一次打开多个窗口。
  • 可能在 Swing 代码中它正在创建新线程..
  • 您在什么 JVM 和平台上看到了这种行为?
  • @Devon_C_Miler 在 Windows 和 Ubuntu 的最后一个 JVM 上。

标签: java user-interface swing layout rendering


【解决方案1】:

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

先试试这个改变:

// pack()

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

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

因此,如果用户稍后单击恢复按钮,框架将准备好打包好的尺寸。

--更新

好的,最后一次尝试。虽然我认为我对问题的描述有一定的道理,但我提供的解决方案并没有起到任何作用。这是最后一次尝试。删除 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!");

        }
    });

-迈克

【讨论】:

  • 感谢您的关注!只需删除 pack() 即可使框架恢复大小(如果我不设置,则为零宽度和高度)。并且组件监听器不会改变当前的行为。我认为这是一个更深的 java 错误。
【解决方案2】:

也许您缺少frameThatContainsCentralPanel.pack()

【讨论】:

  • 只运行了 40 次,我没有任何错误,我的 java 版本是 1.6.0 update 17。您是否尝试指定您想要按钮的位置?我的意思是 getContentPane().add(button, BorderLayout.NORTH); 例如。没有任何说明符,它应该被放置为 BorderLayout.CENTER 但谁知道..
  • 问题已更新。感谢您发布运行 SSCCE 的结果。
【解决方案3】:

好吧,如果它适用于 SSCCE,那么您已经证明问题不在于基本逻辑。 SSCCE 和你的真实代码之间一定有什么不同。由于我们无法访问您的真实代码,您需要自己进行调试,看看有什么不同。

但是,在这种情况下,更好的解决方案是使用 CardLayout,它旨在让您轻松交换面板。阅读 Swing 教程以获取工作示例。

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

【讨论】:

  • 请看我的更新。我运行此 SSCCE 的 10 次中有 2 次显示失败。即使 CardLayout 解决了问题,这是否是 BorderLayout 的错误?它也发生在 GridBagLayout 中。
  • 我更感兴趣的是知道发生了什么然后修复它(它不是一个需要修复的重要错误)。
  • 这就是您要求 SSCCE 的原因。由于您需要 3 个小时来发布 SSCCE,您希望我们如何提出修复建议,请确认我们有同样的问题……这就是为什么每个问题都应该包含正确的 SSCCE。我看没问题。我在 XP 上使用 JDK6_07。
  • 问题已更新。感谢您发布运行 SSCCE 的结果。
【解决方案4】:

我猜你需要先调用 pack() 才能让你的框架可见。

如果您在事件线程上调用上述代码不是,那么您有一个竞争条件并且所有赌注都没有 - 您只能从 EDT(事件调度线程)操作 GUI。


编辑:我在我的系统上尝试了您的 SSCCE,但它没有表现出您所看到的行为。我尝试了大约 50 次,还尝试通过循环代码创建 10 个窗口。我在 Windows XP SP3 上运行 1.6.0_18。

【讨论】:

  • pack() 没有解析,我正在调用 EDT 中的所有内容。请看我的更新。
  • 问题已更新。感谢您发布运行 SSCCE 的结果。
【解决方案5】:

“然后在几毫秒内”对我来说听起来像是您需要在框架上调用 validate()。此外,如果您使用f.pack(),您的面板需要一个首选尺寸,因为pack() 为父组件提供了首选尺寸并根据它们调整大小。

【讨论】:

    【解决方案6】:

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

    我通过在打包前为您的框架设置首选尺寸解决了这个问题。所以:

    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 上运行。

    确实很奇怪……我敢肯定它的大小与秋千树中所有容器的大小差不多。

    【讨论】:

    • 在示例代码上效果很好,但在我的主应用程序上却不行。我想知道为什么..? :(
    • 我正在尝试更新示例代码以使其表现得像我的应用程序(反之亦然,会更好)。有消息我会回来的。
    • 问题更新:现在问题只发生在 f.setExtendedState(JFrame.MAXIMIZED_BOTH);
    【解决方案7】:

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

    import javax.swing.JButton;
    

    导入 javax.swing.JFrame; 导入 javax.swing.JPanel; 导入 javax.swing.SwingUtilities;

    公共类测试{

    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】:

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

    【讨论】:

      猜你喜欢
      • 2016-07-31
      • 2021-09-11
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多