2

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

该类Board扩展JPanel并使用 aGridLayout对 8x8 组件的网格进行建模。为了完成某件事,这些只是Square扩展的类JButton。问题是它们不是正方形!

Board添加到新实例化JFrame、打包并呈现在屏幕上的 . 当然,现在板子在用户调整大小时占据了整个框架。网格与板一起缩放,这会将正方形扭曲成矩形。

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

我如何获得正方形?

4

2 回答 2

7

You can choose to use a LayoutManager that honors the preferred size of the cells instead. GridLayout will provide a equal amount of the available space to each cell, which doesn't appear to be quite what you want.

For example, something like 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);
        }

    }

}

Updated with proportional example

Now, if you want to do a proportional layout (so that each cell of the grid remains proportional to the other regardless of the available space), things begin to get ... fun ...

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

            }

        }
    }
}
于 2013-01-31T23:08:21.703 回答
3

这有点长,所以这里是一个快速的答案:如果用户可以调整它的大小,你不能在给定板尺寸(8x8、10x8)的情况下维护一个带有方形的方形板并完全填满屏幕。您应该限制板子的大小,以便它保持纵横比,即使这意味着您的框架中有一些空白空间。好的,请继续阅读冗长的解释...

有两种方法可以使这项工作。您可以限制 的可能大小JFrame,或者您可以限制您的大小,Board这样它就不会总是填满框架。限制板的大小是更常见的方法,所以让我们从它开始。

选项 1:限制董事会

如果您使用一组固定的棋盘尺寸(8x8、10x8 和其他几个),并假设每个方格都有一些最小尺寸(棋盘上的 1 像素方格听起来不太实用),那么只有如此多的框架尺寸,板可以完全填满。如果您的框架是 80 像素 x 80 像素,那么您的 8x8 板非常适合。但是,一旦用户将大小调整为 85x80 之类的东西,您就会被卡住,因为您无法在保持与您给定的板尺寸相同的正方形的同时完全填充它。

在这种情况下,您希望将 5 个像素留空,无论是高于或低于 5 个,还是高于或低于 2.5 个,或者其他什么,都无所谓。这听起来应该很熟悉 - 这是一个纵横比问题,基本上是为什么您可以根据电视与电影尺寸在电视边缘出现黑条。

选项 2:限制框架

如果您希望板子始终完全填满框架,可能不是您想要的,那么您必须在用户调整框架大小后调整框架的大小。假设您使用的是 10x8 板,并且用户将框架设置为 107x75。这还不错,通过一些数学运算,您可以计算出 100x80 是您最接近的有效纵横比,并修复窗口。如果窗口一直在他们身上跳来跳去,这可能会让用户有点沮丧,特别是如果他们试图让它像 50x200 那样偏离。

最后的想法/示例

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

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

于 2013-01-31T22:34:25.517 回答