Reference of graphics 2d object doesn't work in orphan Thread

回眸只為那壹抹淺笑 提交于 2021-01-28 06:23:51

问题


I am trying to design a simple game using Graphics2D in a JPanel. I am able to draw normal objects by overriding the paintComponent() method. But when I reference the Graphics2D object inside a orphan Thread, it does not work. Where am I going wrong?

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

Here is the complete code for reference. This is essentially a 'ping pong ball' game. Its working well but I am not able to highlight an increase in score when the ball hits the striker. The important part of code is highlighted. It's 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;
    }
}

回答1:


I don't think many people would consider almost 250 LOC to be 'short' (though I must admit I was deliberately vague when writing the SSCCE document). OTOH I adapted my shorter source seen here to an animated example that shows a 'fade effect' on mouse clicks. Adapting it to your needs is left as an exercise for ..you.

This source shows how to change the drawn string over a period of 5 seconds. It uses the same Thread (the EDT) for both the main (bouncing ball) and fade animation.

Fading strikes

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



回答2:


  • everything inside paintComponent is repainted (automatically) on every mouse, key and internall methods implemented in API, then you thread probably never ended, there can be bunch of concurently Threads, nothing is repainted, displayed

  • output to the Swing GUI must be done on EDT

  • use Swing Timer instead of new Thread(new Runnable(){

  • call repaint()




回答3:


As i understand - you save the Graphics2D object into g2d variable and trying to paint something onto it from a separate thread later? If so - don't do it. It is a really bad thing to do. Really.

If you want to modify (animate/change) whatever is painted on your component - simply change the data/model which affects the painting and than repaint the whole component or its modified part (any rectangle within the component bounds).

For example in your example case - keep painted string coordinates outside the paint method and modify them in a separate thread and then just call repaint on the component each time you change them. With each repaint string will be painted at the updated coordinates.

Also note that repaint might be called outside of the EDT (Event Dispatch Thread) as it will perform the actual repaint in EDT anyway.

Here is some random example of animation:

public class AnimationTest
{
    private static List<Point> locationData = new ArrayList<Point> ();
    private static List<Boolean> directionData = new ArrayList<Boolean> ();

    public static void main ( String[] args )
    {
        locationData.add ( new Point ( 5, 25 ) );
        directionData.add ( true );

        final JComponent canvas = new JComponent ()
        {
            protected void paintComponent ( Graphics g )
            {
                super.paintComponent ( g );

                Graphics2D g2d = ( Graphics2D ) g;

                for ( int i = 0; i < locationData.size (); i++ )
                {
                    Point p = locationData.get ( i );
                    g2d.drawString ( "Some string #" + i, p.x, p.y );
                }
            }
        };

        canvas.addMouseListener ( new MouseAdapter ()
        {
            public void mousePressed ( MouseEvent e )
            {
                locationData.add ( e.getPoint () );
                directionData.add ( true );
                canvas.repaint ();
            }
        } );

        JFrame frame = new JFrame ();
        frame.getContentPane ().setLayout ( new BorderLayout () );
        frame.getContentPane ().add ( canvas );
        frame.setSize ( 500, 500 );
        frame.setLocationRelativeTo ( null );
        frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
        frame.setVisible ( true );

        Timer timer = new Timer ( 1000 / 48, new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                for ( int i = 0; i < locationData.size (); i++ )
                {
                    Point p = locationData.get ( i );
                    if ( directionData.get ( i ) )
                    {
                        if ( p.y < canvas.getHeight () - 1 )
                        {
                            p.y += 1;
                        }
                        else
                        {
                            directionData.set ( i, false );
                        }
                    }
                    else
                    {
                        if ( p.y > 20 )
                        {
                            p.y -= 1;
                        }
                        else
                        {
                            directionData.set ( i, true );
                        }
                    }
                }
                canvas.repaint ();
            }
        } );
        timer.start ();
    }
}

You can find here:

  1. Data lists on which painting and animation are based with single initial element
  2. Data modification through mouse interaction
  3. Proper canvas update on any data changes

Example is not too optimized, but should be enough to understand the concept.




回答4:


Print the graphicsobject, if it changes between calls in paintComponent it means the one you are passing to the orphan thread is a dead object which swing doesn't use anymore for painting on the current component.




回答5:


Swing is a platform-independent, Model-View-Controller GUI framework for Java, which follows a single-threaded programming model.

You should use dispatcher. http://en.wikipedia.org/wiki/Event_Dispatch_Thread



来源:https://stackoverflow.com/questions/16545075/reference-of-graphics-2d-object-doesnt-work-in-orphan-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!