Java 图形2d对象的引用在孤立线程中不起作用

Java 图形2d对象的引用在孤立线程中不起作用,java,multithreading,swing,graphics2d,paintcomponent,Java,Multithreading,Swing,Graphics2d,Paintcomponent,我试图在JPanel中使用Graphics2D设计一个简单的游戏。我可以通过重写paintComponent方法来绘制法线对象。但是,当我在孤立线程中引用Graphics2D对象时,它不起作用。我哪里做错了 public void paintComponent(Graphics g) { super.paintComponent(g); g2d = (Graphics2D) g; g2d.drawString("sample",60,100); //Works fin

我试图在JPanel中使用Graphics2D设计一个简单的游戏。我可以通过重写paintComponent方法来绘制法线对象。但是,当我在孤立线程中引用Graphics2D对象时,它不起作用。我哪里做错了

public void paintComponent(Graphics g) {

    super.paintComponent(g);
    g2d = (Graphics2D) g;

    g2d.drawString("sample",60,100); //Works fine

    if(<Certain Condition>){
       new Thread(new Runnable(){
            //Some Code Here
            public void run() {
               try{
                 g2d.drawString("sample2",60,100); //Does not work.. :(
                 System.out.println("Test Print"); //Shows Output
               }
               catch (Exception e)
               {
               }
             }
       }).start();
   }
}
下面是完整的代码供参考。这本质上是一种“乒乓球”游戏。这很好,但我不能强调当球击中前锋时得分的增加。代码的重要部分将突出显示。是SSCCE

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

import java.util.Random;

public class MovingBall extends JPanel {
    int XPos, YPos;
    int speedX, speedY;
    int diameter;
    private JButton jButton1 = new JButton();
    private JButton jButton2 = new JButton();
    private JLabel jLabel1 = new JLabel();
    private static Timer timer;
    private static MovingBall movingball;
    private int w,h;

    private int strikerHeight;
    private int strikerWidth;

    private int score;
    private boolean isBallMoving;

    int strikerYPos;
    Graphics2D g2d;

    public MovingBall() {

        //Striker Properties
        strikerHeight = 100;
        strikerWidth = 20;
        strikerYPos = strikerHeight/2;

        //Ball Properties
        isBallMoving = false;
        XPos = strikerWidth + 5;
        YPos = 0;
        Random r = new Random();
        speedX = 2+ Math.abs(r.nextInt()) % 5;
        speedY = 2+ Math.abs(r.nextInt()) % 5;
        diameter = 50;

        //UI Objects
        try {
            jbInit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        movingball = this; //Helps to access the current class object in inner classes

        //Create a timer for animation
        timer = new Timer(1, new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                movingball.repaint();
            }    
        });
        timer.start();
    }

    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        g2d = (Graphics2D) g;

        Dimension size = getSize();
        Insets insets = getInsets();

        w =  size.width - insets.left - insets.right;
        h =  size.height - insets.top - insets.bottom;

        //Paint the striker
        g2d.setColor(Color.DARK_GRAY);
        if(strikerYPos < strikerHeight/2) //Top End
            g2d.fillRect(0,0, strikerWidth, strikerHeight);
        else if(strikerYPos > (h-strikerHeight/2)) //Bottom End
            g2d.fillRect(0,h-strikerHeight, strikerWidth, strikerHeight);
        else //Anywhere in the middle
            g2d.fillRect(0,strikerYPos - (strikerHeight/2), strikerWidth, strikerHeight);

        //Paint the ball
        if (isBallMoving) {
            XPos += speedX;
            YPos += speedY;

            g2d.drawOval(XPos, YPos, diameter,diameter);

            if((XPos+diameter) >= w)
            {
                //speedX *= -1;
                speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                XPos = w-diameter-1;
            }
            if(XPos <= strikerWidth)
            {
                if((YPos+diameter/2) >= (strikerYPos-strikerHeight/2) && (YPos+diameter/2) <= (strikerYPos+strikerHeight/2))
                {
                    score++;

                    //////////////////////////////////////////////////////////////////////
                    /////THIS IS THE PART TO FOCUS ON///////////////////////////////////////
                    /////WHEN THE BALL HITS THE STRIKER, I SHOW A '+1' TEXT FADING UPWARDS FROM THE POINT OF HIT
                    /////(THIS IS TO HIGHLIGHT A +1 INCREASE IN SCORE)///////////////////
                    //////NOW SINCE THE BALL MAY HIT THE STRIKER AGAIN BEFORE THE PREVIOUS +1 HAS COMPLETELY FADED,
                    //////I HAVE MADE THIS SIMPLE THREAD TO CREATE A +1 EVERY TIME THERE IS A HIT. SO THERE CAN BE MULTIPLE
                    //////+1 ON THE SCREEN.
                    //-------------------------------SADLY, SOMETHING IS WRONG-------------------

                    //Print a '+1' to show score increase
                    new Thread(new Runnable(){
                        int yStart = strikerYPos;
                        int fadeLength = 0;
                        Timer pointTimer;
                        int MAX_FADE_LEN = 50;

                        public void run() {
                            try
                            {

                                pointTimer = new Timer(1, new ActionListener() {
                                         public void actionPerformed(ActionEvent evt) {
                                            if(fadeLength >= MAX_FADE_LEN)
                                                pointTimer.stop();
                                            g2d.setColor(new Color(0,0,0,255));
                                            g2d.setFont(new Font("Times",Font.BOLD,20));
                                            g2d.drawString("+1",60,yStart - fadeLength);
                                            g2d.drawOval(100,100,50,50);
                                            System.out.println("Drawn +1 at x = " + 60 + " y = " + (yStart - fadeLength));
                                            fadeLength++;
                                         }    
                                        });
                                pointTimer.start();
                            }
                            catch (Exception e)
                            {

                            }
                        }

                    }).start();
                    ////////////////THREAD ENDS HERE//////////////////////
                }
                else
                {
                    score--;
                }

                //SHOW THE SCORE ON THE LABEL
                jLabel1.setText("Score: " + score);
                speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                XPos = strikerWidth+1;
            }

            if(YPos <= 0)
            {
                speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                YPos = 0;
            }
            if((YPos+diameter) >= h)
            {
                speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                YPos = h-diameter;
            }
        } else {
            g2d.drawOval(XPos,YPos,diameter,diameter);
            return;
        }
    }

    public static void main(String[] args) {

        JFrame frame = new JFrame("Magic Ball");
        movingball = new MovingBall();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(movingball);
        frame.setSize(450, 700);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void jbInit() throws Exception {
        jButton1.setText("Start");
        jButton1.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        jButton1_actionPerformed(e);
                    }
                });
        jButton2.setText("Stop");
        jButton2.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        jButton2_actionPerformed(e);
                    }
                });
        jLabel1.setText("Score:0");
        this.add(jButton1, null);
        this.add(jButton2, null);
        this.add(jLabel1, null);
        this.setBackground(Color.white);
        this.addMouseMotionListener(new MouseMotionListener() {
                    public void mouseMoved(MouseEvent e) {
                        int coordX = e.getX();
                        if(coordX < 200)
                            strikerYPos = e.getY();
                    }

                    public void mouseDragged(MouseEvent e) {
                    }
                });
    }

    private void jButton1_actionPerformed(ActionEvent e) {
        if(!isBallMoving)
            isBallMoving = true;
    }

    private void jButton2_actionPerformed(ActionEvent e) {
        isBallMoving = false;
    }
}
paintComponent中的所有内容都会在API中实现的每个鼠标、键和内部方法上自动重新绘制,然后您的线程可能永远不会结束,可能会有一堆并发线程,没有任何内容被重新绘制、显示

