在显示之前计算 JDialog 组件的大小并设置总大小

Posted

技术标签:

【中文标题】在显示之前计算 JDialog 组件的大小并设置总大小【英文标题】:Calculate sizes of Components of JDialog with total Size set before making visible 【发布时间】:2012-10-14 07:59:59 【问题描述】:

我有一个 JDialog,我希望它具有一定的给定大小:

JDialog dialog = new JDialog();
dialog.setSize(800, 600);
dialog.setResizable(false);

然后我添加一个组件:

JLabel label = new JLabel("Test");
dialog.add(label);

现在我可以使对话框可见并检查组件的大小

dialog.setVisible(true);
System.out.println(label.getSize());

答案是“[width=784,height=562]”。显然,该组件已调整大小以填充对话窗口的整个客户区/内容窗格。没关系,如我所愿。

问题:如何在调用 setVisible(true) 之前获取组件的最终尺寸?

getPreferredSize() 不会是我想要的大小,因为我希望组件适应给定对话框的大小 dialog.pack() 也不是正确的,因为它将对话框调整为我不想要的组件的首选大小 dialog.validate() 在这里没有任何用处,组件的大小仍然是 0 dialog.getLayout().layoutContainer(dialog) 也没有设置组件的大小

所以我在这里不知所措。我想让布局管理器在显示对话框之前计算所有组件和子组件的正确大小,以适应对话框的整体大小。但我不知道怎么做。

【问题讨论】:

在我的应用程序中,组件不是标签而是 JPanel,更具体地说是地理地图,其大小应适应对话框区域。该地图想在初始化阶段知道它的大小,以便知道显示了多少瓦片等等。在第一次 paintComponent 调用期间也可以稍后完成,但我认为这样更干净。有可能吗?如何或为什么不? 听起来像一个先有后裔的问题 :-) 你需要一些额外的逻辑来决定初始大小,比如 f.i.在给定多个图块和/或您希望对话框填充的屏幕相对区域的情况下,向地图询问其 prefSize。没有看到任何代码就很难分辨细节。 初始尺寸是从外部 (800x600) 给出的,setVisible(true) 在没有任何附加信息的情况下达到组件的所需尺寸。很可能我只是想做同样的事情,但没有显示部分。我只想用已知对话框大小的约束来布局我的所有组件。地图组件本身没有首选大小,但它可以获取任何东西,填充该区域,这是内容窗格的默认布局管理器所做的。我认为上面的代码对于描述问题来说是完整的。 不,您想要布局的最终​​尺寸 - 如果您觉得需要它,那么您的设置/预期有问题。 至少在设置对话框可见之后知道布局的最终​​大小。也许我可以找到解决方法甚至更好的解决方案,但首先我想知道这样是否可能?这个问题本身就是有价值的。 【参考方案1】:

我现在发现可以这样做:

JDialog dialog = new JDialog();
JLabel label = new JLabel("Test");
dialog.add(label);

// pack(), setSize(), validate() in this order will
// set sizes on all components as wished
dialog.pack();
dialog.setSize(800, 600);
dialog.validate();

System.out.println(label.getSize());

这里的输出也是“[width=784,height=562]”,但对话框尚不可见。重要的部分是按此顺序组合 pack()、setSize(desiredSize) 和 validate()。 pack() 可能确定对话框的新大小(所有组件的首选大小),这就是为什么这里必须在之后设置大小,而 validate() 负责调整组件的大小。可能达到相同大小的 setVisible(true) 在内部做类似的事情。

多次调整组件大小似乎有点浪费,但没有 pack() 也 setSize() 和 validate() 没有任何效果。

我猜其他答案是基于一些误解,因为他们总是隐含地假设您想要拥有首选尺寸,但也有一些情况,例如如果用户调整了对话框的大小,或者如果对话框的大小从一开始就固定,您无法获得首选大小,并且某些组件只需要填充可用空间。

这就是这里的布局问题,给定对话框的全局大小,并在组件填充可用空间时确定组件的大小。 LayoutManagers 很好地解决了这个问题,但通常只在 setVisible(true) 之后。

我测试了更多:

    // new dialog
    JDialog dialog = new JDialog();

    // new label, prints messages if resized or painted
    JLabel label = new JLabel("Test") 
        @Override
        protected void paintComponent(Graphics g) 
            super.paintComponent(g);
            System.out.println("Component painted.");
        
    ;
    label.addComponentListener(new ComponentAdapter() 
        @Override
        public void componentResized(ComponentEvent e) 
            System.out.println("Resized: " + e.getComponent().getSize());
        
    );
    dialog.add(label);        
    System.out.println("Size after new JLabel: " + label.getSize());

    // pack dialog - necessary for setSize/validate to work
    dialog.pack();
    System.out.println("Size after pack: " + label.getSize());                

    // set a size and validate changes sizes
    dialog.setSize(800, 600);
    dialog.validate();
    System.out.println("Size after setSize and validate: " + label.getSize());        

    // set visible would have also done the trick
    dialog.setVisible(true);
    System.out.println("Size after setVisible(true): " + label.getSize());

    // and another resizing (no validation neccessary)
    dialog.setSize(300, 200);

    // dispose
    dialog.dispose();

