Java - 从形状创建按钮

Java - Create a button from a shape

我目前正在学习 Java,并且已经分配了完成一个程序来创建游戏 Conways's life(我们从提供给我们的一些代码开始,并且必须向其添加功能等)。

我目前停留在游戏的菜单选项上。我希望它从菜单屏幕开始,其中按钮出现在顶部,用于“开始”、“随机”、“加载”、“保存”。我编写了代码,以便程序通过 fillRect 选项显示这些按钮我的绘画方法。

我的问题是,如何使用 mousePressed 方法来识别选定的单元格,以便在它们被选定时发生动作。我已经看了一段时间,但似乎无法正常工作。

任何建议都将是一个巨大的帮助。我在下面分享了我的代码。这是一项正在进行的工作,但我真的很想在继续使用其他功能之前让它工作。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;

public class ConwaysLife extends JFrame implements Runnable, MouseListener {
    
    // member data
    private BufferStrategy strategy;
    private Graphics offscreenBuffer;
    private boolean gameState[][] = new boolean[40][40];
    private boolean isGameInProgress = false;
    
    // constructor
    public ConwaysLife () {
        //Display the window, centred on the screen
         Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
         int x = screensize.width/2 - 400;
         int y = screensize.height/2 - 400;
         setBounds(x, y, 800, 800);
         setVisible(true);
         this.setTitle("Conway's game of life");
        
         // initialise double-buffering
         createBufferStrategy(2);
         strategy = getBufferStrategy();
         offscreenBuffer = strategy.getDrawGraphics();
        
         // register the Jframe itself to receive mouse events
         addMouseListener(this);
        
         // initialise the game state
         for (x=0;x<40;x++) {
         for (y=0;y<40;y++) {
             gameState[x][y]=false;
         }
         }
         
         // create and start our animation thread
         Thread t = new Thread(this);
         t.start();
    }
    
    // thread's entry point
    public void run() {
        while ( 1==1 ) {
        // 1: sleep for 1/5 sec
            try {
                Thread.sleep(200);
        } catch (InterruptedException e) { }
            
        // 2: animate game objects [nothing yet!]
        /*if (isGameInProgress == false) {
            this.repaint();
        }*/
            
            
            
            
            
        
        // 3: force an application repaint
        this.repaint();
    }
    }
    
    
    // mouse events which must be implemented for MouseListener
     public void mousePressed(MouseEvent e) {
         
         while (!isGameInProgress) {
             int x = e.getX()/20;
             int y = e.getY()/20;
             if(x >= 10 && x <= 80 && y >= 40 && y <= 65) {
                 isGameInProgress = !isGameInProgress;
                 this.repaint();
             }
             
         }
         
    
     // determine which cell of the gameState array was clicked on
         int x = e.getX()/20;
         int y = e.getY()/20;
         // toggle the state of the cell
         gameState[x][y] = !gameState[x][y];
         // request an extra repaint, to get immediate visual feedback
         this.repaint();
     }
     
     
     public void mouseReleased(MouseEvent e) { }
     public void mouseEntered(MouseEvent e) { }
     public void mouseExited(MouseEvent e) { }
     public void mouseClicked(MouseEvent e) { }
    //
     
    
     
    // application's paint method
    public void paint(Graphics g) {
        Font font = new Font("Veranda", Font.BOLD, 20);
            
        g = offscreenBuffer; // draw to off screen buffer
        
        // clear the canvas with a big black rectangle
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, 800, 800);
        
        /*look to add a while game in progress loop here!!!*/
        // draw menu options
        if(!isGameInProgress) {
        g.setColor(Color.green); 
        g.fillRect(10, 40, 70, 25);
        g.fillRect(100, 40, 100, 25);
        g.fillRect(300, 40, 170, 25);
    
        g.setColor(Color.BLACK);
        g.setFont(font);
        g.drawString("Start", 15, 60);
        g.drawString("Random", 105, 60);
        g.drawString("Load", 305, 60);
        g.drawString("Save", 395, 60);
        }
            
        // redraw all game objects
        g.setColor(Color.WHITE);
             for (int x=0;x<40;x++) {
                 for (int y=0;y<40;y++) {
                     if (gameState[x][y]) {
                         g.fillRect(x*20, y*20, 20, 20);
                     }
                 }
             }
             