到Swing GUI的输出必须在EDT上完成

使用Swing计时器而不是new Threadnew Runnable{

呼叫重绘

paintComponent中的所有内容都会在API中实现的每个鼠标、键和内部方法上自动重新绘制,然后您的线程可能永远不会结束,可能会有一堆并发线程,没有任何内容被重新绘制、显示

到Swing GUI的输出必须在EDT上完成

使用Swing计时器而不是new Threadnew Runnable{

呼叫重绘


打印graphicsobject,如果它在paintComponent中的调用之间发生更改,则表示您传递给孤立线程的对象是死对象,swing不再使用它在当前组件上进行绘制。

打印graphicsobject,如果它在paintComponent中的调用之间发生更改,则表示您传递给孤立线程的对象是一个死对象,swing不再使用它在当前组件上绘制。

据我所知-您将Graphics2D对象保存到g2d变量中,并尝试稍后从单独的线程在其上绘制一些内容?如果是,请不要这样做。这是一件非常糟糕的事情。真的

如果要修改动画/更改在组件上绘制的内容,只需更改影响绘制的数据/模型,然后重新绘制整个组件或其修改部分组件边界内的任何矩形

例如,在您的示例中-将绘制的字符串坐标保持在paint方法之外,并在单独的线程中修改它们,然后每次更改它们时只调用组件上的repaint。每次重新绘制时,字符串将以更新的坐标绘制

还请注意,重新绘制可能会在EDT事件调度线程之外调用,因为它无论如何都会在EDT中执行实际的重新绘制

下面是一些随机的动画示例:

你可以在这里找到:

使用单个初始元素绘制和动画所基于的数据列表 通过鼠标交互修改数据 对任何数据更改进行正确的画布更新
示例不是太优化,但应该足以理解这个概念。

据我所知-您将Graphics2D对象保存到g2d变量中,并尝试稍后从单独的线程在其上绘制一些内容?如果是,请不要这样做。这是一件非常糟糕的事情。真的

如果要修改动画/更改在组件上绘制的内容,只需更改影响绘制的数据/模型,然后重新绘制整个组件或其修改部分组件边界内的任何矩形

例如,在您的示例中-将绘制的字符串坐标保持在paint方法之外,并在单独的线程中修改它们,然后每次更改它们时只调用组件上的repaint。每次重新绘制时,字符串将以更新的坐标绘制

还请注意,重新绘制可能会在EDT事件调度线程之外调用,因为它无论如何都会在EDT中执行实际的重新绘制

下面是一些随机的动画示例:

你可以在这里找到:

使用单个初始元素绘制和动画所基于的数据列表 通过鼠标交互修改数据 对任何数据更改进行正确的画布更新
示例并没有太优化,但应该足以理解这个概念。

Swing是一个独立于平台的Java模型-视图-控制器GUI框架,遵循单线程编程模型


您应该使用dispatcher。

Swing是一个独立于平台的Java模型-视图-控制器GUI框架,遵循单线程编程模型


<>你应该使用调度器。

< P>我不认为很多人会认为差不多250个LOC是“短”,尽管我必须承认我在编写SSCCE文档时故意含糊不清。Otoh我把我的短源修改为一个动画例子,它显示了鼠标点击的“渐变效果”。欧

此源代码显示如何在5秒内更改绘制的字符串。它使用相同的线程EDT处理主反弹球和淡入淡出动画

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;

class ShapeCollision {

    private BufferedImage img;
    private Area walls;
    int x;
    int y;
    int xDelta = 3;
    int yDelta = 2;
    ArrayList<Strike> strikes;

    /**
     * A method to determine if two instances of Area intersect
     */
    public boolean doAreasCollide(Area area1, Area area2) {
        boolean collide = false;

        Area collide1 = new Area(area1);
        collide1.subtract(area2);
        if (!collide1.equals(area1)) {
            collide = true;
        }

        Area collide2 = new Area(area2);
        collide2.subtract(area1);
        if (!collide2.equals(area2)) {
            collide = true;
        }

        return collide;
    }

    ShapeCollision() {
        int w = 400;
        int h = 200;
        img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        final JLabel imageLabel = new JLabel(new ImageIcon(img));
        x = w / 2;
        y = h / 2;

        strikes = new ArrayList<Strike>();

        MouseListener strikeListener = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                Strike s = new Strike(e.getPoint(),System.currentTimeMillis());
                strikes.add(s);
            }
        };
        imageLabel.addMouseListener(strikeListener);

        walls = new Area(new Rectangle(0, 0, w, h));

        ActionListener animate = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                animate();
                imageLabel.repaint();
            }
        };
        Timer timer = new Timer(50, animate);

        timer.start();
        JOptionPane.showMessageDialog(null, imageLabel);
        timer.stop();
    }

    public void animate() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());
        x += xDelta;
        y += yDelta;
        int s = 15;
        Area player = new Area(new Ellipse2D.Double(x, y, s, s));

        // Acid test of edge collision;
        if (doAreasCollide(player, walls)) {
            if (x + s > img.getWidth() || x < 0) {
                xDelta *= -1;
            }
            if (y + s > img.getHeight() || y < 0) {
                yDelta *= -1;
            }
        }
        g.setColor(Color.ORANGE);
        g.setColor(Color.YELLOW);
        g.fill(player);

        for (Strike strike : strikes) {
            strike.draw(g);
        }

        g.dispose();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                new ShapeCollision();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