输出是

新 JLabel 后的大小:java.awt.Dimension[width=0,height=0] 打包后的尺寸:java.awt.Dimension[width=116,height=16] setSize 和验证后的大小:java.awt.Dimension[width=784,height=562] setVisible(true) 后的大小:java.awt.Dimension[width=784,height=562] 调整大小:java.awt.Dimension[width=284,height=162] 调整大小:java.awt.Dimension[width=284,height=162] 绘制的组件。

我更多地了解了 Swing 的内部工作原理:

ComponentResized 事件不会在 setVisible(true) 之前触发,即使组件已调整大小(它们的大小发生变化) 即使大小相同的 ComponentResized 事件也可以连续触发多次 如果组件彼此跟随的速度足够快,则在调整大小之间可能不会绘制组件 无论如何,第一次绘制都在 setVisible(true) 之后,届时组件将具有所需的大小(首选大小或由此处的其他约束定义)。 如果由于某种原因您必须在第一次绘制之前知道组件的大小,请使用 pack()、setSize()、validate() 来完成

我测试了更多,也使用最大化的帧,现在可以将所有结果组合成:第一个 painComponent() 总是具有正确的大小,并且相关的 componentResized() 事件总是在之后发生,有时是两次。但是 LayoutManager必须事先知道,否则示例将无法正确绘制。所以如果一个人自己绘制背景,要么在每个paintComponent中读出正确的大小,要么实现一个自定义布局管理器,或者等待resized事件并调用repaint,所以组件被绘制了两次,但它应该可以工作。应用包括要显示的组件数量取决于大小的情况(如在我的地理地图应用程序中)。


为了完成图片,如果用户最大化或调整框架/对话框的大小,我认为流程是这样的:

frame/dialog.setSize() LayoutManager.layoutContainer(frame/dialog) 使用实际大小 使用布局尺寸的框架/对话框paint() 为所有组件等触发 Resized() 事件。

而 pack() 可能只是调用 setSize(layout.preferredLayoutSize()) 作为第一步。

因此,如果您必须根据大小添加或删除组件,例如,覆盖 setSize() 并在那里侦听更改可能是个好主意。我最初是在监听 Resized() 事件,但它们到达第一次绘图时为时已晚。

【讨论】:

【参考方案2】:

Top-Level Containers 在两种情况下返回自己的SizePreferredSizeBounds(如果是的话)

已经可见

在致电pack() 后,在您的情况下为dialog.pack();

必须用Borders计算,ToolBar来自Native OS

必须从Top-Level Containers#getContentPane().getWhatever得到这个尺寸

大部分JComponents 返回自己的PreferredSize,那么就没有理由为Standard Layout Manager 调整大小

【讨论】:

你的回答表明,如果不设置对话框可见,上面的任务是不可能的,对吧?例如 dialog.pack() 和 label.getSize() 标签的大小是“[width=126,height=16]”,标签的首选大小,与设置可见后不同。 是可能的,在所有情况下您都可以设置PreferredSize,然后调用pack(),没问题,但我描述了JTextField("some desc",15)或JTextArea(10, 15),e.i. 如果不将对话框设置为可见,则无法执行上述任务不,这只是 @mKorbel 的幽默 ;-) 你不想要实际大小,你想要首选大小.你的地图应该返回一些有用的东西...... JDialog(JFrame)实现了BorderLayout,然后调用pack()后收缩到以像素为单位的图形值,从JLabel中的String值返回 组件没有有意义的首选大小。唯一有意义的值是对话框的总大小,组件应该适应这个,这似乎是一个定义明确的布局问题。 Setvisible(true) 完成了这项工作,但做得很多。问题是是否有替代方案。

以上是关于在显示之前计算 JDialog 组件的大小并设置总大小的主要内容,如果未能解决你的问题,请参考以下文章

如何限制 JDialog 的尺寸

在 JDialog 上摆动 wait() 和 notify(),对话框不显示其组件

如何设置可见以隐藏设置为可见的 jDialog(在 if 条件内)显示(在 if 循环之外)?

计算多个文件夹的文件夹大小[关闭]

怎么让jdialog显示出来

制作一个显示“请稍候”JDialog 的摆动线程