            // flip the buffers
            strategy.show();
    }
    
    // application entry point
    public static void main(String[] args) {
    ConwaysLife w = new ConwaysLife();
    }
}

您不会喜欢这个答案,但这是解决问题的“正确”方法。

您需要了解的是,Swing/AWT 使用的是“被动”渲染工作流,BufferStrategy 使用的是“主动”渲染工作流,它们彼此不兼容。

作为一般规则,您不应覆盖 paint 顶级容器,例如 JFrame,这将导致无穷无尽的问题。相反,您应该从 JPanel 之类的东西开始,然后覆盖它的 paintComponent 方法。

话虽如此,但有一个“更简单”的解决方案可供您使用。 CardLayout。这将允许您将菜单的工作流程与游戏分开,并解决 Swing/AWT 和 BufferStrategy

之间的问题

例如...

import java.awt.Canvas;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferStrategy;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public final class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new MainPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class MainPane extends JPanel {

        enum View {
            MENU, GAME;
        }

        private CardLayout cardLayout = new CardLayout();
        private GamePane gamePane;

        public MainPane() {
            setLayout(cardLayout);

            gamePane = new GamePane();

            add(new MenuPane(new MenuPane.Observer() {
                @Override
                public void startNewGame() {
                    showGame();
                }

                @Override
                public void randomGame() {
                }

                @Override
                public void loadGame() {
                }

                @Override
                public void saveGame() {
                }
            }), View.MENU);
            add(gamePane, View.GAME);
        }

        protected void add(Component compent, View view) {
            add(compent, view.name());
        }

        protected void showGame() {
            show(View.GAME);
            gamePane.start();
        }

        protected void showMenu() {
            gamePane.stop();
            show(View.MENU);
        }

        protected void show(View view) {
            cardLayout.show(this, view.name());
        }

    }

    public class MenuPane extends JPanel {

        public interface Observer {
            public void startNewGame();
            public void randomGame();
            public void loadGame();
            public void saveGame();
        }

        private Observer observer;

        public MenuPane(Observer observer) {
            this.observer = observer;

            JButton startButton = new JButton("Start");
            JButton randomButton = new JButton("Random");
            JButton loadButton = new JButton("Load");
            JButton saveButton = new JButton("Save");

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.ipadx = 10;
            gbc.ipady = 10;
            gbc.insets = new Insets(8, 8, 8, 8);
            gbc.weightx = GridBagConstraints.REMAINDER;

            add(startButton, gbc);
            add(randomButton, gbc);
            add(loadButton, gbc);
            add(saveButton, gbc);

            startButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.startNewGame();
                }
            });
            randomButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.randomGame();
                }
            });
            loadButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.loadGame();
                }
            });
            saveButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    observer.saveGame();
                }
            });
        }

    }

    public class GamePane extends Canvas {

        private Thread thread;
        private volatile boolean isRunning = false;

        public GamePane() {
            setBackground(Color.BLACK);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 800);
        }

        protected void start() {
            if (isRunning) {
                return;
            }
            createBufferStrategy(3);
            isRunning = true;
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    mainLoop();
                }
            });
            thread.start();
        }

        protected void stop() {
            if (!isRunning || thread == null) {
                return;
            }
            isRunning = false;
            try {
                thread.join();
            } catch (InterruptedException ex) {
            }
            thread = null;
        }

        protected void mainLoop() {
            try {
                while (isRunning) {
                    render();
                    Thread.sleep(16);
                }
            } catch (InterruptedException ex) {
            }
        }

        protected void render() {
            BufferStrategy strategy = getBufferStrategy();
            if (strategy == null) {
                return;
            }
            // Render single frame
            do {
                // The following loop ensures that the contents of the drawing buffer
                // are consistent in case the underlying surface was recreated
                do {
                    // Get a new graphics context every time through the loop
                    // to make sure the strategy is validated
                    Graphics graphics = strategy.getDrawGraphics();

                    FontMetrics fm = graphics.getFontMetrics();
                    String text = "All your game are belong to us";
                    int x = (getWidth() - fm.stringWidth(text)) / 2;
                    int y = (getHeight() - fm.getHeight()) / 2;

                    graphics.setColor(Color.WHITE);
                    graphics.drawString(text, x, y + fm.getAscent());

                    // Render to graphics
                    // ...
                    // Dispose the graphics
                    graphics.dispose();

                    // Repeat the rendering if the drawing buffer contents
                    // were restored
                } while (strategy.contentsRestored());

                // Display the buffer
                strategy.show();

                // Repeat the rendering if the drawing buffer was lost
            } while (strategy.contentsLost());
        }
    }
}

