如何使 Grid Overlay 中的所有图像齐平?

How to make all images in Grid Overlay flush?

我正在尝试为我创建的满足以下特定条件的基于迷宫的游戏实现 GUI:

  1. GUI 本身具有固定大小且不可调整大小(第 41 行)。

  2. 包含所有迷宫图像的主面板(第 57 行)是可滚动的。所有迷宫图像组件彼此齐平。

    • 如果迷宫足够小,那么整个迷宫将在主面板中可见。
    • 如果迷宫很大,则用户需要滚动。
  3. 鼠标侦听器(第 130 行)需要访问主面板,returns 正在单击的组件。

以下代码似乎满足标准 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);
    
  }

}

这是它生成的图像:

首先,我会使用不同的布局管理器,它会尝试扩展以适应底层容器的大小。

然后,我会让组件完成它们的工作。我不知道你为什么要将标签添加到另一个面板,该面板似乎没有添加额外的内容 functionality/features,只是增加了复杂性。

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

The GUI itself has a set size and is not resizable

所以这里的问题是您强制“板”面板具有任意大小。

this.setSize(600, 600);

面板的实际大小应为 8 * 64 = 512。因此每个网格都会添加额外的 space。

不要硬编码大小值。

布局管理器的工作是确定每个组件的首选大小。

因此,您应该 pack() 在使框架可见之前,而不是使用 setSize(...):

this.pack();
this.setVisible(true);

执行此操作时,您会看到迷宫完全适合框架。

如果你想在迷宫周围增加 space,那么你需要在你的棋盘上添加一个“边框”:

setBorder( new EmptyBorder(88, 88, 88, 88) );
GridLayout layout = new GridLayout(m.getNumRows(),m.getNumCols(), 0, 0);

Turns out I should have been using GridBagLayout!

不需要改变布局管理器,只是更有效地使用布局管理器。

如果您确实出于某种原因需要指定固定的帧大小,那么您可以进行以下更改:

//this.add(scroller, BorderLayout.CENTER);
JPanel wrapper = new JPanel( new GridBagLayout() );
wrapper.add(scroller, new GridBagConstraints());
this.add(wrapper, BorderLayout.CENTER);

这将允许“看板”面板以其首选大小显示,并且“看板”面板将在其父容器中居中。

使用这些技巧将帮助您有效地创建更复杂的布局。