class Strike {

    private Point point;
    private long started;
    private final long DURATION = 5000;
    private boolean expired = false;

    Strike(Point point, long time) {
        this.point = point;
        started = time;
    }

    public void draw(Graphics g) {
        long now = System.currentTimeMillis();
        long age = now - started;
        if (age>DURATION) {
            expired = true;
            return;
        }
        double fraction = 1d-((double)age/(double)DURATION);
        int alpha = (int)(fraction*255d);
        Color c = new Color(255,255,255,alpha);
        g.setColor(c);
        String s = point.x + "," + point.y;
        g.drawString( s, point.x, point.y );
    }

    public boolean isExpired() {
        return expired;
    }
}

我不认为很多人会考虑。 虽然我必须承认我在写SSCCE文件时故意含糊其辞,但几乎250 LOC被认为是“短的”。OTOH我将我看到的较短的源代码改编为一个动画示例,该示例显示了鼠标点击时的“淡入淡出效果”。让它适应你的需要是留给你的一个练习

此源显示如何在5秒内更改绘制的字符串。它对主反弹球和淡入淡出动画使用相同的线程EDT

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;

class ShapeCollision {

    private BufferedImage img;
    private Area walls;
    int x;
    int y;
    int xDelta = 3;
    int yDelta = 2;
    ArrayList<Strike> strikes;

