Draw on JPanel from other class

狂风中的少年 提交于 2021-02-05 09:48:53

问题


In my program I try to paint on a JPanel when the mouse is pressed. The mousePressed method is just to test the painting from another class. Later on the spawn method will be called by other class methods. When I press the mouse button spawnPedestrian() is called, but no Pedestrian is painted. Below is a running example with code from my project. If you create a project Roundabout and paste this code in it, you should be able to run it (images are hotlinked). How to fix the spawnPedestrian() method?

public class Roundabout extends JFrame {

public static Surface surface;

public Roundabout() {
    initUI();
}

private void initUI() {
    setTitle("Roundabout");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    surface = new Surface();
    add(surface); 
    this.addMouseListener(new MouseAdapter() {// empty implementation of all
        // MouseListener`s methods
        @Override
        public void mousePressed(MouseEvent e) {
            //Spawn
            Spawn sp = new Spawn();
            sp.spawnPedestrian(300, 100);
        }
    });

    setSize(1618, 850);
    setLocationRelativeTo(null);
}

public static JPanel getSurface() {
    return surface;
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            Roundabout roundabout = new Roundabout();
            roundabout.setVisible(true);
        }
    });
}

//Track class
class Track {

    BufferedImage track;
    Point trackPosition;
    Point TRACK_POS = new Point(0, 0);

    public Track() {
        try {
            track = ImageIO.read(new URL("http://i.stack.imgur.com/2U3j5.png"));
        } catch (Exception ex) {
            System.out.println("Problem loading track image: " + ex);
        }
        trackPosition = new Point(TRACK_POS.x, TRACK_POS.y);
    }

    public void paint(Graphics g) {
        g.drawImage(track, TRACK_POS.x, TRACK_POS.y, null);
    }

}

//Surface class
public class Surface extends JPanel {

    Track track = new Track();

    public List<Vehicle> toDraw = new ArrayList<>();

    public Surface() {
        Pedestrian p = new Pedestrian(100, 100);
        toDraw.add(p);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        //setLayout(null);
        track.paint(g);
        //Make sure the track is painted first
        for (Vehicle v : toDraw) {
            v.paint(g);
        }

    }

}

class Pedestrian extends Vehicle {

    BufferedImage pedestrian;
    Point pedestrianPosition;
    double pedestrianRotation = 0;
    int pedestrianW, pedestrianH;

    public Pedestrian(int x, int y) {
        try {
            pedestrian = ImageIO.read(new URL("http://i.stack.imgur.com/wm0I5.png"));
        } catch (IOException e) {
            System.out.println("Problem loading pedestrian images: " + e);
        }

        pedestrianPosition = new Point(x, y);
        pedestrianW = pedestrian.getWidth();
        pedestrianH = pedestrian.getHeight();
    }

    @Override
    public void paint(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;

        g2d.rotate(Math.toRadians(pedestrianRotation), pedestrianPosition.x, pedestrianPosition.y);

        g2d.drawImage(pedestrian, pedestrianPosition.x, pedestrianPosition.y, null);

    }

    @Override
    public void setPath(List<Point> path) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void update(double i) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}

//Spawn class
class Spawn {

    public void spawnPedestrian(int x, int y) {
        //Create a new pedestrian.        

        System.out.println("Spawn a pedestrian.");
        Pedestrian p = new Pedestrian(x, y);

        Roundabout.surface.toDraw.add(p);
        Roundabout.surface.revalidate();
        Roundabout.surface.repaint();            

    }
}

public abstract class Vehicle {

    public abstract void setPath(List<Point> path);

    public abstract void update(double i);

    public abstract void paint(Graphics g);
    }
}

EDIT: It works now Pedestrian is spawned on mouse click.


回答1:


Basically, you want to decouple your code and centralise the responsible to the classes. So the "data" should be maintained by a model of some kind, the rendering should be handle by some kind of view and the updates to the model and view should be handled by some kind of controller.

This makes it easier to swap out any one part with out requiring a whole bunch of new code or other changes. It also means that each class has a defined domain of responsibility and discourages you from trying to, for example, make changes to state from within the view which should be handled by the model (which could put the state into disarray)

Let's start with something that what's to be painted

public interface Sprite {

    public void paint(Graphics2D g2d);

}

public interface MoveableSprite extends Sprite {

    public void update(Container container);

}

These represent either a static sprite (like a tree for example) or a sprite which is moving (and wants to be updated on a regular bases)

