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

发布于 2024-11-15 09:01:34 字数 5780 浏览 1 评论 0原文

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

在此处输入图像描述

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

在一个框架中,具有 WeirdGridLayout 的 JPanel 排列为东和西,其他排列 排列为北和南。

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

I'm experimenting with a custom LayoutManager and I don't understand the subtleties of using it within a component that itself has an upper-level layout manager.

enter image description here

Below is a test program that makes two frames with pair of JPanels. Each of the two JPanels has a thin black border and uses my WeirdGridLayout to force its child components into squares in a grid, with the JPanel height being computed from the width. Both JPanels are in another JPanel with a thin red border that uses BorderLayout.

In one frame, the JPanels with WeirdGridLayout are arranged EAST and WEST, the other
arranged NORTH and SOUTH.

The problem is that in the north/south case, if I change the width/height of the frame, the
two JPanels with WeirdGridLayout have the right size, but not the right position (they either have a gap or overlap vertically).

enter image description here

In the east/west case, it just ends up wrong.

enter image description here

What I have to do to get my layout manager to play well with outer layout managers?

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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

等风来 2024-11-22 09:01:34

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

  • LayoutManager 负责调整容器的子级的大小和位置,而不是容器本身。因此 WeirdLayoutManager 在将父级的大小设置为其首选项时行为不当。
  • PreferredLayoutSize 必须始终返回合理的内容:将其视为某种独立的内容,例如“亲爱的容器,假设您拥有世界上所有的空间,您到底想要拥有什么大小”或者相反:不要依赖根据父级尺寸来回答问题。这就像一只狗试图咬自己的尾巴。对于类似网格的结构,可能需要某种 prefColumns/-Rows 属性,
  • layoutContainer 必须在容器的当前边界内调整直接子级的大小和位置,不要触摸容器本身。它可以按照需要的任意数量的行/列来执行此操作

Answering the subject: none :-) LayoutManager is not designed for interaction with other LayoutManagers, they act in isolation on the target they are responsible for.

  • LayoutManager is responsible for sizing and positioning the children of container, not the container itself. So WeirdLayoutManager is misbehaving in setting the size of the parent to its pref.
  • preferredLayoutSize must return something reasonable always: regard it as kind-of detached, something like "dear container, given you had all the space of the world, what size exactly would you like to have" Or the other way round: don't rely on the parent size to answer the question. It would be like a dog trying to bite into its own tail. For a grid-like structure, that probably requires some kind of prefColumns/-Rows property
  • layoutContainer must size and position the direct children inside the current bounds of the container, don't touch the container itself. It can do so in any way it likes, in as many rows/columns as needed
回眸一笑 2024-11-22 09:01:34

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

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

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

The key is to understand what the top level layout manager is doing and making sure the lower level has a preferred size or other attribute set to have it render correctly.

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

The NORTH and SOUTH components may be stretched horizontally; the EAST and WEST components may be stretched vertically; the CENTER component may stretch both horizontally and vertically to fill any space left over.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文