【问题标题】:Extending JPanel to draw Image. But on the same Panel added to JFrame扩展 JPanel 以绘制图像。但是在同一个面板上添加到 JFrame
【发布时间】:2018-10-16 01:51:25
【问题描述】:

更新:正如 Dmich 所指出的,因为我在面板类之外进行绘图,这会导致对 MyPanel 和 Animation 类的初始化的递归调用。那么我怎样才能在 MyPanel 上完成绘图,在 Animation 类中,但没有这个问题。

我的代码有一个非常具体的问题,我不知道如何描述正在发生的事情,但我会尽力而为。我尝试搜索 stackOverFLow,但问题是我什至不知道要搜索 什么

我去:

为了尽可能地组织这一点,我将首先编写我正在处理的所有类。

public class MyPanel extends JPanel implements Runnable{
private Animation anim = new Animation();
}
public class Animation extends ??? implements KeyListener{}

所以我有一个名为 MyPanel 的类,它扩展了 JPanel。我已经将这个类添加到我的 JFrame 中使用 add(new MyPanel()) 我在 MyPanel 中使用一个线程,它调用 @Overrid public void paintComponent(Graphics g) paintComponent 调用 Animation 中的一个方法,我在该方法中将图像绘制到屏幕g2d.drawImage(image, int, int, ImageObserver)。问题是使用这种方法我需要一个 ImageObserver,如果 Animation 扩展了 JPanel,我可以得到它。但是,如果我扩展 JPanel,则不会在我的 JFrame 上绘制任何内容,因为这是一个未添加到 JFrame 的新 jPanel。

但如果我扩展 MyPanel(添加到我的 JFrame 中)我会得到一大堆错误。

Eclipse 错误:

Exception in thread "main" java.lang.StackOverflowError
at java.awt.Component.setBackground(Unknown Source)
at javax.swing.JComponent.setBackground(Unknown Source)
at javax.swing.LookAndFeel.installColors(Unknown Source)
at javax.swing.LookAndFeel.installColorsAndFont(Unknown Source)
at javax.swing.plaf.basic.BasicPanelUI.installDefaults(Unknown Source)
at javax.swing.plaf.basic.BasicPanelUI.installUI(Unknown Source)
at javax.swing.JComponent.setUI(Unknown Source)
at javax.swing.JPanel.setUI(Unknown Source)
at javax.swing.JPanel.updateUI(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source) 

在此错误消息下,这两行一直持续到被 eclipse 终止。

at MyPanel.<init>(MyPanel.java:9)
at Animation.<init>(Animation.java:9)

任何帮助将不胜感激。

更新 2 按照建议添加代码示例:

import javax.swing.JFrame;

public class Simulation extends JFrame {
private MyPanel panel = new MyPanel();
public Simulation() {
    initUI();
}

private void initUI() {

    add(panel);
    setResizable(false);
    pack();

    setTitle("Simulation");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocationRelativeTo(null); // centers
}
public static void main(String[] args) {

    Simulation ex = new Simulation();
    ex.setVisible(true);
}
}


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

public class MyPanel extends JPanel implements Runnable { 
private Animation anim;
public MyPanel() {
    anim = new Animation();
    initPanel();
}

private void initPanel() {
    //other customizables
}

@Override
public void run() {
//Thread that calls repaint();
}

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D) g;
    anim.step(g2D, this);
}
}


import java.awt.Graphics2D;

public class Animation {
private Sprite player;
private KeyBinder kB;

public Animation() {
    player = new Sprite();
  kB = new KeyBinder(player);
}

public void step(Graphics2D g2d, JPanel p) {
    player.move();
    drawSprite(g2d, p);
}

private void drawSprite(Graphics2D g2d, JPanel p) {
    g2d.drawImage(player.getImage(), player.getX(), player.getY(), p);
}
}

import java.awt.Image;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;

public class Sprite {
private final String PLAYER_IMAGE_FILE = "Image location";
private Image image;
private int width, height;
private int x, y, dx, dy;

public Sprite() {
    x = 0;
    y = 0;
    loadImage();
}

private void loadImage() {
    ImageIcon ii = new ImageIcon(PLAYER_IMAGE_FILE);
    image = ii.getImage();

    width = image.getWidth(null);
    height = image.getHeight(null);
}

public void move() {
    x += dx;
    y += dy;
}

public void keyPressed(KeyEvent e) {

    //assings meaning to keypressed
}

public void keyReleased(KeyEvent e) {

    //assings meaning to keyReleased
}

public int getX() {
    return x;
}

public int getY() {
    return y;
}

public int getW() {
    return width;
}

public int getH() {
    return height;
}

public Image getImage() {
    return image;
}
}

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class KeyBinder extends MyPanel implements KeyListener {
Sprite p;
public KeyBinder(Sprite player) {
    p = player;
    addKeyListener(this);
    setFocusable(true);
    setFocusTraversalKeysEnabled(false);
}

@Override
public void keyPressed(KeyEvent e) {
    p.keyPressed(e);
}
@Override
public void keyReleased(KeyEvent e) {
    p.keyReleased(e);
}
@Override
public void keyTyped(KeyEvent e) {

}
}

