Java布局比例:创建可伸缩的方形面板

时间:2013-01-31 22:00:42

标签: java user-interface layout

我正在制作一个GUI组件来代表窗口中的棋盘。通常它将是一个8x8正方形的网格,虽然有些变体需要10x8板等。第一步是制作一个包含8x8组件网格的面板。

课程Board扩展JPanel并使用GridLayout为8x8组件的网格建模。为了完成某些工作,这些仅仅是Square类,它扩展了JButton。麻烦的是他们不是正方形!

Board已添加到新近实例化的JFrame中,在屏幕上打包并呈现。当然,现在电路板在用户调整大小时占用整个帧。网格与电路板一起缩放,这会将方块扭曲成矩形。

这并非完全不受欢迎的行为。我希望电路板能够与框架一起扩展。但是,我想确保方块始终保持正方形。电路板可以是矩形(10x8),但应保持固定比例。

如何获得正方形?

2 个答案:

答案 0 :(得分:5)

您可以选择使用符合细胞首选大小的LayoutManagerGridLayout将为每个单元格提供相等数量的可用空间,这似乎不是您想要的。

例如GridBagLayout

之类的内容

enter image description here

public class TestChessBoard {

    public static void main(String[] args) {
        new TestChessBoard();
    }

    public TestChessBoard() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ChessBoardPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ChessBoardPane extends JPanel {

        public ChessBoardPane() {
            int index = 0;
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            for (int row = 0; row < 8; row++) {
                for (int col = 0; col < 8; col++) {
                    Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE;
                    gbc.gridx = col;
                    gbc.gridy = row;
                    add(new Cell(color), gbc);
                    index++;
                }
                index++;
            }
        }

    }

    public class Cell extends JButton {

        public Cell(Color background) {

            setContentAreaFilled(false);
            setBorderPainted(false);
            setBackground(background);
            setOpaque(true);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(25, 25);
        }

    }

}

已更新为比例示例

现在,如果你想做一个比例布局(这样,无论可用空间如何,网格的每个单元格都保持与另一个单元格成比例),事情开始变得......有趣......

enter image description here

public class TestChessBoard {

    public static void main(String[] args) {
        new TestChessBoard();
    }

    public TestChessBoard() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestChessBoard.ChessBoardPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ChessBoardPane extends JPanel {

        public ChessBoardPane() {
            int index = 0;
            setLayout(new ChessBoardLayoutManager());
            for (int row = 0; row < 8; row++) {
                for (int col = 0; col < 8; col++) {
                    Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE;
                    add(new TestChessBoard.Cell(color), new Point(col, row));
                    index++;
                }
                index++;
            }
        }
    }

    public class Cell extends JButton {

        public Cell(Color background) {

            setContentAreaFilled(false);
            setBorderPainted(false);
            setBackground(background);
            setOpaque(true);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(25, 25);
        }
    }

    public class ChessBoardLayoutManager implements LayoutManager2 {

        private Map<Point, Component> mapComps;

        public ChessBoardLayoutManager() {
            mapComps = new HashMap<>(25);
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
            if (constraints instanceof Point) {

                mapComps.put((Point) constraints, comp);

            } else {

                throw new IllegalArgumentException("ChessBoard constraints must be a Point");

            }
        }

