JPanel 创建两次
JPanel creates twice
我正在尝试为战舰游戏制作 GUI。一个 class 用于创建 GUI 本身,第二个 class 在其之上用于管理游戏中的棋盘。我的问题是,一旦发生鼠标单击,JPanel 就会创建两次(鼠标单击应该是在游戏中开火的位置,然后将其标记为 hit/miss)。我不确定为什么它会创建两次。是因为一个面板过去了吗?下面的代码和代码生成的照片。
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BattleshipApplet extends JApplet implements MouseListener {
private final JButton playButton = new JButton("Play");
private final JLabel msgBar = new JLabel("Click Play to start game");
private BoardPanel panel;
public BattleshipApplet(){
playButton.addActionListener(this::playButtonClicked);
addMouseListener(this);
}
public void init(){
configureGui();
}
private void configureGui(){
setLayout(new BorderLayout());
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
buttons.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
buttons.add(playButton);
add(buttons, BorderLayout.NORTH);
msgBar.setBorder(BorderFactory.createEmptyBorder(10,10,5,5));
add(createBoardPanel(), BorderLayout.CENTER);
add(msgBar, BorderLayout.SOUTH);
}
private BoardPanel createBoardPanel(){
panel = new BoardPanel();
return panel;
}
private void displayMessage(String msg){
msgBar.setText(msg);
}
private void playButtonClicked(ActionEvent event){
displayMessage("Play button clicked!");
}
public void mouseClicked(MouseEvent e) {
panel.mouseClickedAt(e.getX(), e.getY());
e.consume();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
}
板class使用JPanel
[![import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class BoardPanel extends JPanel {
int mx, my;
boolean rect1Clicked;
//gamePlay a;
public void init(){
rect1Clicked = false;
}
/***Your applet shall show the status of the board before and after
each shot, including the number of shots made and the status of
each place (no shot or hit/miss shot). ***/
public void paint(Graphics g){
boolean miss = false;
for (int i=0; i<11; i++){
g.setColor(Color.blue);
g.drawLine(20,20+i*28, 300, 20+i*28);
}
for (int i=0; i<11; i++)
g.drawLine(20+i*28,20,20+i*28,300);
//if inside board
if(rect1Clicked == true){
g.setColor(Color.green);
//aligns to square to check in computer board for hit/miss
int bx =(my-20)/28;
int by =(mx-20)/28;
//check hit on board
//if shot was a miss
if(miss == true ){
//update to white
g.setColor(Color.white);
}
//if shot was a hit
if(miss == false){
//update to red
g.setColor(Color.red);
}
//compare to line for fill
int fillx = mx/2;
int filly = my/2 ;
if(mx<=47){
fillx = 20;
}
if(mx>47 && mx<=75){
fillx = 48;
}
if(mx>75 && mx<=103){
fillx = 76;
}
if(mx>103 && mx <=131){
fillx = 104;
}
if(mx>131 && mx<=159){
fillx = 132;
}
if(mx>159 && mx<=187){
fillx = 160;
}
if(mx>187 && mx <=215){
fillx = 188;
}
if(mx>215 && mx <=243){
fillx = 216;
}
if(mx>243 && mx <=271){
fillx = 244;
}
if(mx>271 && mx<=299){
fillx = 272;
}
if(mx>299){
fillx = 300;
}
//y comparisons
if(my<=47){
filly = 20;
}
if(my>47 && my<=75){
filly = 48;
}
if(my>75 && my<=103){
filly = 76;
}
if(my>103 && my <=131){
filly = 104;
}
if(my>131 && my<=159){
filly = 132;
}
if(my>159 && my<=187){
filly = 160;
}
if(my>187 && my <=215){
filly = 188;
}
if(my>215 && my <=243){
filly = 216;
}
if(my>243 && my <=271){
filly = 244;
}
if(my>271 && my<=299){
filly = 272;
}
if(my>299){
filly = 300;
}
g.drawString("("+mx+","+my+")",mx,my);
//25 describes size of square
g.fillOval(fillx, filly, 25, 25);
}
}
public void game(BoardPanel p){
//while game plays
}
public void mouseClickedAt(int x, int y){
mx = x;
my = y;
//user clicked inside of board space
if(mx>20 && mx<300 && my>20 && my<300){
//send to board in MainBattleship
rect1Clicked = true;
}
//updates board
repaint();
}
}][1]][1]
我很迷茫,谢谢你的帮助!
建议:
- 不要重写 JPanel 的
paint
方法,而是重写其 paintComponent
方法,因为这样更安全,稍后当您想要制作动画时,动画效果会更流畅。
- 最重要的是,您几乎总是需要在自己的绘画方法中调用 super 的绘画方法,否则 JPanel 不会删除需要清理的先前图像工件。因此,如果您继续覆盖 paint(尽管我不建议这样做),您覆盖的第一行应该是
super.paint(g);
,或者如果您覆盖 paintComponent
,那么第一行应该是 super.paintComponent(g);
,当然假设你的方法使用名为 g
. 的 Graphics 参数
- 此外,将 MouseListener 添加到 JPanel,而不是 到小程序,因为面板上的鼠标单击位置对您很重要。
- 另外,使用组件网格或一些数学来极大地简化您的代码——丑陋的 if 块列表应该被更简单的 for 循环所取代,一个使用基本数学的循环。
- 考虑从您的绘画方法中提取上面讨论的逻辑,并将其提取到某种模型中,也许是二维布尔数组。
- 您在代码中使用了很多 "magic" 数字,这些数字应该更改为常量和数学派生数字的组合。
- 注意如果您单击您的 GUI,然后调整它的大小,或者如果您最小化它然后恢复它会发生什么——除了最后一个按下的红色圆圈之外,您会丢失所有红色圆圈。这是使用布尔网格或其他模型来保存游戏状态,然后在绘制 GUI 时使用此模型的另一个原因。
- 进一步考虑,您可能需要枚举的二维数组或整数数组,因为网格单元格状态可能会超过 2 个值(真或假),而是 三个 值 -- 未经测试、命中和未命中,如果命中,您可能希望用红色填充椭圆形,如果未命中,则可能需要用白色填充椭圆形。
例如:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import javax.swing.*;
public class GridExample {
private static void createAndShowGui() {
final GridPanel gridPanel = new GridPanel();
JButton resetBtn = new JButton(new AbstractAction("Reset") {
@Override
public void actionPerformed(ActionEvent e) {
gridPanel.reset();
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(resetBtn);
JFrame frame = new JFrame("GridExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gridPanel);
frame.getContentPane().add(btnPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class GridPanel extends JPanel {
private static final int ROWS = 10;
private static final int CELL_WIDTH = 28;
private static final int PAD = 20;
private static final int PREF_W = ROWS * CELL_WIDTH + 2 * PAD;
private static final int PREF_H = PREF_W;
private static final Color GRID_COLOR = Color.blue;
private static final Color CIRCLE_COLOR = Color.red;
private static final int SML_GAP = 2;
private boolean[][] grid = new boolean[ROWS][ROWS];
public GridPanel() {
addMouseListener(new MyMouse());
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void reset() {
grid = new boolean[ROWS][ROWS]; // fills grid with false
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw grid:
g2.setColor(GRID_COLOR);
for (int i = 0; i <= ROWS; i++) {
int x1 = PAD + i * CELL_WIDTH;
int y1 = PAD;
int x2 = x1;
int y2 = PAD + CELL_WIDTH * ROWS;
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x1, y2, x2);
}
// iterate through the grid boolean array
// draw red circles if the grid value is true.
g2.setColor(CIRCLE_COLOR);
int w = CELL_WIDTH - 2 * SML_GAP; // width of the circle to draw
int h = w;
// nested for loop to go through the grid array
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c]) {
int x = PAD + c * CELL_WIDTH + SML_GAP;
int y = PAD + r * CELL_WIDTH + SML_GAP;
g2.fillOval(x, y, w, h);
}
}
}
}
private class MyMouse extends MouseAdapter {
public void mousePressed(MouseEvent e) {
int x = e.getPoint().x;
int y = e.getPoint().y;
if (x < PAD || y < PAD) {
// clicked above or to right of grid
return;
}
int r = (y - PAD) / CELL_WIDTH;
int c = (x - PAD) / CELL_WIDTH;
// if clicked to right or below grid.
// the < 0 part is likely unnecessary, but why not be extra safe?
if (r >= ROWS || c >= ROWS || r < 0 || c < 0) {
return;
}
grid[r][c] = true;
repaint();
}
}
}
我正在尝试为战舰游戏制作 GUI。一个 class 用于创建 GUI 本身,第二个 class 在其之上用于管理游戏中的棋盘。我的问题是,一旦发生鼠标单击,JPanel 就会创建两次(鼠标单击应该是在游戏中开火的位置,然后将其标记为 hit/miss)。我不确定为什么它会创建两次。是因为一个面板过去了吗?下面的代码和代码生成的照片。
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.BorderFactory;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class BattleshipApplet extends JApplet implements MouseListener {
private final JButton playButton = new JButton("Play");
private final JLabel msgBar = new JLabel("Click Play to start game");
private BoardPanel panel;
public BattleshipApplet(){
playButton.addActionListener(this::playButtonClicked);
addMouseListener(this);
}
public void init(){
configureGui();
}
private void configureGui(){
setLayout(new BorderLayout());
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
buttons.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
buttons.add(playButton);
add(buttons, BorderLayout.NORTH);
msgBar.setBorder(BorderFactory.createEmptyBorder(10,10,5,5));
add(createBoardPanel(), BorderLayout.CENTER);
add(msgBar, BorderLayout.SOUTH);
}
private BoardPanel createBoardPanel(){
panel = new BoardPanel();
return panel;
}
private void displayMessage(String msg){
msgBar.setText(msg);
}
private void playButtonClicked(ActionEvent event){
displayMessage("Play button clicked!");
}
public void mouseClicked(MouseEvent e) {
panel.mouseClickedAt(e.getX(), e.getY());
e.consume();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
}
板class使用JPanel
[![import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class BoardPanel extends JPanel {
int mx, my;
boolean rect1Clicked;
//gamePlay a;
public void init(){
rect1Clicked = false;
}
/***Your applet shall show the status of the board before and after
each shot, including the number of shots made and the status of
each place (no shot or hit/miss shot). ***/
public void paint(Graphics g){
boolean miss = false;
for (int i=0; i<11; i++){
g.setColor(Color.blue);
g.drawLine(20,20+i*28, 300, 20+i*28);
}
for (int i=0; i<11; i++)
g.drawLine(20+i*28,20,20+i*28,300);
//if inside board
if(rect1Clicked == true){
g.setColor(Color.green);
//aligns to square to check in computer board for hit/miss
int bx =(my-20)/28;
int by =(mx-20)/28;
//check hit on board
//if shot was a miss
if(miss == true ){
//update to white
g.setColor(Color.white);
}
//if shot was a hit
if(miss == false){
//update to red
g.setColor(Color.red);
}
//compare to line for fill
int fillx = mx/2;
int filly = my/2 ;
if(mx<=47){
fillx = 20;
}
if(mx>47 && mx<=75){
fillx = 48;
}
if(mx>75 && mx<=103){
fillx = 76;
}
if(mx>103 && mx <=131){
fillx = 104;
}
if(mx>131 && mx<=159){
fillx = 132;
}
if(mx>159 && mx<=187){
fillx = 160;
}
if(mx>187 && mx <=215){
fillx = 188;
}
if(mx>215 && mx <=243){
fillx = 216;
}
if(mx>243 && mx <=271){
fillx = 244;
}
if(mx>271 && mx<=299){
fillx = 272;
}
if(mx>299){
fillx = 300;
}
//y comparisons
if(my<=47){
filly = 20;
}
if(my>47 && my<=75){
filly = 48;
}
if(my>75 && my<=103){
filly = 76;
}
if(my>103 && my <=131){
filly = 104;
}
if(my>131 && my<=159){
filly = 132;
}
if(my>159 && my<=187){
filly = 160;
}
if(my>187 && my <=215){
filly = 188;
}
if(my>215 && my <=243){
filly = 216;
}
if(my>243 && my <=271){
filly = 244;
}
if(my>271 && my<=299){
filly = 272;
}
if(my>299){
filly = 300;
}
g.drawString("("+mx+","+my+")",mx,my);
//25 describes size of square
g.fillOval(fillx, filly, 25, 25);
}
}
public void game(BoardPanel p){
//while game plays
}
public void mouseClickedAt(int x, int y){
mx = x;
my = y;
//user clicked inside of board space
if(mx>20 && mx<300 && my>20 && my<300){
//send to board in MainBattleship
rect1Clicked = true;
}
//updates board
repaint();
}
}][1]][1]
我很迷茫,谢谢你的帮助!
建议:
- 不要重写 JPanel 的
paint
方法,而是重写其paintComponent
方法,因为这样更安全,稍后当您想要制作动画时,动画效果会更流畅。 - 最重要的是,您几乎总是需要在自己的绘画方法中调用 super 的绘画方法,否则 JPanel 不会删除需要清理的先前图像工件。因此,如果您继续覆盖 paint(尽管我不建议这样做),您覆盖的第一行应该是
super.paint(g);
,或者如果您覆盖paintComponent
,那么第一行应该是super.paintComponent(g);
,当然假设你的方法使用名为g
. 的 Graphics 参数
- 此外,将 MouseListener 添加到 JPanel,而不是 到小程序,因为面板上的鼠标单击位置对您很重要。
- 另外,使用组件网格或一些数学来极大地简化您的代码——丑陋的 if 块列表应该被更简单的 for 循环所取代,一个使用基本数学的循环。
- 考虑从您的绘画方法中提取上面讨论的逻辑,并将其提取到某种模型中,也许是二维布尔数组。
- 您在代码中使用了很多 "magic" 数字,这些数字应该更改为常量和数学派生数字的组合。
- 注意如果您单击您的 GUI,然后调整它的大小,或者如果您最小化它然后恢复它会发生什么——除了最后一个按下的红色圆圈之外,您会丢失所有红色圆圈。这是使用布尔网格或其他模型来保存游戏状态,然后在绘制 GUI 时使用此模型的另一个原因。
- 进一步考虑,您可能需要枚举的二维数组或整数数组,因为网格单元格状态可能会超过 2 个值(真或假),而是 三个 值 -- 未经测试、命中和未命中,如果命中,您可能希望用红色填充椭圆形,如果未命中,则可能需要用白色填充椭圆形。
例如:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import javax.swing.*;
public class GridExample {
private static void createAndShowGui() {
final GridPanel gridPanel = new GridPanel();
JButton resetBtn = new JButton(new AbstractAction("Reset") {
@Override
public void actionPerformed(ActionEvent e) {
gridPanel.reset();
}
});
JPanel btnPanel = new JPanel();
btnPanel.add(resetBtn);
JFrame frame = new JFrame("GridExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gridPanel);
frame.getContentPane().add(btnPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class GridPanel extends JPanel {
private static final int ROWS = 10;
private static final int CELL_WIDTH = 28;
private static final int PAD = 20;
private static final int PREF_W = ROWS * CELL_WIDTH + 2 * PAD;
private static final int PREF_H = PREF_W;
private static final Color GRID_COLOR = Color.blue;
private static final Color CIRCLE_COLOR = Color.red;
private static final int SML_GAP = 2;
private boolean[][] grid = new boolean[ROWS][ROWS];
public GridPanel() {
addMouseListener(new MyMouse());
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void reset() {
grid = new boolean[ROWS][ROWS]; // fills grid with false
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw grid:
g2.setColor(GRID_COLOR);
for (int i = 0; i <= ROWS; i++) {
int x1 = PAD + i * CELL_WIDTH;
int y1 = PAD;
int x2 = x1;
int y2 = PAD + CELL_WIDTH * ROWS;
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x1, y2, x2);
}
// iterate through the grid boolean array
// draw red circles if the grid value is true.
g2.setColor(CIRCLE_COLOR);
int w = CELL_WIDTH - 2 * SML_GAP; // width of the circle to draw
int h = w;
// nested for loop to go through the grid array
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c]) {
int x = PAD + c * CELL_WIDTH + SML_GAP;
int y = PAD + r * CELL_WIDTH + SML_GAP;
g2.fillOval(x, y, w, h);
}
}
}
}
private class MyMouse extends MouseAdapter {
public void mousePressed(MouseEvent e) {
int x = e.getPoint().x;
int y = e.getPoint().y;
if (x < PAD || y < PAD) {
// clicked above or to right of grid
return;
}
int r = (y - PAD) / CELL_WIDTH;
int c = (x - PAD) / CELL_WIDTH;
// if clicked to right or below grid.
// the < 0 part is likely unnecessary, but why not be extra safe?
if (r >= ROWS || c >= ROWS || r < 0 || c < 0) {
return;
}
grid[r][c] = true;
repaint();
}
}
}