【问题标题】:Swing: proper layout manager interaction with upper-level layout manager?Swing:与上层布局管理器进行适当的布局管理器交互?
【发布时间】:2011-09-14 03:55:00
【问题描述】:

我正在尝试自定义 LayoutManager,但我不明白在本身具有上层布局管理器的组件中使用它的微妙之处。

下面是一个测试程序,它用一对 JPanel 制作两个框架。两个 JPanel 中的每一个都有一个细黑色边框,并使用我的 WeirdGridLayout 将其子组件强制为网格中的正方形,JPanel 的高度是根据宽度计算的。两个 JPanel 都位于另一个 JPanel 中,该 JPanel 具有使用 BorderLayout 的细红色边框。

在一帧中,带有 WeirdGridLayout 的 JPanel 被排列为 EAST 和 WEST,另一个 NORTH 和 SOUTH 排列。

问题是在北/南的情况下,如果我改变框架的宽度/高度, 两个带有 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,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);
    }
}

【问题讨论】:

  • 您的 WeirdGridLayout 看起来像我在此处发布的 ModifiedFlowLayout:stackoverflow.com/questions/3679886/…
  • 能否保证2个区域的box数相同?如果是这样,在 GridLayout 中使用 2 x FlowLayout 应该可以解决问题。
  • @Andrew:谢谢,但我正在尝试了解如何实现LayoutManager,所以即使有一个预定义的类可以做我想做的事,它也无济于事。
  • 很公平。好问题,顺便说一句。
  • ARGH:我快疯了!我现在已经尝试过 BoxLayout 和 MigLayout 作为外部布局,他们仍然有这个问题。 :-(

标签: java swing layout-manager


【解决方案1】:

回答这个问题:无 :-) LayoutManager 不是为与其他 LayoutManager 交互而设计的,它们在它们负责的目标上独立运行。

  • LayoutManager 负责调整和定位容器的子项,而不是容器本身。所以 WeirdLayoutManager 在将父级的大小设置为其首选项时行为不端。
  • preferredLayoutSize 必须始终返回合理的值:将其视为一种分离,例如“亲爱的容器,鉴于您拥有世界上所有的空间,您希望拥有什么大小”或者反过来:不要不要依赖父母的大小来回答这个问题。这就像一条狗试图咬自己的尾巴。对于类似网格的结构,这可能需要某种 prefColumns/-Rows 属性
  • layoutContainer 必须在容器的当前边界内调整和定位直接子级,不要接触容器本身。它可以按照自己喜欢的任何方式进行操作,根据需要在任意多的行/列中进行操作

【讨论】:

  • @mKorbel:如果你有一个有用的答案,为什么不自己发布而不是评论呢?
  • 对解释哲学非常有用。好的,那么如果容器知道它想要什么大小,并且最近发生了变化,它如何将它传递给它自己的父级?
  • 听起来我应该不只是使用布局管理器,而是使用自定义 JPanel(使用我的自定义布局管理器),它可以根据内容断言自己的最小/最大/首选项大小。 ..
  • @Jason 如果有任何东西使容器(在其职责范围内)想要不同的大小,它应该使自己失效
【解决方案2】:

关键是要了解顶层布局管理器在做什么,并确保较低层具有首选大小或其他属性设置以使其正确呈现。

来自 API (http://download.oracle.com/javase/7/docs/api/java/awt/BorderLayout.html):

NORTH 和 SOUTH 组件可以水平拉伸; EAST 和 WEST 组件可以垂直拉伸;CENTER 组件可以水平和垂直拉伸以填充剩余空间。

【讨论】:

  • 有趣...这是谁的责任?自定义 LayoutManager 的程序员,还是使用自定义 LayoutManager 的应用程序的程序员?在 LayoutManager 中可以/应该在哪里调用 setPreferredSize()?
  • 应用程序的程序员必须了解每个布局管理器的工作原理。如果你将标准的 FlowLayout 和 BorderLayouts 结合起来,你可能会做出一些丑陋的 GUI。
  • 好的,好吧,在我的东/西案例中,我想水平分割红色边框的 JPanel,让我的每个 WeirdLayoutManager JPanel 有一半的总大小并确定它们适当的垂直大小.有没有办法使用与 BorderLayout 不同的顶级经理来做到这一点?
  • ...在我的北/南案例中,它看起来像是根据过时的信息分割垂直空间。
  • @Jason - 永远不会调用 setPreferredSize,甚至在普通应用程序代码中也不会。在 LayoutManager 中这样做是纯粹的 EVIL - 这些是由组件产生和在布局中使用的尺寸提示
猜你喜欢
  • 1970-01-01
  • 2011-09-13
  • 1970-01-01
  • 2016-01-31
  • 2018-06-12
  • 2016-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多