Java / AWT / Swing:关于验证和大小

时间:2011-01-12 15:21:05

标签: java swing layout awt

我对如何在Java中管理组件的布局感到困惑(我想手动完成它而不是由布局管理器处理它)。 Component中有这些方法:

  • layoutdoLayout
  • validateinvalidaterevalidate
  • validateTreeinvalidateTree
  • setSizesetBoundssetPreferredSize
  • getSizegetBoundsgetPreferredSize
  • paintrepaintupdate
  • updateUI

早些时候,我曾尝试重载上述的各种组合,但我不太确定哪一个重载,我必须在内部做什么以及我必须在子组件上调用哪些函数。


我现在正在做的是:

  • 仅重载上述内容doLayout
  • doLayout中,对于所有子组件:
    • 致电child.doLayout
    • 致电child.setBounds(有时在child.setBounds之前,有时在之后,有时两者之后)。
  • doLayout中,因为我正在进行布局,所以我自动计算了它的首选尺寸。
  • doLayout中,请致电this.setPreferredSize
  • 在所有构造函数中,请致电:this.setLayout(null)
  • 在某些构造函数中,调用:this.doLayout。 (如果我不这样做,它就无法正确显示。)
  • 当我做一些我必须重做布局的操作时(例如我动态地在某个容器中添加了一些文本字段,因此我想调整容器的大小以及相应的所有父级),我调用container.revalidate()

剩下的问题:

  • 我想我还没有真正明白什么功能在调用什么,我需要重载以及如何处理。
  • doLayout,我致电this.setPreferredSizedoLayout本身通常取决于this.getSize()。对于父级,child.setBounds通常取决于child.getPreferredSize()。所以我有一个困境,在某些情况下,我首先要调用child.doLayout然后调用child.setBounds,而在其他情况下反过来调用this.setPreferredSize。在某些情况下甚至更复杂。所以似乎我必须在其他地方调用setBounds。但是哪里?因为它总是必须在大小改变时更新(这就是为什么我之前已经超载JScrollPane,但那更难看。)
  • 我拥有viewportView.getPreferredSize()内的所有内容,根据revalidate设置滚动条。在我想要重新计算布局的情况下调用的doLayout会导致setPreferredSize调用,这些调用会正确地为层次结构中的所有组件/容器调用JScrollPane。但是,似乎doLayout在之前设置了滚动条 JScrollPane被调用,因此它总是错误的。我该如何解决这个问题?

关于我如何解决setPreferredSize问题的一些进一步的想法(请评论它们)(没有真正尝试过,因为它需要一些重大的重写,所以我想先问一下):

  • 移除doLayout中的所有getPreferredSize来电。
  • 重载doLayout并从那里调用revalidate(以获得首选尺寸)。

- 或 -

  • 当我执行需要重做布局的操作时,请拨打validateTree,而不是致电revalidate

- 或 -

  • 当我执行需要重做布局的操作时,不要拨打doLayout,而是手动拨打所有revalidate,然后JScrollPanel上的comp.width

最后,如何进行尺寸和首选尺寸的循环依赖?即,我经常遇到这种情况:

  • comp.height固定在根目录下。我可以在根上设置宽度,并递归地向下移动到所有子节点并设置其宽度。
  • setPreferredSize固定在最内在的孩子身上,取决于其宽度。因此,在设置了所有宽度之后,我可以自下而上地计算并设置高度。

在我没有致电setSize之前,我无法致电setSize。在我没有致电setPreferredSize之前,我无法致电{{1}}。

3 个答案:

答案 0 :(得分:3)

如果您使用LayoutManagers,则可以解决所有问题和疑问。你似乎在努力避免它们,但在布局GUI项目时它们确实是你们最好的盟友。

由于学习曲线,很多人一开始就试图避免使用LayoutManagers。我在学习它们时发现非常有用的是布局管理员的视觉指南:http://download.oracle.com/javase/tutorial/uiswing/layout/visual.html

我的建议是找到一种方法让你的布局与LayoutManagers一起使用。即使您解决了其中的一些问题,进行更改或其他人必须阅读您的代码也会非常耗时。此外,为了让它能够工作,您需要做的一切都是因为在一个组件上留下了一些东西而存在很多错误。

答案 1 :(得分:1)

恐怕你走错了路。

使用Stewart的答案并设置一个空布局管理器。这允许您通过调用setBounds()来设置所有组件的位置。

然后,在调整窗口大小时调用的窗口上添加一个监听器,然后重新计算位置并调用setBounds()。

如果您将定位代码实现为布局管理器,则所有这些都可以自动完成。 (毕竟,在resize ==布局管理中定位组件,所以不管你相信与否,你 开发布局管理器,就这么简单!)

public class MyLayout implements LayoutManager,LayoutManager2 {

    @Override
    public void addLayoutComponent(Component comp, Object constraints) {
        // TODO Auto-generated method stub

    }

    @Override
    public Dimension maximumLayoutSize(Container target) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public float getLayoutAlignmentX(Container target) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public float getLayoutAlignmentY(Container target) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void invalidateLayout(Container target) {
        // TODO Auto-generated method stub

    }

    @Override
    public void addLayoutComponent(String name, Component comp) {
        // TODO Auto-generated method stub

    }

    @Override
    public void removeLayoutComponent(Component comp) {
        // TODO Auto-generated method stub

    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Dimension minimumLayoutSize(Container parent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void layoutContainer(Container parent) {
        // Now call setBounds of your components here
    }

}

在layoutContainer方法中,您可以调用所有组件的setBounds。最初布局窗口时以及每次调整大小时都会调用此方法。

然后,当你例如把东西放到一个窗口或一个JPanel,只需setLayoutManager(新的MyLayoutManager()),你就是黄金。

然而,仍然存在一个非常粗糙的问题。您的布局管理器是一个单独的类,但它仍然必须访问您在窗口代码中的其他位置创建的组件。蛮力解决方案是简单地获取构造函数中所有组件的引用,例如:

class MyWindow extends JFrame {
   public MyWindow() { 
       JLabel label=new JLabel("Hello");
       JButton button=new JButton("Ok");

       setLayoutManager(new MyLayoutManager(label,button)); // PASS THEM
       add(label);
       add(button);
       pack();
       setVisible(true);
   }
}

当然,这是一种天真的方法,但可以奏效。处理此问题的正确方法是在布局管理器中实现addLayoutComponent,只要向JFrame添加任何内容(例如调用add(label)时)就会调用它。这样布局管理器就能够了解布局中的组件。

答案 2 :(得分:0)

阅读完评论后,您似乎只想使用空布局进行绝对定位。它上面有一个tutorial,但要点是你为容器设置布局管理器为null,然后在该容器中的每个组件上调用setBounds()来设置它的大小和位置,如下所示:

JPanel p = new JPanel();
p.setLayout(null);

JButton b = new JButton ("Hit It");
p.add(b);
b.setBounds(new Rectangle(10, 20, 100, 50));