        @Override
        public Dimension maximumLayoutSize(Container target) {
            return preferredLayoutSize(target);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void invalidateLayout(Container target) {
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
            Point[] keys = mapComps.keySet().toArray(new Point[mapComps.size()]);
            for (Point p : keys) {
                if (mapComps.get(p).equals(comp)) {
                    mapComps.remove(p);
                    break;
                }
            }
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return new CellGrid(mapComps).getPreferredSize();
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return preferredLayoutSize(parent);
        }

        @Override
        public void layoutContainer(Container parent) {
            int width = parent.getWidth();
            int height = parent.getHeight();

            int gridSize = Math.min(width, height);

            CellGrid grid = new CellGrid(mapComps);
            int rowCount = grid.getRowCount();
            int columnCount = grid.getColumnCount();

            int cellSize = gridSize / Math.max(rowCount, columnCount);

            int xOffset = (width - (cellSize * columnCount)) / 2;
            int yOffset = (height - (cellSize * rowCount)) / 2;

            Map<Integer, List<CellGrid.Cell>> cellRows = grid.getCellRows();
            for (Integer row : cellRows.keySet()) {
                List<CellGrid.Cell> rows = cellRows.get(row);
                for (CellGrid.Cell cell : rows) {
                    Point p = cell.getPoint();
                    Component comp = cell.getComponent();

                    int x = xOffset + (p.x * cellSize);
                    int y = yOffset + (p.y * cellSize);

                    comp.setLocation(x, y);
                    comp.setSize(cellSize, cellSize);

                }
            }

        }

        public class CellGrid {

            private Dimension prefSize;
            private int cellWidth;
            private int cellHeight;

            private Map<Integer, List<Cell>> mapRows;
            private Map<Integer, List<Cell>> mapCols;

            public CellGrid(Map<Point, Component> mapComps) {
                mapRows = new HashMap<>(25);
                mapCols = new HashMap<>(25);
                for (Point p : mapComps.keySet()) {
                    int row = p.y;
                    int col = p.x;
                    List<Cell> rows = mapRows.get(row);
                    List<Cell> cols = mapCols.get(col);
                    if (rows == null) {
                        rows = new ArrayList<>(25);
                        mapRows.put(row, rows);
                    }
                    if (cols == null) {
                        cols = new ArrayList<>(25);
                        mapCols.put(col, cols);
                    }
                    Cell cell = new Cell(p, mapComps.get(p));
                    rows.add(cell);
                    cols.add(cell);
                }

                int rowCount = mapRows.size();
                int colCount = mapCols.size();

                cellWidth = 0;
                cellHeight = 0;

                for (List<Cell> comps : mapRows.values()) {
                    for (Cell cell : comps) {
                        Component comp = cell.getComponent();
                        cellWidth = Math.max(cellWidth, comp.getPreferredSize().width);
                        cellHeight = Math.max(cellHeight, comp.getPreferredSize().height);
                    }
                }

                int cellSize = Math.max(cellHeight, cellWidth);

                prefSize = new Dimension(cellSize * colCount, cellSize * rowCount);
                System.out.println(prefSize);
            }

            public int getRowCount() {
                return getCellRows().size();
            }

            public int getColumnCount() {
                return getCellColumns().size();
            }

            public Map<Integer, List<Cell>> getCellColumns() {
                return mapCols;
            }

            public Map<Integer, List<Cell>> getCellRows() {
                return mapRows;
            }

            public Dimension getPreferredSize() {
                return prefSize;
            }

            public int getCellHeight() {
                return cellHeight;
            }

            public int getCellWidth() {
                return cellWidth;
            }

            public class Cell {

                private Point point;
                private Component component;

                public Cell(Point p, Component comp) {
                    this.point = p;
                    this.component = comp;
                }

                public Point getPoint() {
                    return point;
                }

                public Component getComponent() {
                    return component;
                }

            }

        }
    }
}

答案 1 :(得分:3)

这有点长,所以这里是快速回答:考虑到您的电路板尺寸(8x8,10x8),您无法维护方形正方形板,如果是用户可以调整大小。您应该限制电路板的大小,以便它保持纵横比,即使这意味着您的框架中有一些空白区域。好的,请继续阅读冗长的解释...

有两种方法可以使这项工作。您可以限制JFrame的可能大小,也可以限制Board的大小,以便它不会始终填充框架。限制电路板的尺寸是更常用的方法,所以让我们从这开始。

选项1:限制董事会

如果您正在使用一组固定的电路板尺寸(8x8,10x8,以及其他几个尺寸),并假设每个正方形都有一些最小尺寸(棋盘上的1个像素正方形听起来不太实用),只有很多框架尺寸,电路板可以完全填充。如果您的框架是80像素,80像素,那么您的8x8电路板非常适合。但是一旦用户调整到像85x80这样的东西,你就会被卡住,因为你不能完全填充它,同时保持你给出的板尺寸方块。

在这种情况下,你想要留空5个像素,无论它是5还是以上,或2.5以上,或者其他什么都没关系。这应该听起来很熟悉 - 这是一个宽高比问题,基本上是为什么你可以根据电视和电影尺寸在电视边缘看到黑条。

选项2:限制框架

如果您希望电路板始终完全填满框架,可能不是您想要的,那么您必须在用户调整框架大小后调整框架的大小。假设您使用的是10x8电路板,用户将帧设置为107x75。这不是太糟糕,只需要一点点数学,你就能发现100x80是你最接近的宽高比,然后修复窗口。如果窗口不停地在它们周围跳跃,对用户来说可能会有点令人沮丧,特别是如果他们试图让它像50x200一样。

最后的想法/示例

限制电路板很可能是正确的解决方案。从游戏到桌面应用程序的一切都遵循这一原则。以MS Office产品中的功能区为例。当您使窗口变大时,功能区将扩展(保持其比例),直到达到最大尺寸,然后您只需为文档获得更多空间。当你使窗口变小时,色带变小(再次保持其比例),直到它达到最小尺寸,然后你开始丢失它的一部分(记住,不要在你的板上放置1x1方块)。

另一方面,您可以完全阻止用户调整窗口大小。我很确定这就是MineSweeper的工作方式(不要在这台计算机上进行仔细检查),并且可能是您需要的更好/更简单的解决方案。