Swing:正确的布局管理器与上层布局管理器的交互?

问题描述

| 我正在尝试使用自定义LayoutManager,但我不明白在本身具有上级布局管理器的组件中使用它的微妙之处。 下面是一个测试程序,该程序使用一对JPanels制作两个框架。两个JPanel中的每个都有一个细的黑色边框,并使用我的WeirdGridLayout强制其子组件变成网格中的正方形,而JPanel的高度是根据宽度计算的。这两个JPanel都位于另一个带有使用BorderLayout的红色细边框的JPanel中。 在一个框架中,具有WeirdGridLayout的JPanels排列为EAST和WEST,另一个 排列南北。 问题是在南北情况下,如果我更改框架的宽度/高度, 具有WeirdGridLayout的两个JPanel具有正确的大小,但位置却不正确(它们之间有间隙或垂直重叠)。 在东西方的情况下,它最终以错误告终。 我该怎么做才能使布局管理器与外部布局管理器一起正常工作?
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.LayoutManager2;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 */
public class WeirdGridLayout implements LayoutManager2
{


    static final private int GRIDGAP = 10; 
    static final private int COMPONENT_SIZE = 30;
    static final private int GRIDSPACING = COMPONENT_SIZE + GRIDGAP;

    final private List<Component> components
        = new ArrayList<Component>();

    @Override public void addLayoutComponent(Component comp,Object constraints) {
        this.components.add(comp);
    }
    @Override public void addLayoutComponent(String name,Component comp) {
        this.components.add(comp);
    }
    @Override public void removeLayoutComponent(Component comp) {
        this.components.remove(comp);
    }   

    @Override public float getLayoutAlignmentX(Container target) {
        return Component.LEFT_ALIGNMENT;
    }
    @Override public float getLayoutAlignmentY(Container target) {
        return Component.TOP_ALIGNMENT;
    }

    @Override public void invalidateLayout(Container target) {}
    @Override public Dimension maximumLayoutSize(Container target) {
        return new Dimension(Integer.MAX_VALUE,Integer.MAX_VALUE);
    }
    @Override public Dimension minimumLayoutSize(Container parent) {
        return new Dimension(0,0);
    }

    @Override public void layoutContainer(Container parent) {
        int x = GRIDGAP;
        int y = GRIDGAP;

        Dimension d = preferredLayoutSize(parent);
        parent.setSize(d);
        for (Component component : this.components)
        {
            component.setBounds(x,y,COMPONENT_SIZE,COMPONENT_SIZE);

            x += GRIDSPACING;
            if (x >= d.getWidth())
            {
                x = GRIDGAP;
                y += GRIDSPACING;
            }
        }
    }

    @Override public Dimension preferredLayoutSize(Container parent) {
        // how many blocks wide can we fit?
        int n = this.components.size();
        int nblockwidth = (parent.getWidth() - GRIDGAP) / GRIDSPACING;
        int nblockheight = (nblockwidth == 0) ? 0 
                : ((n-1)/nblockwidth) + 1;      
        return new Dimension(
                nblockwidth*GRIDSPACING+GRIDGAP,nblockheight*GRIDSPACING+GRIDGAP);
    }

    /* ---- test methods ---- */

    static public class ColorPanel extends JPanel {
        final private Color color;
        final private String label;
        public ColorPanel(String label,Color color) { 
            this.label = label;
            this.color = color; 
        }
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(this.color);
            g.fillRect(0,getWidth(),getHeight());
            g.setColor(Color.WHITE);
            FontMetrics fm = g.getFontMetrics();
            int w = fm.stringWidth(this.label);
            g.drawString(this.label,(getWidth()-w)/2,(getHeight()+fm.getAscent())/2);
        } 
    }

    public static void main(String[] args) {
        showFrame(true);
        showFrame(false);
    }
    private static void showFrame(boolean eastWest) {
        JFrame frame = new JFrame(\"WeirdGridLayout test: eastWest=\"+eastWest);
        JPanel framePanel = new JPanel(new BorderLayout());
        framePanel.setPreferredSize(new Dimension(400,200));

        JPanel panel[] = new JPanel[2];
        for (int i = 0; i < 2; ++i)
        {
            panel[i] = new JPanel(new WeirdGridLayout());
            panel[i].setBorder(BorderFactory.createLineBorder(Color.BLACK));
            final Random r = new Random();
            for (int j = 0; j < 24; ++j)
            {
                Color c = new Color(
                            r.nextFloat(),r.nextFloat(),r.nextFloat());
                JPanel subpanel = new ColorPanel(Integer.toString(j),c);
                panel[i].add(subpanel);
            }
        }

        framePanel.add(new JButton(\"test\"),BorderLayout.norTH);
        JPanel bottomPanel = new JPanel(new BorderLayout());
        framePanel.add(bottomPanel,BorderLayout.soUTH);

        if (eastWest)
        {
            bottomPanel.add(panel[0],BorderLayout.WEST);
            bottomPanel.add(panel[1],BorderLayout.EAST);
        }
        else
        {
            bottomPanel.add(panel[0],BorderLayout.norTH);
            bottomPanel.add(panel[1],BorderLayout.soUTH);          
        }
        bottomPanel.setBorder(BorderFactory.createLineBorder(Color.RED));


        frame.setContentPane(framePanel);
        frame.pack();
        frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
    

解决方法

        回答主题:无:-) LayoutManager并非设计用于与其他LayoutManager进行交互,它们独立于它们负责的目标起作用。 LayoutManager负责确定容器子级的大小和位置,而不是容器本身。因此,WeirdLayoutManager在将父级的大小设置为其首选项方面行为不当。 preferredLayoutSize必须始终返回合理的值:将其视为一种分离的东西,例如“亲爱的容器,鉴于您拥有世界上所有的空间,那么您究竟希望拥有什么大小”,反之亦然:don \不要依靠父母的大小来回答这个问题。这就像一条狗试图咬自己的尾巴。对于类似网格的结构,可能需要某种prefColumns / -Rows属性 layoutContainer必须将直接子级的大小和位置放置在容器的当前边界内,不要触摸容器本身。它可以按照自己喜欢的任何方式进行操作,可以根据需要在任意数量的行/列中进行操作     ,        关键是要了解顶层布局管理器在做什么,并确保下层具有首选的大小或其他属性集,以使其能够正确呈现。 从API(http://download.oracle.com/javase/7/docs/api/java/awt/BorderLayout.html): NORTH和SOUTH分量可以水平拉伸; EAST和WEST分量可以垂直拉伸; CENTER组件可以水平和垂直拉伸以填充剩余的任何空间。