    /**
     * A method to determine if two instances of Area intersect
     */
    public boolean doAreasCollide(Area area1, Area area2) {
        boolean collide = false;

        Area collide1 = new Area(area1);
        collide1.subtract(area2);
        if (!collide1.equals(area1)) {
            collide = true;
        }

        Area collide2 = new Area(area2);
        collide2.subtract(area1);
        if (!collide2.equals(area2)) {
            collide = true;
        }

        return collide;
    }

    ShapeCollision() {
        int w = 400;
        int h = 200;
        img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        final JLabel imageLabel = new JLabel(new ImageIcon(img));
        x = w / 2;
        y = h / 2;

        strikes = new ArrayList<Strike>();

        MouseListener strikeListener = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                Strike s = new Strike(e.getPoint(),System.currentTimeMillis());
                strikes.add(s);
            }
        };
        imageLabel.addMouseListener(strikeListener);

        walls = new Area(new Rectangle(0, 0, w, h));

        ActionListener animate = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                animate();
                imageLabel.repaint();
            }
        };
        Timer timer = new Timer(50, animate);

        timer.start();
        JOptionPane.showMessageDialog(null, imageLabel);
        timer.stop();
    }

    public void animate() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());
        x += xDelta;
        y += yDelta;
        int s = 15;
        Area player = new Area(new Ellipse2D.Double(x, y, s, s));

        // Acid test of edge collision;
        if (doAreasCollide(player, walls)) {
            if (x + s > img.getWidth() || x < 0) {
                xDelta *= -1;
            }
            if (y + s > img.getHeight() || y < 0) {
                yDelta *= -1;
            }
        }
        g.setColor(Color.ORANGE);
        g.setColor(Color.YELLOW);
        g.fill(player);

        for (Strike strike : strikes) {
            strike.draw(g);
        }

        g.dispose();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                new ShapeCollision();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

class Strike {

    private Point point;
    private long started;
    private final long DURATION = 5000;
    private boolean expired = false;

    Strike(Point point, long time) {
        this.point = point;
        started = time;
    }

    public void draw(Graphics g) {
        long now = System.currentTimeMillis();
        long age = now - started;
        if (age>DURATION) {
            expired = true;
            return;
        }
        double fraction = 1d-((double)age/(double)DURATION);
        int alpha = (int)(fraction*255d);
        Color c = new Color(255,255,255,alpha);
        g.setColor(c);
        String s = point.x + "," + point.y;
        g.drawString( s, point.x, point.y );
    }

    public boolean isExpired() {
        return expired;
    }
}

我已经在程序中详细实现了swing定时器和重绘。没有在这里写完整的代码。。定时器会影响这个吗?我已经在程序中详细实现了swing定时器和重绘。没有在这里写完整的代码。。计时器是否会影响这一点?为了更快地获得更好的帮助,请发布一个。是的,Andrew,为了更好地理解,现在添加了完整的代码。没有Ashis。这既不是“完整代码”,也不是没有主字符串[]或其他东西可以在屏幕上显示,而且它肯定不是SSCCE,这是我建议您发布的..@Andrew。。请现在检查。它符合SSCCE标准。感谢您的回复。为了更快地获得更好的帮助,请发布一个。是的,Andrew,为了更好地理解,现在添加了完整的代码。没有Ashis。这既不是“完整代码”,也不是没有主字符串[]或其他东西可以在屏幕上显示,而且它肯定不是SSCCE,这是我建议您发布的..@Andrew。。请现在检查。它符合SSCCE标准。谢谢回复。但是我已经在全局范围内声明了Graphics2D引用变量。现在,正在使用单独的计时器重复调用repaint方法。这会定期刷新引用。我认为这应该是可行的。但是我已经在全局范围内声明了Graphics2D引用变量。现在,正在使用单独的计时器重复调用repaint方法。这会定期刷新引用。我想这应该行得通谢谢你安德鲁我想我现在明白了。很高兴你把它整理好了谢谢你,安德鲁我想我现在明白了。很高兴你把它整理好了