如何使网格叠加中的所有图像齐平?

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

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

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

【讨论】:

以上是关于如何使网格叠加中的所有图像齐平?的主要内容,如果未能解决你的问题,请参考以下文章

在引导网格中使图像大小相同

css 使所有不同大小的图像在网格中均匀缩放

如何使下一个/图像填充可用的网格空间?

如何使网格内的图像控件居中?

如何在响应式方形网格中仅使背景图像透明?

如何在网格中布置图像?