I am making a roundabout simulation. The images are all painted in doDrawing() so far, but the program is connected with a server so I want to draw (spawn) the images from another class according to what the server sends. For testing purposes I identify a byte array on mousePressed() in the Roundabout class. This works I can see the println from spawnPedestrian() but the pedestrian isn't painted on the JPanel.
What do I have to change in spawnPedestrian() to make it work?
Roundabout.java - main class
public class Roundabout extends JFrame{
Track track=new Track();
TrafficLight trafficLight1=new TrafficLight(1);
TrafficLight trafficLight3=new TrafficLight(3);
TrafficLight trafficLight2=new TrafficLight(2);
TrafficLight trafficLight4=new TrafficLight(4);
TrafficLight trafficLight5=new TrafficLight(5);
Car car=new Car(412, 750); // south to west
Car car2=new Car(50,400); // west to south
Car car3=new Car(700,290); //east to south
Car car4=new Car(470,750);
Bus bus=new Bus();
Bicycle bicycle=new Bicycle();
Pedestrian pedestrian = new Pedestrian(571,750);
ArrayList<Car> cars = new ArrayList<>();
//public ArrayList<TrafficLight> trafficLights = new ArrayList<>{trafficLight3}();
public static Map<Integer,TrafficLight> trafficLights = new HashMap<>();
byte[] array=new byte[]{0,2,1,1}; //test byte array
private Long startTime;
private long playTime = 4000;
private double i;
static TCPClient client;
Surface surface=new Surface();
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Dimension size = getSize();
Insets insets = getInsets();
int w = size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;
/* Draw the track first */
track.paint(g);
/* Draw a car */
//car.START_POS = new Point(412, 750);
car.setCarLane(Lane.topLane);
car.paint(g);
cars.add(car); //add to list
//car2.START_POS=new Point(50,400);
car2.carRotation=180;
car2.setCarLane(Lane.wsLane);
car2.paint(g);
cars.add(car2);
car3.carRotation=360;
car3.setCarLane(Lane.esLane);
car3.paint(g);
cars.add(car3);
car4.setCarLane(Lane.seLane);
car4.paint(g);
cars.add(car4);
/*Draw a bus*/
bus.paint(g);
/*Draw a bicycle */
bicycle.setBicyclePath(Lane.bicyclePath);
bicycle.paint(g);
/*Draw a pedestrian */
pedestrian.setPedestrianPath(Lane.pedesSePath);
pedestrian.paint(g);
/* Draw traffic light*/
trafficLight1.setPosition(520, 333);
trafficLight1.paint(g);
trafficLight3.setPosition(100, 275);
trafficLight3.paint(g);
trafficLight2.setPosition(100, 400);
trafficLight2.paint(g);
trafficLight4.setPosition(355, 535);
trafficLight4.paint(g);
trafficLight5.setPosition(404, 535);
trafficLight5.paint(g);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public Roundabout(){
initUI();
trafficLights.put(trafficLight1.id, trafficLight1);
trafficLights.put(trafficLight2.id,trafficLight2);
trafficLights.put(trafficLight3.id,trafficLight3);
trafficLights.put(trafficLight4.id,trafficLight4);
trafficLights.put(trafficLight5.id,trafficLight5);
}
private void initUI() {
setTitle("Roundabout");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(surface);
//add start
this.addMouseListener(new MouseAdapter() {// empty implementation of all
// MouseListener`s methods
#Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getX() + "," + e.getY());
ByteProtocol proto=new ByteProtocol();
proto.identifyByteArray(new byte []{0x01,0x02,0x01,0x00});
}
});
//end add
//setSize(580, 550);
setSize(1618,850);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
//Swing thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Roundabout roundabout=new Roundabout();
roundabout.setVisible(true);
roundabout.moveCar();
}
});
}
}
ByteProtocol.java method where spawnPedestrian() is called.
public void identifyByteArray(byte [] inputArray) {
byte identifier=inputArray[0];
switch(identifier){
case vehicle:
System.out.println("Identifier: Vehicle");
decodeVehicleArray(inputArray);
break;
case trafficLight:
System.out.println("Traffic light");
decodeTrafficLightArray(inputArray);
break;
case vehicleRegistration:
System.out.println("Vehicle registration");
decodeVehicleRegistrationArray(inputArray);
break;
default: System.out.println("Unable to identify the byte array.");
break;
}
}
public void decodeVehicleArray(byte [] vehicleArray){
byte startPosition=vehicleArray[1];
String startPositionString = directions.get(startPosition);
System.out.println("Start position: "+startPositionString);
byte endPosition=vehicleArray[2];
String endPositionString = directions.get(endPosition);
System.out.println("End position: "+endPositionString);
byte vehicleType=vehicleArray[3];
String vehicleTypeString=vehicles.get(vehicleType);
System.out.println("Vehicle: "+vehicleTypeString);
Spawn spawn=new Spawn();
spawn.spawnPedestrian();
}
Spawn.java
public class Spawn {
Roundabout roundabout = new Roundabout();
public void spawnPedestrian(){
Pedestrian p = new Pedestrian(30,50);
System.out.println("Spawn me ");
p.setVisible(true);
roundabout.surface.add(p);
roundabout.surface.revalidate();
roundabout.surface.repaint();
roundabout.revalidate();
roundabout.repaint();
}
}
When you write Roundabout roundabout = new Roundabout(); in your Spawn class, you are creating a new object of Roundabout type (a new JFrame in the end), and you don't want a new object of that type, you want the one which is already created.
What you need to do is reference the JPanel surface from the Spawn class through a getter. So add this method to Roundabout class and call it from Spawn to get its context and be able to draw on it:
public static JPanel getSurface()
{
return surface;
}
And then in your Spawn class remove Roundabout roundabout = new Roundabout();, get the Surface JPanel object and draw on it:
public class Spawn {
public void spawnPedestrian(){
Pedestrian p = new Pedestrian(30,50);
System.out.println("Spawn me ");
p.setVisible(true);
Roundabout.getSurface.add(p);
Roundabout.getSurface.revalidate();
Roundabout.getSurface.repaint();
Roundabout.getSurface.revalidate();
Roundabout.getSurface.repaint();
}
}
Note that you will need to make the JPanel Surface object static.
Related
I am trying to make a little program that includes changing the color of a Panel time-based.
Right now I am just trying to do that part without the rest. So I just wrote a little Interface with only one panel and I want to change the color within a loop multiple times.
The problem is, even though the thread pauses for the correct amount of time, the color of the Panel doesn't change correctly. It changes just sometimes in the loop not every time.
my Interface Class:
import javax.swing.*;
import java.awt.*;
//creates the Interface
public class Interface extends JFrame {
private JPanel frame1;
public Interface (String titel) {
super(titel);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame1 = new JPanel();
this.frame1.setPreferredSize(new Dimension (200, 200));
setLayout(new FlowLayout());
add(frame1);
this.setVisible(true);
}
public JPanel getFrame1() {
return frame1;
}
}
my Pause Class:
import java.util.TimerTask;
//supposed to pause the thread by #pause amount of milliseconds
public class Pause extends TimerTask {
private int pause;
public Pause(int pause){
this.pause = pause;
}
#Override
public void run() {
System.out.println("Timer"+ pause+" task started at:"+System.currentTimeMillis());
pause();
System.out.println("Timer task"+ pause+" ended at:"+System.currentTimeMillis());
}
public void pause() {
try {
Thread.sleep(this.pause);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
my Blink Class
import javax.swing.*;
import java.awt.*;
public class Blink {
private JPanel frame1;
public Blink(Interface anInterface){
this.frame1 = anInterface.getFrame1();
}
// blink should change the color of the JPanel inside my Frame.
// Its supposed to change to red for 200 ms
// and then to white again for 1000 ms.
// this should be repeated 10 times.
public void blink() {
Pause pause1 = new Pause(200);
Pause pause2 = new Pause(1000);
pause2.run();
int i = 1;
while(i <= 10){
i++;
frame1.setBackground(Color.red);
frame1.repaint();
pause1.run();
frame1.setBackground(Color.white);
frame1.repaint();
pause2.run();
}
}
public static void main ( String[] args ) {
Interface anInterface = new Interface("Title");
anInterface.setVisible(true);
Blink blink = new Blink(anInterface);
blink.blink();
}
}
According to Concurrency to Swing you cannot simply Thread.sleep the Thread where the GUI runs because it will freeze it, hence events cannot take place. Instead, for any kind of animation or long-heavy task (consider Thread.sleep as one), Swing Timers and Swing Workers should be used. In your case, a javax.swing.Timer fits better.
One example of its usage:
public class Blink {
private JPanel frame1;
private int pause1TimesRan;
private int pause2TimesRan;
private Timer pauser1, pauser2;
public Blink(Interface anInterface) {
this.frame1 = anInterface.getFrame1();
//Create pauser 1 with delay 200ms
pauser1 = new Timer(200, e -> {
if (pause1TimesRan == 10) {
pauser1.stop();
return;
}
Color color = randomColor();
frame1.setBackground(color);
System.out.println("Pauser #1 changed background to: " + color);
pause1TimesRan++;
});
//Create pauser 2 with delay 1000ms
pauser2 = new Timer(1000, e -> {
if (pause2TimesRan == 10) {
pauser2.stop();
return;
}
Color color = randomColor();
frame1.setBackground(color);
System.out.println("Pauser #2 changed background to: " + color);
pause2TimesRan++;
});
}
private static Color randomColor() {
return new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));
}
public void blink() {
pauser1.start();
pauser2.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Interface anInterface = new Interface("Title");
anInterface.setVisible(true);
Blink blink = new Blink(anInterface);
blink.blink();
});
}
static class Interface extends JFrame {
private JPanel frame1;
public Interface(String titel) {
super(titel);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame1 = new JPanel();
this.frame1.setPreferredSize(new Dimension(200, 200));
setLayout(new FlowLayout());
add(frame1);
this.setVisible(true);
}
public JPanel getFrame1() {
return frame1;
}
}
}
One off-topic advice is to name your methods (and variables) properly. You called the method getFrame1(), but it is actually a JPanel and not a JFrame. So, a better name could be getPanel(). Also, about the SwingUtilities.invokeLater part, read What does SwingUtilities.invokeLater does.
So I created two objects from tank1 class, and their names are tank1 and tank2. I want tank1 to shift right with RIGHT key and to shift left with LEFT key, and tank2 to shift right with D and to shift left with A key. But when I finished compiling my code, the tank2 shift left with A, and LEFT keys, and shift right with D, and RIGHT keys, and tank1 doesn't shift with any key at all. Is there any way to fix this so tank 1 shift with LEFT, RIGHT key?
here is my gridbasegame class:
public class GridBasedGameDriver {
private JFrame frame = new JFrame("my world");
private JPanel panel;
private List<Drawable> drawables= new ArrayList();
private Terrain terrain;
private Tank1 Tank1;
private Tank1 Tank2;
public static void main(String[] args) {
new GridBasedGameDriver().start();
}
private void start() { // REPAINT
setUpGame();
frame.setBackground(new Color(127, 127, 127));
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new JPanel() { public void paintComponent(Graphics g) { super.paintComponent(g); drawGame(g); } }; // what does paint componet and draw game do,
//and where does the
//super come from?
panel.setPreferredSize(new Dimension(800,600)); // doesn't matter, can be set again manually
frame.add(panel); // so frame needs to add panel
frame.pack(); // no idea, probably not important
// int b= (int)(Math.random()*100+100); //100-199, only here for fun doesn't change a damn thing
panel.getInputMap().put(KeyStroke.getKeyStroke("RIGHT"),"slideRight");
panel.getActionMap().put("slideRight",new AbstractAction(){
public void actionPerformed(ActionEvent arg0) {
Tank1.moveRight();
panel.repaint();
}
});
panel.requestFocusInWindow();
panel.getInputMap().put(KeyStroke.getKeyStroke("LEFT"),"slideLeft");
panel.getActionMap().put("slideLeft",new AbstractAction(){
public void actionPerformed(ActionEvent arg0) {
Tank1.moveleft();
panel.repaint();
}
});
panel.requestFocusInWindow();
panel.getInputMap().put(KeyStroke.getKeyStroke("D"),"slideRight");
panel.getActionMap().put("slideRight",new AbstractAction(){
public void actionPerformed(ActionEvent arg0) {
Tank2.moveRight();
panel.repaint();
}
});
panel.requestFocusInWindow();
panel.getInputMap().put(KeyStroke.getKeyStroke("A"),"slideLeft");
panel.getActionMap().put("slideLeft",new AbstractAction(){
public void actionPerformed(ActionEvent arg0) {
Tank2.moveleft();
panel.repaint();
}
});
panel.requestFocusInWindow();
setUpObjects();
frame.repaint();
}
private void setUpObjects() {
terrain = new Terrain(panel.getWidth(), panel.getHeight()); // where does the panel come from?
terrain.initialize();
List<Integer> b=terrain.getlist();
Tank1 = new Tank1(20,0,b);
Tank2 = new Tank1(740,1,b);
drawables.add(terrain); // I get it, so the terrain has drawable, and once it gets added to drawable array it implements its own drawable
drawables.add(Tank1);
drawables.add(Tank2);
}
public void drawGame(Graphics g) {
for(Drawable dr:drawables) {
dr.draw(g);
}
}
and here is my tank1 class:
public class Tank1 implements Drawable {
public int gi;
public int it;
public List<Integer> b;
List<Integer> Krell = new ArrayList<>();
public Tank1(int gi, int it, List<Integer> b) {
this.b=b;
this.gi=gi;
this.it=it;
}
public void moveleft() {
gi=gi-1;
}
public void moveRight() {
gi=gi+1;
}
public void draw(Graphics g) {
// TODO Auto-generated method student
if (it==0) {
g.setColor(new Color(230,50,58)); // draws that recoatlve
}
if(it==1) {
g.setColor(new Color(120,160,60)); // draws that recoatlve
}
g.fillRect(gi, b.get(gi)-25, 25, 25); //draws that rectangle
}
}
Might first thought is, JPanel is not focusable by default, so requesting focus in the window won't do anything. Besides, the point of using key bindings like this is to avoid having to deal with "grab focus" style hacks.
There are a couple of variations to getInputMap, one which allows you to define the focus context in which the bindings will be triggered. You might consider using WHEN_IN_FOCUSED_WINDOW, for example...
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("RIGHT"),"slideRight");
I am trying to make a JComponent application which uses two JFrames, one frame with alterable sliders and textfields for the graphical display of a firework on the second. When the "fire" button is pressed, a rendering of the firework should appear. However, I have found through placing strategic print statements, that my paintComponent() method does not run even though the conditional statement wrapping the code is satisfied. I have also double checked all of my other methods to ensure that correct values are generated at the correct times. After looking through all of the JComponent literature and questions I could find, I'm afraid I cannot get it to work - this problem is most likely derived from my lack of familiarity with the library. That being said, any advice no matter how rudimentary, will be much appreciated. Abridged code is below:
*The swing timer may also be the issue for I am not sure if I have used it correctly
[fireworksCanvas.java]
public class fireworkCanvas extends JComponent implements ActionListener{
private static final long serialVersionUID = 1L;
private ArrayList<Ellipse2D> nodes = new ArrayList<Ellipse2D>();
private ArrayList<Line2D> cNodes = new ArrayList<Line2D>();
private ArrayList<QuadCurve2D> bCurves = new ArrayList<QuadCurve2D>();
private int[] arcX;
private int[] arcY;
private Color userColor;
private Random rand = new Random();
private int shellX, shellY, fType, theta, velocity;
private Timer timer;
private int time;
private double g = -9.8; //gravity in m/s
public boolean explosivesSet;
public fireworkCanvas() {
time = rand.nextInt(3000) + 2000;
timer = new Timer(time, this); // 5 seconds
timer.start();
fType = 0;
}
#Override
public void paintComponent(Graphics g){
if (explosivesSet) {
System.out.println("fType" + fType);
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
g.setColor(Color.BLACK);
g.drawPolyline(arcX, arcY, arcX.length);
for (Ellipse2D e : nodes) {
System.out.println("painting nodes"); // NEVER PRINTS
g.setColor(userColor);
g.fillOval(shellX + (int) e.getX(), shellY + (int) e.getY(), (int) e.getWidth(), (int) e.getHeight());
}
for (Line2D l: cNodes) {
System.out.println("painting cNodes"); // NEVER PRINTS
g.setColor(determineColor("l"));
g.drawLine(shellX + (int) l.getX1(), shellY + (int) l.getY1(), shellX + (int) l.getX2(), shellY + (int) l.getY2());
}
for (QuadCurve2D c: bCurves) {
System.out.println("painting curves"); // NEVER PRINTS
g.setColor(determineColor("c"));
g2D.draw(c);
}
}
}
public Color determineColor(String type) {
// returns color
}
public void setExplosives() {
if (fType != 5 && fType != 0) {
nodes.clear(); // clears three array lists with FW components
cNodes.clear(); // these are the components to paint for the
bCurves.clear(); // firework explosion graphic
setArc(); // stores path of shell for a polyLine to be drawn
// builds and generates components for FW based on type chosen (fType)
setExplosivesSet(true);
repaint();
}
}
public void setArc() {
// builds int[] for shellX, shellY
}
#Override
public void actionPerformed(ActionEvent e) {
// nothing is here??
// should I use the action performed in some way?
}
[GUI.java]
public class GUI extends JFrame implements ActionListener, ChangeListener, ItemListener, MouseListener{
private static JFrame canvasFrame = new JFrame("Canvas");
private fireworkCanvas canvas = new fireworkCanvas();
private Choice fireworkChooser = new Choice();
private JSlider launchAngle = new JSlider();
private JSlider velocity = new JSlider();
private JSlider r = new JSlider();
private JSlider g = new JSlider();
private JSlider b = new JSlider();
private JPanel panel = new JPanel();
private JButton button = new JButton("Fire!");
private JLabel launchLabel = new JLabel("Launch Angle ");
private JLabel velocityLabel = new JLabel("Velocity ");
private JLabel rLabel = new JLabel("Red ");
private JLabel gLabel = new JLabel("Green ");
private JLabel bLabel = new JLabel("Blue ");
public static int fHeight = 500;
public static int fWidth = 500;
public GUI() {
this.add(panel);
panel.add(button);
panel.add(fireworkChooser);
panel.add(launchAngle);
panel.add(launchLabel);
panel.add(velocity);
panel.add(velocityLabel);
panel.add(r);
panel.add(rLabel);
panel.add(g);
panel.add(gLabel);
panel.add(b);
panel.add(bLabel);
addActionListener(this);
BoxLayout bl = new BoxLayout(getContentPane(), BoxLayout.Y_AXIS);
setLayout(bl);
fireworkChooser.addItemListener(this);
launchAngle.addChangeListener(this);
velocity.addChangeListener(this);
r.addChangeListener(this);
g.addChangeListener(this);
b.addChangeListener(this);
button.addActionListener(this);
fireworkChooser.add("Firework 1");
fireworkChooser.add("Firework 2");
fireworkChooser.add("Firework 3");
fireworkChooser.add("Firework 4");
fireworkChooser.add("Super Firework");
launchAngle.setMinimum(1);
launchAngle.setMaximum(90);
velocity.setMinimum(1);
velocity.setMaximum(50);
r.setMinimum(0);
r.setMaximum(255);
g.setMinimum(0);
g.setMaximum(255);
b.setMinimum(0);
b.setMaximum(255);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 200);
}
#Override
public void stateChanged(ChangeEvent e) {
// sets FW variables
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button) {
canvas.setfType(fireworkChooser.getSelectedIndex()+1);
canvas.setExplosives();
canvas.repaint();
canvas.setExplosivesSet(false);
System.out.println("button fired");
}
}
public static void createAndShowGUI() {
GUI gui = new GUI();
gui.pack();
gui.setLocationRelativeTo(null);
gui.setVisible(true);
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fireworkCanvas canvas = new fireworkCanvas();
canvasFrame.pack();
canvasFrame.add(canvas);
canvasFrame.setLocationRelativeTo(null);
canvasFrame.setVisible(true);
canvasFrame.setSize(fWidth, fHeight);
canvasFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
First of all:
public fireworkCanvas()
Class names should start with an upper case character. All the other classes in your code follow this rule. Learn by example.
private Choice fireworkChooser = new Choice();
Choice is an AWT component don't mix AWT components in a Swing application. Use a JComboBox.
that my paintComponent() method does not run
fireworkCanvas canvas = new fireworkCanvas();
canvasFrame.pack();
canvasFrame.add(canvas);
You add the canvas to the frame AFTER you pack() the frame, so the size of the canvas is (0, 0) and there is nothing to paint.
The canvas should be added to the frame BEFORE the pack() and you should implement getPreferredSize() in your FireworkCanvas class so the pack() method can work properly.
Read the section from the Swing tutorial on Custom Painting for the basics and working examples to get you started.
Hi I'm building a game which includes 3 JPanels on a JFrame, a Startscreen, a DrawingPanel and a GameOver screen. If I just create the DrawingPanel and tell the GameController class to begin updating it works fine, but if I create a StartScreen with a button to start the game, then when I press the start game button the game window does not display, although the game code runs.
EDIT:
I have created a new program which mimics the creation of the JPanels, but excludes all of the game code to make it a bit simpler to follow. Below I have included all the relevant classes:
This class creates a JFrame and two JPanels. It also runs the code that updates the game state and tells the DrawingPanel to repaint.
public class TestController{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private final int FRAME_WIDTH = (int)screenSize.getWidth();
private final int FRAME_HEIGHT = (int)screenSize.getHeight();
public boolean gameStarted = false;
private JFrame gameWindow;
private TestDrawingPanel myDrawingPanel;
private TestStartGame startGame;
int counter;
Set<Rectangle> rects;
//creates a JFrame and all the JPanels
public TestController(String title)
{
gameWindow = new JFrame(title);
gameWindow.setSize(FRAME_WIDTH, FRAME_HEIGHT);
gameWindow.setVisible(true);
gameWindow.setResizable(false);
gameWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
startGame = new TestStartGame(getAvailableWidth(), getAvailableHeight());
startGame.addAL(new StartButton());
myDrawingPanel = new TestDrawingPanel(getAvailableWidth(), getAvailableHeight());
gameWindow.add(startGame);
gameWindow.add(myDrawingPanel);
myDrawingPanel.setVisible(false);
myDrawingPanel.setEnabled(false);
rects = new HashSet();
}
private int getAvailableWidth()
{
return gameWindow.getWidth() - gameWindow.getInsets().left - gameWindow.getInsets().right;
}
private int getAvailableHeight()
{
return gameWindow.getHeight() - gameWindow.getInsets().top - gameWindow.getInsets().bottom;
}
//starts the game running
public void startTheGame()
{
myDrawingPanel.setEnabled(true);
startGame.setVisible(false);
startGame.setEnabled(false);
myDrawingPanel.setVisible(true);
gameStarted = true;
update();
}
public boolean getGameStarted()
{
return gameStarted;
}
//loop that runs the game code
public void update()
{
counter = 0;
while(gameStarted)
{
updatePictureState();
myDrawingPanel.draw(rects);
myDrawingPanel.repaint();
}
}
//updates the game state
public void updatePictureState()
{
rects.clear();
for (int i = counter + 10; i < counter + 100; i = i + 10)
{
rects.add(new Rectangle(i,i,10,10));
}
}
//an action listener to be added to the start screen
private class StartButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Object buttonPressed = e.getSource();
if(buttonPressed.equals(TestStartGame.start))
{
startTheGame();
}
}
}
}
This class is an extended JPanel with a single button to start the game:
public class TestStartGame extends JPanel{
private final JPanel buttons;
public static JButton start;
//creates a JPanel with a single button to start the game
public TestStartGame(int width, int height)
{
setSize(width, height);
setLayout(new GridLayout(2,1));
setBackground(Color.GREEN);
buttons = new JPanel();
buttons.setSize(width, height / 2);
buttons.setBackground(Color.red);
start = new JButton("Start");
buttons.add(start);
add(buttons, BorderLayout.SOUTH);
}
//adds an action listener to the button
public void addAL(ActionListener al)
{
start.addActionListener(al);
}
}
This class is an extended JPanel and acts as the main screen for the game, being updated to with each cycle of the game to display it's current state:
public class TestDrawingPanel extends JPanel{
Set<Rectangle> drawSet;
//creates the drawing panel and sets the size and background
public TestDrawingPanel(int width, int height)
{
setSize(width, height);
this.setBackground(Color.CYAN);
drawSet = new HashSet();
}
public void draw(Set<Rectangle> platforms)
{
drawSet.clear();
drawSet = platforms;
}
//draws the game window
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("works here");
g.setColor(Color.red);
for (Rectangle r : drawSet)
{
g.fillRect((int)r.getX(), (int)r.getY(), (int)r.getWidth(), (int)r.getHeight());
}
}
}
If I just add the TestDrawingPanel, it displays fine, but if I start with a TestStartScreen then when I click the start game button the TestStartScreen does not disappear and the TestDrawingPanel never displays. Interestingly, if I have both screens but do not call the update method is TestController then the start game button works correctly and the TestDrawingPanel displays, although obviously nothing happens as the update method is where the game state is changed.
I have discovered the problem is that if the TestDrawingPanel is not the only JPanel created then the call to repaint it fails.
Here:
Thread.sleep(20);
You are most likely sleeping on the Event Dispatcher Thread. That will freeze your whole application. You have to step back and look into invokeLater to ensure "correct" threading within your UI.
Problem solved courtesy of #Andrew-Thompson:
"while(gameStarted) .. No, no a thousand times no. An infinite loop will likely freeze the GUI. Use a Swing based Timer to call those code statements in the loop, in the actionPerformed method of an ActionListener"
I followed steps from many articles but the undo system not behaving as expected. first, the undo button goes back correctly till the first edit but can't go beyond the first edit. also, when I click undo and then re-edit it circles back to the last edit. It's better if you execute the code and see yourself.
Another question, while I'm trying to figure out a solution I read that saving images in an Array is memory intensive, is that true even for this simple paint class, and what is the alternative? saving the image's graphic?
import java.awt.*;import java.awt.image.BufferedImage;import java.awt.event.*;import java.util.ArrayList;import javax.swing.*;import javax.swing.event.*;import javax.swing.undo.*;
public class Painter extends JFrame{
//attributes//
Painter.Canvas canvas;
JPanel controlPanel;
JButton undoButton;
JButton redoButton;
PainterHandler handler;
Container container;
//undo system elements//
UndoManager undoManager; // history list
UndoableEditSupport undoSupport; // event support
//constructor//
public Painter()
{
super("Painter-test");
controlPanel = new JPanel();
undoButton = new JButton("undo");
redoButton = new JButton("redo");
handler = new Painter.PainterHandler();
container = getContentPane();
canvas = new Painter.Canvas();
this.organizer();
}// end constructor
public void organizer()
{
controlPanel.setLayout(null);
controlPanel.setPreferredSize(new Dimension(120,350));
controlPanel.setBackground(null);
//add undo listeners to undo/redo buttons.
undoButton.addActionListener( new AbstractAction()
{
public void actionPerformed( ActionEvent evt )
{ undoManager.undo(); refreshCanvas(); refreshUndoRedo();}
});
redoButton.addActionListener(new AbstractAction()
{
public void actionPerformed(ActionEvent evt )
{ undoManager.redo(); refreshCanvas(); refreshUndoRedo(); }
});
// initilize the undo/redo system.
undoManager= new UndoManager();//history list
// event support, instance.
undoSupport = new UndoableEditSupport();
//add undoable edit listener to the support instance.
undoSupport.addUndoableEditListener(new UndoableEditListener()
{
public void undoableEditHappened (UndoableEditEvent event)
{
UndoableEdit edit = event.getEdit();
undoManager.addEdit( edit );
refreshUndoRedo();
}
});
refreshUndoRedo();
canvas.setPreferredSize(new Dimension(600,400));
//place buttons on panel.
undoButton.setBounds(10, 160, 80, 20);
redoButton.setBounds(10, 181, 80, 20);
//add components to panel.
controlPanel.add(undoButton);
controlPanel.add(redoButton);
//add panels to window.
container.add(canvas,BorderLayout.WEST);
container.add(controlPanel, BorderLayout.EAST);
}//end organizerTab3()
public void refreshCanvas() { canvas.repaint(); }
//refresh undo, redo buttons.
public void refreshUndoRedo()
{
// refresh undo
undoButton.setEnabled( undoManager.canUndo() );
// refresh redo
redoButton.setEnabled( undoManager.canRedo() );
}
//INNER CLASSES
ArrayList<BufferedImage> imagesArray = new ArrayList<BufferedImage>();
BufferedImage imageCopy;
Graphics graphics;
BufferedImage image;
int index;
private class Canvas extends JPanel
{
public Canvas()
{
//Panel properties
setSize(600,400);
setBackground(new Color(84,84,118));
image = new BufferedImage(getWidth(), getHeight(),BufferedImage.TYPE_INT_ARGB);
//add Listeners.
addMouseMotionListener(handler);
addMouseListener(handler);
}//end constructor
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}//end inner class PaintPanel
private class PainterHandler extends MouseAdapter
{
#Override
public void mouseDragged(MouseEvent event)
{
if(event.getComponent().equals(canvas))
{
if (image != null)
{
// Paint into the image
graphics = image.getGraphics();
graphics.setColor(new Color(249,30,138));
graphics.fillOval(event.getX(), event.getY(), 20, 20);
imageCopy = new BufferedImage(canvas.getWidth(),canvas.getHeight(),BufferedImage.TYPE_INT_ARGB);
imageCopy.getGraphics().drawImage(image, 0, 0, null);
canvas.repaint();
}
}
}//end mouseDragged(MouseEvent event)
#Override
public void mouseReleased(MouseEvent event)
{
if(event.getComponent().equals(canvas))
{
//UNDO SYSTEM START//
//add image to the array.
imagesArray.add(imageCopy);
// get image's index.
index = imagesArray.indexOf(imageCopy);
//create AddEdit instance of type UndoableEdit.
UndoableEdit edit = new Painter.AddEdit(imagesArray, imageCopy,index);
// notify the listeners
undoSupport.postEdit( edit );
//UNDO SYSTEM END//
}
}
}//end MouseHandler class
private class AddEdit extends AbstractUndoableEdit
{
private ArrayList<BufferedImage> undoableImagesArray;
private BufferedImage undoableImage;
int undoableIndex;
public AddEdit(ArrayList<BufferedImage> v, BufferedImage img, int i)
{
undoableImagesArray = v;
undoableImage = img;
undoableIndex = i;
}
public void undo() throws CannotUndoException
{
undoableImagesArray.remove(undoableImage);
if(!undoableImagesArray.isEmpty())
image = (BufferedImage)undoableImagesArray.get(undoableImagesArray.size()-1);
canvas.repaint();
}
public void redo() throws CannotRedoException
{
undoableImagesArray.add(undoableImage);
image = (BufferedImage)undoableImagesArray.get(undoableImagesArray.size()-1);
canvas.repaint();
}
public boolean canUndo() { return true; }
public boolean canRedo() { return true; }
}//end class AddEdit
public static void main(String[] s)
{
Painter p = new Painter();
p.setSize(800, 500);
p.setVisible(true);
p.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}//END CLASS Painter
You don't use undoableIndex. In fact you should not remove the image from array but instead move the pointer.
canUndo() should return true if the list is not empty and the pointer >0
canRedo() should return true if the pointer!= size() of the list