These are contained within a model

public interface GameModel {

    public List<Sprite> getSprites();

    public void setObserver(Observer<MoveableSprite> observer);

    public Observer<MoveableSprite> getObserver();

    public void spawnSprite();

}

Which provides some means by which it can notify (in this case, a single) interested party about some kind of state change. For this example, that means a new MoveableSprite has become available

The Observer is pretty basic and just has a single call back...

public interface Observer<T> {

    public void stateChanged(T parent);

}

And an "engine" to help drive it...

public class GameEngine {

    private GameModel model;
    private SurfacePane surface;
    private Timer timer;

    public GameEngine(GameModel model, SurfacePane surface) {
        this.model = model;
        this.surface = surface;

        model.setObserver(new Observer<MoveableSprite>() {
            @Override
            public void stateChanged(MoveableSprite sprite) {
                sprite.update(getSurface());
            }
        });

        timer = new Timer(40, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (Sprite sprite : getModel().getSprites()) {
                    if (sprite instanceof MoveableSprite) {
                        ((MoveableSprite) sprite).update(getSurface());
                    }
                }
                getSurface().repaint();
            }
        });
    }

    public GameModel getModel() {
        return model;
    }

    public SurfacePane getSurface() {
        return surface;
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

}

This is a pretty basic example, but basically, it updates the position of MoveableSprite and asks the surface to repaint itself. It's also observing the GameModel for any new sprites and it will update their position immediately, so they don't appear in some "weird" place

Okay, now we actually need to implement some of this to make it work

public class DefaultGameModel implements GameModel {

    private Observer<MoveableSprite> observer;
    private List<Sprite> sprites;

    public DefaultGameModel() {
        sprites = new ArrayList<>(25);
        for (int index = 0; index < 10; index++) {
            spawnSprite();
        }
    }

    @Override
    public List<Sprite> getSprites() {
        return Collections.unmodifiableList(sprites);
    }

    public void spawnSprite() {
        try {
            ZombieSprite sprite = new ZombieSprite();
            sprites.add(sprite);

            Observer<MoveableSprite> observer = getObserver();
            if (observer != null) {
                observer.stateChanged(sprite);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setObserver(Observer<MoveableSprite> observer) {
        this.observer = observer;
    }

    @Override
    public Observer<MoveableSprite> getObserver() {
        return observer;
    }

}

public class ZombieSprite implements MoveableSprite {

    private int x;
    private int y;

    private int xDelta;
    private int yDelta;

    private BufferedImage img;

    private Observer<Sprite> observer;
    private boolean initialised = false;

    public ZombieSprite() throws IOException {
        img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
    }

    @Override
    public void update(Container container) {
        if (!initialised) {
            x = (int) (Math.random() * container.getWidth());
            y = (int) (Math.random() * container.getHeight());

            Random rnd = new Random();
            xDelta = rnd.nextBoolean() ? 1 : -1;
            yDelta = rnd.nextBoolean() ? 1 : -1;
            initialised = true;
        }
        x += xDelta;
        y += yDelta;

        if (x < 0) {
            x = 0;
            xDelta *= -1;
        } else if (x + img.getWidth() > container.getWidth()) {
            x = container.getWidth() - img.getWidth();
            xDelta *= -1;
        }
        if (y < 0) {
            y = 0;
            yDelta *= -1;
        } else if (y + img.getHeight() > container.getHeight()) {
            y = container.getHeight() - img.getHeight();
            yDelta *= -1;
        }
    }

    @Override
    public void paint(Graphics2D g2d) {
        g2d.drawImage(img, x, y, null);
    }

}

These two classes implement the GameModel and MoveableSprite interfaces. We use interfaces to decouple the code, which makes it easier to change the way in which things work and provides a jumping off point for agreed to contracts and exceptions of the implemenations

And finally, something that actually paints the current state...

public class SurfacePane extends JPanel {

    private GameModel model;

    public SurfacePane(GameModel model) {
        this.model = model;

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                getModel().spawnSprite();
            }
        });
    }

    public GameModel getModel() {
        return model;
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        GameModel model = getModel();
        for (Sprite sprite : model.getSprites()) {
            sprite.paint(g2d);
        }
        g2d.dispose();
    }

}

You'll not that this class has the MouseListener, this is kind of deliberate, as other components which might be added to this container could prevent the MouseListener from been notified, so don't do that. But the MouseListener just calls the model to spawn another zombie...

And finally, we need to plumb it altogether...

