如何使网格叠加中的所有图像齐平?
Posted
技术标签:
【中文标题】如何使网格叠加中的所有图像齐平?【英文标题】:How to make all images in Grid Overlay flush? 【发布时间】:2022-01-14 19:33:08 【问题描述】:我正在尝试为我创建的满足以下特定条件的基于迷宫的游戏实现 GUI:
GUI 本身有一个固定的大小并且不可调整大小(第 41 行)。
包含所有迷宫图像的主面板(第 57 行)是可滚动的。所有迷宫图像组件彼此齐平。
如果迷宫足够小,则整个迷宫都将在主面板中可见。 如果迷宫很大,则用户需要滚动。主面板需要通过鼠标侦听器(第 130 行)访问,该侦听器返回被点击的组件。
以下代码似乎符合标准 1 和 3,但不符合标准 2:
public class MazeGui extends JFrame implements DungeonView
private final Board board;
public MazeGui(ReadOnlyModel m)
//this.setSize(m.getNumRows()*100, m.getNumCols()*100);
this.setSize(600, 600);
this.setLocation(200, 200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.board = new Board(m);
JScrollPane scroller = new JScrollPane(board);
this.add(scroller, BorderLayout.CENTER);
setTitle("Dungeon Escape");
private class Board extends JPanel
private ReadOnlyModel m;
public Board(ReadOnlyModel m)
this.m = m;
GridLayout layout = new GridLayout(m.getNumRows(),m.getNumCols(), 0, 0);
// layout.setHgap(-100);
// layout.setVgap(-100);
this.setLayout(layout);
this.setSize(m.getNumRows()*64,m.getNumCols()*64);
for (int i = 0; i < m.getNumRows() * m.getNumCols(); i++)
try
// load resource from the classpath instead of a specific file location
InputStream imageStream = getClass().getResourceAsStream(String.format("/images/%s.png", m.getRoomDirections(i + 1)));
// convert the input stream into an image
Image image = ImageIO.read(imageStream);
// add the image to a label
JLabel label = new JLabel(new ImageIcon(image));
label.setPreferredSize(new Dimension(64, 64));
JPanel panel = new JPanel();
panel.setSize(64, 64);
String name = String.format("%d", i);
panel.setName(name);
panel.add(label);
// add the label to the JFrame
//this.layout.addLayoutComponent(TOOL_TIP_TEXT_KEY, label);
this.add(panel);
catch (IOException e)
JOptionPane.showMessageDialog(this, e.getMessage());
e.printStackTrace();
@Override
public void addClickListener(DungeonController listener)
Board board = this.board;
MouseListener mouseListener = new MouseAdapter()
@Override
public void mouseClicked(MouseEvent e)
System.out.println(String.format("(%d,%d)", e.getX(), e.getY()));
JPanel panel = (JPanel) board.getComponentAt(e.getPoint());
System.out.println(panel.getName());
;
board.addMouseListener(mouseListener);
@Override
public void refresh()
this.repaint();
@Override
public void makeVisible()
this.setVisible(true);
这是它产生的图像:
【问题讨论】:
1) 不要设置标签的首选尺寸。标签的大小将自动由图像的大小决定。如果您将首选尺寸设置为大于实际图像尺寸,您将获得额外空间。 2)不要将标签添加到子面板。面板的默认布局是 FlowLayout,它会增加额外的空间。只需使用 GridLayout 将标签直接添加到面板。 如需更好的帮助,请edit 添加minimal reproducible example。上面的代码需要至少导入才能成为 MRE。热链接到合适的图像,以便其他人可以轻松运行它。 @camickr 我实现了你的建议。除了现在我的 MouseListener 坏了,GUI 看起来一样。因此,标准 3 也不再满足。 1) 您永远不会将听众添加到董事会 2) 如果您需要更多帮助,则需要发布您的 minimal reproducible example。 @Nova 原来我应该使用 GridBagLayout!我 - 没有主要问题是您手动将框架的大小设置为(600x 600)。当您有一个由 8 个正方形组成的网格时,每个网格的大小为 64,您会使框架太大,以至于每个 JLabel 的大小都调整得太大。Don't use setSize()
。确定每个组件的大小是布局管理器的工作。相反,简单的解决方案是 pack()
框架在所有组件都添加到框架之后。那么就不会有差距了。
【参考方案1】:
首先,我会使用不同的布局管理器,它会尝试扩展以适应底层容器的大小。
然后,我会让组件完成它们的工作。我不知道您为什么要将标签添加到另一个面板,该面板似乎没有添加其他功能/特性,只是增加了复杂性。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Test
public static void main(String[] args)
new Test();
public Test()
EventQueue.invokeLater(new Runnable()
@Override
public void run()
List<Maze.Direction> directions = new ArrayList<>(32);
directions.add(Maze.Direction.EAST_SOUTH);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
System.out.println(directions.size());
Maze maze = new DefaultMaze(5, 6, directions);
MazeGui frame = new MazeGui(maze);
frame.addClickListener(null);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
);
public interface Maze
enum Direction
EAST_SOUTH("EastSouth.png"), EAST_SOUTH_WEST("EastSouthWest.png"), SOUTH_WEST("SouthWest.png"),
NORTH_EAST_SOUTH("NorthEastSouth.png"), NORTH_EAST_SOUTH_WEST("NorthEastSouthWest.png"),
NORTH_SOUTH_WEST("NorthSouthWest.png"), NORTH_SOUTH("NorthSouth.png"), NORTH("North.png");
private BufferedImage image;
private Direction(String name)
try
image = ImageIO.read(getClass().getResource("/images/" + name));
catch (IOException ex)
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
public BufferedImage getImage()
return image;
public int getRows();
public int getColumns();
public Direction getRoomDirections(int index);
public class DefaultMaze implements Maze
int rows;
int columns;
private List<Direction> directions;
public DefaultMaze(int rows, int columns, List<Direction> directions)
this.rows = rows;
this.columns = columns;
this.directions = directions;
public int getRows()
return rows;
public int getColumns()
return columns;
@Override
public Direction getRoomDirections(int index)
return directions.get(index);
public class MazeGui extends JFrame
// Missing code
public interface DungeonController
private final Board board;
public MazeGui(Maze m)
this.setSize(600, 600);
this.setResizable(false);
this.board = new Board(m);
JScrollPane scroller = new JScrollPane(board);
this.add(scroller, BorderLayout.CENTER);
setTitle("Dungeon Escape");
public Board getBoard()
return board;
public void addClickListener(DungeonController listener)
Board board = getBoard();
board.addMouseListener(new MouseAdapter()
@Override
public void mouseClicked(MouseEvent e)
Component cell = board.getComponentAt(e.getPoint());
System.out.println(cell.getName());
board.highlight(cell.getBounds());
);
private class Board extends JPanel
private Rectangle selectedCell;
private Maze maze;
public Board(Maze maze)
this.maze = maze;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
for (int index = 0; index < maze.getRows() * maze.getColumns(); index++)
Maze.Direction direction = maze.getRoomDirections(index);
JLabel label = new JLabel(new ImageIcon(direction.getImage()));
label.setName(direction.name());
add(label, gbc);
gbc.gridx++;
if (gbc.gridx >= maze.getColumns())
gbc.gridx = 0;
gbc.gridy++;
// addMouseListener(new MouseAdapter()
// @Override
// public void mouseClicked(MouseEvent e)
// Component component = getComponentAt(e.getPoint());
// selectedCell = null;
// if (component != null)
// selectedCell = component.getBounds();
//
// repaint();
//
// );
public void highlight(Rectangle bounds)
selectedCell = bounds;
repaint();
@Override
public void paint(Graphics g)
super.paint(g);
if (selectedCell != null)
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0, 0, 255, 128));
g2d.fill(selectedCell);
g2d.dispose();
【讨论】:
我被这个问题困扰了好几天!原来我应该一直在使用 GridBagLayout!我非常感谢您的帮助。【参考方案2】:GUI 本身有一个固定的大小并且不可调整大小
所以这里的问题是您强制“板”面板具有任意大小。
this.setSize(600, 600);
面板的实际大小应该是 8 * 64 = 512。因此,每个网格都会增加额外的空间。
不要硬编码大小值。
布局管理器的工作是确定每个组件的首选大小。
所以不要使用 setSize(...) 你应该pack()
使框架可见:
this.pack();
this.setVisible(true);
当你这样做时,你会看到迷宫完全适合框架。
如果你想在迷宫周围有额外的空间,那么你需要在你的棋盘上添加一个“边框”:
setBorder( new EmptyBorder(88, 88, 88, 88) );
GridLayout layout = new GridLayout(m.getNumRows(),m.getNumCols(), 0, 0);
原来我应该一直在使用 GridBagLayout!
无需更改布局管理器,只需更有效地使用布局管理器。
如果您确实出于某种原因需要指定固定的帧大小,则可以进行以下更改:
//this.add(scroller, BorderLayout.CENTER);
JPanel wrapper = new JPanel( new GridBagLayout() );
wrapper.add(scroller, new GridBagConstraints());
this.add(wrapper, BorderLayout.CENTER);
这将允许“板”面板以其首选大小显示,并且“板”面板将在其父容器中居中。
使用这些技巧将帮助您有效地创建更复杂的布局。
【讨论】:
以上是关于如何使网格叠加中的所有图像齐平?的主要内容,如果未能解决你的问题,请参考以下文章