强烈建议您花时间通读:

“完全”基于 BufferStrategy 的方法...

现在,如果您“出于某些原因”不能使用 Swing,您仍然可以使用“委托”来实现类似的概念。

基本上这意味着将执行某些工作流程的责任“委派”给另一个人。在这种情况下,我们希望委托鼠标事件的渲染和处理。

这使您可以拥有专门的菜单工作流程和专门的游戏工作流程,而无需尝试混合大量状态。

为什么我一直坚持将这两个工作流程分开?很简单,因为它更容易管理和推理,还因为它支持 Single Responsibility Principle.

下面的例子利用了Renderable interface 来定义结束“渲染”委托需要实现的核心功能,在这种情况下,它很简单,我们想告诉渲染器在每个绘制过程中“渲染”它的内容,我们希望(可选)委托鼠标单击事件(这可以通过第二个 interface 甚至直接 MouseListener interface 来完成,但出于演示目的,我已将其作为 Renderable interface 的要求。

您的实际问题的“基本”解决方案是通过使用 Rectangle#contains(Point) 找到的。

这基本上是检查每个“按钮”Rectangle 以确定所提供的 MouseEvent 是否在其范围内,如果是,我们会采取行动。

然而,它有点复杂,因为我们需要提前构建 Rectangles,并不困难,但它实际上依赖于父级的状态,因为我们需要知道渲染器显示的区域,运行这个例子,你会明白我的意思

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;

public final class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                MainPane mainPane = new MainPane();
                JFrame frame = new JFrame();
                frame.add(mainPane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                mainPane.start();
            }
        });
    }

    public interface Renderable {
        public void render(Graphics2D g2d, Dimension size);

        // We could just extend from MouseListener
        // but I don't need all those event handlers
        public void mouseClicked(MouseEvent e);
    }

    public class MainPane extends Canvas {

        private Thread thread;
        private volatile boolean isRunning = false;

        private Renderable currentRenderer;

        private MenuRenderer menuRenderer;
        private GameRenderer gameRenderer;

        public MainPane() {
            setBackground(Color.BLACK);

            gameRenderer = new GameRenderer();
            menuRenderer = new MenuRenderer(new MenuRenderer.Observer() {
                @Override
                public void startNewGame() {
                    showGame();
                }

                @Override
                public void randomGame() {
                }

                @Override
                public void loadGame() {
                }

                @Override
                public void saveGame() {
                }
            });

            showMenu();

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if (currentRenderer == null) {
                        return;
                    }
                    currentRenderer.mouseClicked(e);
                }
            });
        }

        protected void showMenu() {
            // This may need to tell the game renderer to stop
            // or pause
            currentRenderer = menuRenderer;
        }

        protected void showGame() {
            currentRenderer = gameRenderer;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 800);
        }

        protected void start() {
            if (isRunning) {
                return;
            }
            createBufferStrategy(3);
            isRunning = true;
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    mainLoop();
                }
            });
            thread.start();
        }

        protected void stop() {
            if (!isRunning || thread == null) {
                return;
            }
            isRunning = false;
            try {
                thread.join();
            } catch (InterruptedException ex) {
            }
            thread = null;
        }

        protected void mainLoop() {
            try {
                while (isRunning) {
                    render();
                    Thread.sleep(16);
                }
            } catch (InterruptedException ex) {
            }
        }

        protected void render() {
            BufferStrategy strategy = getBufferStrategy();
            if (strategy == null && currentRenderer != null) {
                return;
            }
            // Render single frame
            do {
                // The following loop ensures that the contents of the drawing buffer
                // are consistent in case the underlying surface was recreated
                do {
                    // Get a new graphics context every time through the loop
                    // to make sure the strategy is validated
                    Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
                    g2d.setBackground(Color.BLACK);
                    g2d.fillRect(0, 0, getWidth(), getHeight());
                    RenderingHints hints = new RenderingHints(
                            RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
                    );
                    g2d.setRenderingHints(hints);
                    // Render to graphics
                    currentRenderer.render(g2d, getSize());
                    // Dispose the graphics
                    g2d.dispose();
                    // Repeat the rendering if the drawing buffer contents
                    // were restored
                } while (strategy.contentsRestored());

                // Display the buffer
                strategy.show();

                // Repeat the rendering if the drawing buffer was lost
            } while (strategy.contentsLost());
        }
    }

    public class GameRenderer implements Renderable {
        @Override
        public void render(Graphics2D g2d, Dimension size) {
            FontMetrics fm = g2d.getFontMetrics();
            String text = "All your game are belong to us";
            int x = (size.width - fm.stringWidth(text)) / 2;
            int y = (size.height - fm.getHeight()) / 2;

            g2d.setColor(Color.WHITE);
            g2d.drawString(text, x, y + fm.getAscent());
        }

        @Override
        public void mouseClicked(MouseEvent e) {
        }
    }

    public class MenuRenderer implements Renderable {

        public interface Observer {
            public void startNewGame();
            public void randomGame();
            public void loadGame();
            public void saveGame();
        }

        private Observer observer;

        private String[] menuOptions = new String[]{
            "New Game",
            "Random",
            "Load Game",
            "Save Game"
        };

        private Rectangle[] menuBounds;

        private int internalPadding = 20;
        private int horizontalGap = 16;

        public MenuRenderer(Observer observer) {
            this.observer = observer;
        }

        @Override
        public void render(Graphics2D g2d, Dimension size) {
            if (menuBounds == null) {
                createMenus(g2d, size);
            }
            renderMenus(g2d);
        }

        protected void createMenus(Graphics2D g2d, Dimension size) {
            FontMetrics fm = g2d.getFontMetrics();
            int totalHeight = (((fm.getHeight() + internalPadding) + horizontalGap) * menuOptions.length) - horizontalGap;
            int buttonHeight = fm.getHeight() + internalPadding;

            menuBounds = new Rectangle[menuOptions.length];

            int buttonWidth = 0;
            for (int index = 0; index < menuOptions.length; index++) {
                int width = fm.stringWidth(menuOptions[index]) + internalPadding;
                buttonWidth = Math.max(width, buttonWidth);
            }

            int yPos = (size.height - totalHeight) / 2;
            for (int index = 0; index < menuOptions.length; index++) {
                int xPos = (size.width - buttonWidth) / 2;
                Rectangle menuRectangle = new Rectangle(xPos, yPos, buttonWidth, buttonHeight);
                menuBounds[index] = menuRectangle;
                yPos += buttonHeight + (horizontalGap / 2);
            }
        }

        protected void renderMenus(Graphics2D g2d) {
            for (int index = 0; index < menuOptions.length; index++) {
                String text = menuOptions[index];
                Rectangle bounds = menuBounds[index];
                renderMenu(g2d, text, bounds);
            }
        }

        protected void renderMenu(Graphics2D g2d, String text, Rectangle bounds) {
            FontMetrics fm = g2d.getFontMetrics();
            int textWidth = fm.stringWidth(text);

            int textXPos = (bounds.x + (internalPadding / 2)) + ((bounds.width - internalPadding - textWidth) / 2);
            int textYPos = bounds.y + (internalPadding / 2);

            RoundRectangle2D buttonBackground = new RoundRectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height, 20, 20);

            g2d.setColor(Color.BLUE.darker());
            g2d.fill(buttonBackground);

            g2d.setColor(Color.WHITE);
            g2d.drawString(text, textXPos, textYPos + fm.getAscent());
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (menuBounds == null) {
                return;
            }
            for (int index = 0; index < menuOptions.length; index++) {
                if (menuBounds[index].contains(e.getPoint())) {
                    switch (index) {
                        case 0:
                            observer.startNewGame();
                            break;
                        case 2:
                            observer.randomGame();
                            break;
                        case 3:
                            observer.loadGame();
                            break;
                        case 4:
                            observer.saveGame();
                            break;
                    }
                }
            }
        }
    }
}