我正在制作一个GUI组件来代表窗口中的棋盘。通常它将是一个8x8正方形的网格,虽然有些变体需要10x8板等。第一步是制作一个包含8x8组件网格的面板。
课程Board
扩展JPanel
并使用GridLayout
为8x8组件的网格建模。为了完成某些工作,这些仅仅是Square
类,它扩展了JButton
。麻烦的是他们不是正方形!
Board
已添加到新近实例化的JFrame
中,在屏幕上打包并呈现。当然,现在电路板在用户调整大小时占用整个帧。网格与电路板一起缩放,这会将方块扭曲成矩形。
这并非完全不受欢迎的行为。我希望电路板能够与框架一起扩展。但是,我想确保方块始终保持正方形。电路板可以是矩形(10x8),但应保持固定比例。
如何获得正方形?
答案 0 :(得分:5)
您可以选择使用符合细胞首选大小的LayoutManager
。 GridLayout
将为每个单元格提供相等数量的可用空间,这似乎不是您想要的。
例如GridBagLayout
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);
}
}
}
已更新为比例示例
现在,如果你想做一个比例布局(这样,无论可用空间如何,网格的每个单元格都保持与另一个单元格成比例),事情开始变得......有趣......
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的工作方式(不要在这台计算机上进行仔细检查),并且可能是您需要的更好/更简单的解决方案。