通过尝试对代码进行故障排除,我对代码进行了更改,主要是通过尝试将 JPanel 传递给 Animation.step 方法并为按键监听创建一个单独的类。

新的错误列表大体相同:

Exception in thread "main" java.lang.StackOverflowError
at java.awt.Component.setBackground(Unknown Source)
at javax.swing.JComponent.setBackground(Unknown Source)
at javax.swing.LookAndFeel.installColors(Unknown Source)
at javax.swing.LookAndFeel.installColorsAndFont(Unknown Source)
at javax.swing.plaf.basic.BasicPanelUI.installDefaults(Unknown Source)
at javax.swing.plaf.basic.BasicPanelUI.installUI(Unknown Source)
at javax.swing.JComponent.setUI(Unknown Source)
at javax.swing.JPanel.setUI(Unknown Source)
at javax.swing.JPanel.updateUI(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source)
at javax.swing.JPanel.<init>(Unknown Source)

然后是一个无限循环,直到以下三行终止:

at MyPanel.<init>(MyPanel.java:10)
at KeyBinder.<init>(KeyBinder.java:6)
at Animation.<init>(Animation.java:11)

【问题讨论】:

  • because this is a new jPanel which is not added to the JFrame. - 为什么要创建一个新的 JPanel?创建框架时,应创建一次具有自定义绘画的面板。然后将面板添加到框架中。阅读 Custom Painting 上的 Swing 教程部分。为基础。一旦您了解了基础知识,如果您需要动画,那么您可以使用Swing Timer。该教程还有一个关于How to Use Swing Timers的部分。
  • 当你有动画扩展面板时,面板包含一个被初始化的动画。由于每个动画都是一个面板,并且每个面板在初始化时都会初始化一个新动画,因此您可以递归调用动画和面板初始化。
  • 并且不要将您的自定义面板称为“面板”。名称中有一个 AWT 类,令人困惑。类名应该更具描述性。
  • @camickr 我正在使用线程来制作动画,所以不会使用 Swing Timer。我所说的“新 JPanel”的意思是我没有扩展使用(添加新面板())添加到我的 JFrame 的 JPanel。感谢您的回复,将更改面板名称。
  • @Dmich 这解释了为什么我有一个无限的初始化循环以及为什么 Eclipse 终止了我的程序。感谢您的回复!

标签: java swing jpanel


【解决方案1】:

这是另一个继承不良的小伙子的案例。我通过在 MYPanel 类中实现 KeyListener 并将 MyPanel 作为参数传递给 Animation 来修复它。尝试将 keyListener 实现到 JPanel 但来自不同的类对我来说是不好的做法。

【讨论】:

    【解决方案2】:

    所以我有一个名为 Panel 的类,它扩展了 JPanel。我已经使用 add(new Panel()) 将这个类添加到我的 JFrame 我在 Panel 中使用一个线程,它调用 @Overrid public void paintComponent(Graphics g)

    首先,Swing 不是线程安全的,您不应该从事件调度线程的上下文之外更新 UI。

    其次,绝不应该有任何情况下您会直接调用paintComponent,也永远不需要它是public,因为不应该调用它。

    详情请见Concurrency in Swing

    paintComponent 调用 Animation 中的一个方法,在该方法中我将图像绘制到屏幕 g2d.drawImage(image, int, int, ImageObserver)。问题是使用这种方法我需要一个 ImageObserver,如果 Animation 扩展了 JPanel,我可以得到它。

    为什么不让Animation“可绘制”,提供一种方法,要求调用者传递您完成工作所需的信息?

    public class Animation {
        public void paint(Graphics2D g2d, ImageObserver observer) {
            //...
        }
    }
    

    您还可以将其他可能需要的信息传递给paint 方法,例如对当前状态进行建模的模型。

    这基本上是一个委托模型 - 您将特定任务的责任“委托”给另一个类

    KeyListener 呢?

    我听到你问了。

    好吧,首先,处理它可能不是Animation 的责任。相反,控制器应该修改状态,以响应最终由渲染器绘制的事件

    另外,您应该改用Key bindings API,它将解决与KeyListener相关的不可靠性问题

    【讨论】:

      【解决方案3】:

      我不建议让类扩展 JPanel,而是在实例化时接受 JPanel 参数:

      public class Panel implements Runnable{
          private JPanel panel;
      
          public Panel(JPanel panel){
              this.panel = panel;
          }
      }
      

      此外,我认为让 Panel 类具有扩展组件是明智的,以便在将其添加到标题中提到的现有面板时可以无缝集成:

      public class Panel extends Component implements Runnable{
          private JPanel panel;
      
          public Panel(JPanel panel){
              this.panel = panel;
          }
      }
      

      如果这不起作用或您对此有其他疑问,请告诉我!

      【讨论】:

      • 谢谢你的回答,我觉得有误会。我不想要多个面板,而是想要一个用于绘制图像的面板。我遇到的问题是我想做我的动画是我的动画类,在我的面板类之外。但正如@Dmich 所说,这会导致对我的面板和动画初始化的递归调用。也许我弄错了,因为我不明白你的解决方案是否是为了解决这个问题,请告诉我。
      猜你喜欢
      • 2012-10-17
      • 1970-01-01
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多