GameModel model = new DefaultGameModel();
SurfacePane surfacePane = new SurfacePane(model);
GameEngine engine = new GameEngine(model, surfacePane);

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(surfacePane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

engine.start();

And just because I know that's a lot of disjointed concepts to put together, a complete example...

import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeListener;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                GameModel model = new DefaultGameModel();
                SurfacePane surfacePane = new SurfacePane(model);
                GameEngine engine = new GameEngine(model, surfacePane);

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(surfacePane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                engine.start();
            }
        });
    }

    public class SurfacePane extends JPanel {

        private GameModel model;

        public SurfacePane(GameModel model) {
            this.model = model;

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    getModel().spawnSprite();
                }
            });
        }

        public GameModel getModel() {
            return model;
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            GameModel model = getModel();
            for (Sprite sprite : model.getSprites()) {
                sprite.paint(g2d);
            }
            g2d.dispose();
        }

    }

    public class GameEngine {

        private GameModel model;
        private SurfacePane surface;
        private Timer timer;

        public GameEngine(GameModel model, SurfacePane surface) {
            this.model = model;
            this.surface = surface;

            model.setObserver(new Observer<MoveableSprite>() {
                @Override
                public void stateChanged(MoveableSprite sprite) {
                    sprite.update(getSurface());
                }
            });

            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Sprite sprite : getModel().getSprites()) {
                        if (sprite instanceof MoveableSprite) {
                            ((MoveableSprite) sprite).update(getSurface());
                        }
                    }
                    getSurface().repaint();
                }
            });
        }

        public GameModel getModel() {
            return model;
        }

        public SurfacePane getSurface() {
            return surface;
        }

        public void start() {
            timer.start();
        }

        public void stop() {
            timer.stop();
        }

    }

    public interface Observer<T> {

        public void stateChanged(T parent);

    }

    public interface Sprite {

        public void paint(Graphics2D g2d);

    }

    public interface MoveableSprite extends Sprite {

        public void update(Container container);

    }

    public interface GameModel {

        public List<Sprite> getSprites();

        public void setObserver(Observer<MoveableSprite> observer);

        public Observer<MoveableSprite> getObserver();

        public void spawnSprite();

    }

    public class DefaultGameModel implements GameModel {

        private Observer<MoveableSprite> observer;
        private List<Sprite> sprites;

        public DefaultGameModel() {
            sprites = new ArrayList<>(25);
            for (int index = 0; index < 10; index++) {
                spawnSprite();
            }
        }

        @Override
        public List<Sprite> getSprites() {
            return Collections.unmodifiableList(sprites);
        }

        public void spawnSprite() {
            try {
                ZombieSprite sprite = new ZombieSprite();
                sprites.add(sprite);

                Observer<MoveableSprite> observer = getObserver();
                if (observer != null) {
                    observer.stateChanged(sprite);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void setObserver(Observer<MoveableSprite> observer) {
            this.observer = observer;
        }

        @Override
        public Observer<MoveableSprite> getObserver() {
            return observer;
        }

    }

    public class ZombieSprite implements MoveableSprite {

        private int x;
        private int y;

        private int xDelta;
        private int yDelta;

        private BufferedImage img;

        private Observer<Sprite> observer;
        private boolean initialised = false;

        public ZombieSprite() throws IOException {
            img = ImageIO.read(getClass().getResource("/LogoZombi.png"));
        }

        @Override
        public void update(Container container) {
            if (!initialised) {
                x = (int) (Math.random() * container.getWidth());
                y = (int) (Math.random() * container.getHeight());

                Random rnd = new Random();
                xDelta = rnd.nextBoolean() ? 1 : -1;
                yDelta = rnd.nextBoolean() ? 1 : -1;
                initialised = true;
            }
            x += xDelta;
            y += yDelta;

            if (x < 0) {
                x = 0;
                xDelta *= -1;
            } else if (x + img.getWidth() > container.getWidth()) {
                x = container.getWidth() - img.getWidth();
                xDelta *= -1;
            }
            if (y < 0) {
                y = 0;
                yDelta *= -1;
            } else if (y + img.getHeight() > container.getHeight()) {
                y = container.getHeight() - img.getHeight();
                yDelta *= -1;
            }
        }

        @Override
        public void paint(Graphics2D g2d) {
            g2d.drawImage(img, x, y, null);
        }

    }

}


来源:https://stackoverflow.com/questions/30342509/draw-on-jpanel-from-other-class

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