java enumeration loop doubles up on repeat of cycle - java

I'm trying to make a trivia game in swing with CardLayout to change between screens in a single JFrame. I have a loop set to move through the states in my enumeration. Everything works quite well except for when, at the end of a game, if I choose to start a new game, it somehow goes to both the first and second states simultaneously. I'm not sure how this happens as this is the only state this happens in.
Code for main:
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.awt.CardLayout;
import java.awt.Color;
import java.io.IOException;
import java.util.Random;
import javax.swing.*;
public class Main
{
public enum State {SPLASH, PREQUESTION, QUESTION, WIN, LOSS};
public static JFrame frame;
public static JPanel content;
public static JPanel neutral;
public static int event;
public static Random rn = new Random();
public static Splash splash;
public static Prequestion eventStart;
public static Question question;
public static Win win;
public static Loss loss;
public static void main(String[] args) throws LineUnavailableException, UnsupportedAudioFileException, IOException
{
frame = new JFrame("DREWcathalon");
content = new JPanel();
neutral = new JPanel();
CardLayout cards = new CardLayout();
content.setLayout(cards);
neutral.setBackground(Color.WHITE);
content.add(neutral, "neutral");
frame.add(content);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 750);
frame.setVisible(true);
cards.show(content, "neutral");
State gameState = State.SPLASH;
while (true)
{
switch(gameState)
{
case SPLASH:
event = 0;
splash = new Splash(content, cards);
splash.splashSound();
while (splash.holdState()){};
cards.show(content, "neutral");
gameState = State.PREQUESTION;
splash = null;
break;
case PREQUESTION:
event++;
eventStart = new Prequestion(event, content, cards);
while (eventStart.holdState()){};
cards.show(content, "neutral");
gameState = State.QUESTION;
eventStart = null;
break;
case QUESTION:
question = new Question(event, rn.nextInt(5)+1, content, cards);
while (question.holdState()){};
cards.show(content, "neutral");
if (question.rightState() && event > 9)
{
gameState = State.WIN;
}
else if (question.rightState())
{
gameState = State.PREQUESTION;
}
else
{
gameState = State.LOSS;
}
question = null;
break;
case WIN:
win = new Win(content, cards);
while(win.holdState()){};
cards.show(content, "neutral");
gameState = State.SPLASH;
win = null;
break;
case LOSS:
loss = new Loss(content, cards);
while(loss.holdState()){};
cards.show(content, "neutral");
gameState = State.SPLASH;
loss = null;
break;
}
}
}
}
Code for Win:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.sound.sampled.*;
import java.io.*;
public class Win
{
static private Clip clip;
static private boolean hold = true;
public Win(JPanel content, CardLayout cards) throws UnsupportedAudioFileException, IOException, LineUnavailableException
{
JButton restart = new JButton("New Game+");
JButton quit = new JButton("Flee");
JLabel title = new JLabel("A Winnar is You!");
JPanel wContent = new JPanel();
wContent.add(title);
wContent.add(restart);
wContent.add(quit);
wContent.setBackground(Color.WHITE);
content.add(wContent, "win");
cards.show(content, "win");
File soundFile = new File("TEST.wav");
AudioInputStream audioIn = AudioSystem.getAudioInputStream(soundFile);
clip = AudioSystem.getClip();
clip.open(audioIn);
clip.start();
restart.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
if (clip.getMicrosecondPosition() < clip.getMicrosecondLength())
{
clip.stop();
}
hold = false;
}
});
quit.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
}
public boolean holdState()
{
return hold;
}
}

I don't have the specific answer to your specific question, which is one reason I'm answering this as a Community Wiki, but I can state with confidence that your program structure is broken and is not written in a way that respects either object-oriented programming principles, as evidenced by your copious use of static fields, or GUI event-driven coding principles, as evidenced by your while (true) loop. The solution is to completely restructure your program, getting rid of your while (true) loop and instead use notifications / observer design pattern to drive state changes.
I can almost guarantee that if you fix these issues, especially by getting rid of the while (true) loop and replacing it with event notification and listening, your problem with game restart will resolve.
If you need more specific help, you'll want to create and post your valid SSCCE (please see the link for the details). I tried doing this myself with your code, but it's taking me too long, and the effort really should be yours.
A very simplistic example that uses simple notification to change state
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class Main2 extends JPanel {
private CardLayout cardLayout = new CardLayout();
private MyState myState = MyState.SPLASH;
private MySplash mySplash = new MySplash(this);
private MyPreQuestion myPreQuestion = new MyPreQuestion(this);
private MyQuestion myQuestion = new MyQuestion(this);
public Main2() {
setLayout(cardLayout);
add(mySplash, MyState.SPLASH.toString());
add(myPreQuestion, MyState.PREQUESTION.toString());
add(myQuestion, MyState.QUESTION.toString());
mySplash.startSplashTimer();
}
public void next() {
int index = 0;
for (int i = 0; i < MyState.values().length; i++) {
if (MyState.values()[i] == myState) {
index = i;
}
}
index++;
index %= MyState.values().length;
setState(MyState.values()[index]);
}
public void setState(MyState nextState) {
myState = nextState;
cardLayout.show(this, nextState.toString());
if (myState == MyState.SPLASH) {
mySplash.startSplashTimer();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Main");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new Main2());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
enum MyState {
SPLASH, PREQUESTION, QUESTION // ..., WIN, LOSS
}
#SuppressWarnings("serial")
class NextAction extends AbstractAction {
private Main2 main;
public NextAction(Main2 main) {
super("Next");
putValue(MNEMONIC_KEY, KeyEvent.VK_N);
this.main = main;
}
#Override
public void actionPerformed(ActionEvent e) {
main.next();
}
}
#SuppressWarnings("serial")
abstract class AbstractView extends JPanel {
private Main2 main;
public AbstractView(Main2 main) {
this.main = main;
}
public Main2 getMain() {
return main;
}
public void next() {
main.next();
}
}
#SuppressWarnings("serial")
class MySplash extends AbstractView {
private static final int TIMER_DELAY = 3000;
private Timer timer;
public MySplash(Main2 main) {
super(main);
setPreferredSize(new Dimension(300, 140));
add(new JLabel("My Splash"));
}
public void startSplashTimer() {
timer = new Timer(TIMER_DELAY, e -> {
next();
timer.stop();
});
timer.start();
}
}
#SuppressWarnings("serial")
class MyPreQuestion extends AbstractView {
public MyPreQuestion(Main2 main) {
super(main);
add(new JLabel("Prequestion"));
add(new JButton(new NextAction(main)));
}
}
#SuppressWarnings("serial")
class MyQuestion extends AbstractView {
public MyQuestion(Main2 main) {
super(main);
add(new JLabel("Question"));
add(new JButton(new NextAction(main)));
}
}

Related

How do you seperate Java logic from Swing GUI into different files/packages?

This is just a simple audio player that I was messing around with to dip my feet into making a GUI. How do you efficiently seperate Java logic and the GUI elements into different files and packages? I tried creating methods in the main class to perform the ActionListeners but the "track" object can't be globally used. This is my first time messing with Swing and GUI's in general.
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.io.*;
public class Main extends JFrame{
public static void main(String[] args) throws LineUnavailableException, IOException, UnsupportedAudioFileException {
// Audio
File filePath = new File("src/Tracks/Track.wav");
AudioInputStream stream = AudioSystem.getAudioInputStream(filePath);
Clip track = AudioSystem.getClip();
track.open(stream);
// Buttons
JButton playButton = new JButton("Play");
playButton.addActionListener(e -> {track.start();});
playButton.setHorizontalAlignment(SwingConstants.LEFT);
playButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
playButton.setBounds(0,0,100,50);
JButton pauseButton = new JButton("Pause");
pauseButton.addActionListener(e -> {track.stop();});
pauseButton.setHorizontalAlignment(SwingConstants.CENTER);
pauseButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
pauseButton.setBounds(150,0,100,50);
JButton replayButton = new JButton("Replay");
replayButton.addActionListener(e -> {track.setMicrosecondPosition(0);});
replayButton.setHorizontalAlignment(SwingConstants.RIGHT);
replayButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
replayButton.setBounds(300,0,100,50);
// Frame
JFrame frame = new JFrame();
frame.setTitle("MusicPlayer");
frame.setSize(500, 300);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setVisible(true);
frame.getContentPane().setBackground(new Color(123, 100, 250));
frame.setLayout(new FlowLayout());
frame.add(playButton);
frame.add(pauseButton);
frame.add(replayButton);
}
}
I'd recommend using decomposition, packages, and dependency injection.
Swing encourages developers to put reams of code into large main methods. That's a mistake, in my opinion.
Decompose that main method into classes. Make them independently testable and only assemble the UI from constitutive parts. Only create the JFrame class in the main method. Give the JFrame an instance of JPanel in its constructor.
There ought to be a /ui package with a class that extends JPanel. Give it all the buttons, text boxes, and GUI assets that it needs in its constructors.
Decomposition is your friend. Start breaking the problem up into classes.
Create a factory that gives you track instances. You can reuse it every where it's needed if you do.
Introduction
Your code has problems with implementing Swing as well as being one main method.
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
Let's take this step by step.
Step 1
The first thing I did was rename your main GUI class to something more meaningful, remove the player code, and focus on the GUI.
I added a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
Here's the result of that step.
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class SimpleMusicPlayer implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleMusicPlayer());
}
#Override
public void run() {
// Buttons
JButton playButton = new JButton("Play");
// playButton.addActionListener(e -> {
// track.start();
// });
playButton.setHorizontalAlignment(SwingConstants.LEFT);
playButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
playButton.setBounds(0, 0, 100, 50);
JButton pauseButton = new JButton("Pause");
// pauseButton.addActionListener(e -> {
// track.stop();
// });
pauseButton.setHorizontalAlignment(SwingConstants.CENTER);
pauseButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
pauseButton.setBounds(150, 0, 100, 50);
JButton replayButton = new JButton("Replay");
// replayButton.addActionListener(e -> {
// track.setMicrosecondPosition(0);
// });
replayButton.setHorizontalAlignment(SwingConstants.RIGHT);
replayButton.setFont(new Font("JetBrains Mono", Font.BOLD, 25));
replayButton.setBounds(300, 0, 100, 50);
// Frame
JFrame frame = new JFrame();
frame.setTitle("MusicPlayer");
frame.setSize(500, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.getContentPane().setBackground(new Color(123, 100, 250));
frame.setLayout(new FlowLayout());
frame.add(playButton);
frame.add(pauseButton);
frame.add(replayButton);
}
}
This step took the code out of the static realm.
Step 2
There are a couple of changes in the next step.
I created a JPanel to hold the JButtons. The JPanel uses a FlowLayout to arrange the buttons in a row. I added some spacing around and between the buttons.
I rearranged the JFrame method calls. These methods must be called in a specific order. The setVisible method must be called last.
Here's the result of that step.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleMusicPlayer implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleMusicPlayer());
}
#Override
public void run() {
JFrame frame = new JFrame("Music Player");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.setSize(500, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 5));
panel.setBackground(new Color(123, 100, 250));
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
Font font = new Font("JetBrains Mono", Font.BOLD, 25);
JButton playButton = new JButton("Play");
// playButton.addActionListener(e -> {
// track.start();
// });
playButton.setFont(font);
panel.add(playButton);
JButton pauseButton = new JButton("Pause");
// pauseButton.addActionListener(e -> {
// track.stop();
// });
pauseButton.setFont(font);
panel.add(pauseButton);
JButton replayButton = new JButton("Replay");
// replayButton.addActionListener(e -> {
// track.setMicrosecondPosition(0);
// });
replayButton.setFont(font);
panel.add(replayButton);
return panel;
}
}
Since this is a simple GUI, this concludes the GUI cleanup.
Step 3
Now, we add the audio back in as a separate class. This class becomes the model of our Swing application.
I didn't test this, since I don't have a WAV file handy.
Here's the result of that step.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleMusicPlayer implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleMusicPlayer());
}
private final MusicPlayer player;
public SimpleMusicPlayer() {
this.player = new MusicPlayer("src/Tracks/Track.wav");
}
#Override
public void run() {
JFrame frame = new JFrame("Music Player");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.setSize(500, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 5));
panel.setBackground(new Color(123, 100, 250));
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
Font font = new Font("JetBrains Mono", Font.BOLD, 25);
JButton playButton = new JButton("Play");
playButton.addActionListener(e -> {
player.start();
});
playButton.setFont(font);
panel.add(playButton);
JButton pauseButton = new JButton("Pause");
pauseButton.addActionListener(e -> {
player.stop();
});
pauseButton.setFont(font);
panel.add(pauseButton);
JButton replayButton = new JButton("Replay");
replayButton.addActionListener(e -> {
player.replay();
});
replayButton.setFont(font);
panel.add(replayButton);
return panel;
}
public class MusicPlayer {
private final Clip track;
public MusicPlayer(String filename) {
this.track = openClip(filename);
}
private Clip openClip(String filename) {
try {
File filePath = new File(filename);
AudioInputStream stream = AudioSystem.getAudioInputStream(filePath);
Clip track = AudioSystem.getClip();
track.open(stream);
return track;
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
return null;
}
public void start() {
if (track != null) {
track.start();
}
}
public void stop() {
if (track != null) {
track.stop();
}
}
public void replay() {
if (track != null) {
track.setMicrosecondPosition(0);
}
}
}
}
There are lots of way you might achieve this, but lets break down you problems...
UI Customisations...
Swing is generally highly customisable, for example, you could make your own custom look and feel (either in parts or as a whole). I think building a whole new look and feel is probably little bit of overkill for your problem, but you can supply a look and feel for individual components, for example...
How to customize a JProgressBar?
Java JButton set text background color
Java: Making a pretty JProgressBar
Now, if all you want to do is change a few properties of the components, then it might be better to simply modify the look and feel default properties instead, for example UIManager.put("Button.font", new Font(...));.
You need to be careful with this approach, as not all UI delegates will use the same keys (looking at you Nimbus) and also consider that this will set the default properties used for ALL new components which you create.
Alternatively, if you just wanted to modify a given set of components, you might consider using a "factory" style workflow, for example...
public class Style {
public static JButton makeButton() {
JButton btn = new JButton();
// Apply any configuration you need
// by default
return btn;
}
}
This would then then give you a centralised location for the creation of these components, as well as a centralised management workflow should you want to tweak it.
Or, you could use a builder pattern, which can be used to configure the component based on how you want to use it...
public class ButtonBuilder {
private String title;
private ActionListener actionListener;
public ButtonBuilder withTitle(String title) {
this.title = title;
return this;
}
public ButtonBuilder withActionListener(ActionListener actionListener) {
this.actionListener = actionListener;
return this;
}
// Add any additional properties you might like to configure
public JButton build() {
JButton btn = new JButton();
if (title != null) {
btn.setText(title);
}
if (actionListener != null) {
btn.addActionListener(actionListener);
}
// Configure the button as you see fit
return btn;
}
}
I would also consider looking at How to Use Actions which is a great way creating a self contained workflows, but which can easily be applied to different areas of the UI.
Code design...
The following concepts apply to just about any type of coding you might do. You want to decouple the code, reducing cohesion between classes - stop and think, if I changed something here, how hard would it be to modify the code? Then work towards a solution which is easily maintained and managed.
When you're presented with a problem try breaking the problem down into as many small units of work as possible (as state - decomposition)
These support the Single Responsibility Principle
For example, you have a "media player". Is it responsible for loading the media? Is it responsible for managing the live cycle of the media? Does it care where the media comes from our how it's actually played?
In general, the answer is, no. It only cares about allowing the user to select an action and applying the action to the current "track"
Let's break it down. Starting with a concept of a "track"...
public interface TrackModel {
enum State {
STOPPED, PLAYING, PAUSED;
}
public Clip getClip();
public State getState();
public void open() throws LineUnavailableException, IOException;
public void close();
public boolean isOpen();
public void play() throws IOException;
public void stop();
public void pause();
public void addChangeListener(ChangeListener changeListener);
public void removeChangeListener(ChangeListener changeListener);
}
Now, a "track" could be manage any thing, a single, an album, a playlist, we don't care, we just care about what the track can do.
It has functionality to control the life cycle (open/close), the state (play/pause/stop) and provides an Observer Pattern so we can observer state changes to it. It doesn't describe how those actions actually work, only that anyone with a reference to an implementation can perform these operations on it.
Personally, I always like to make use of a abstract implementation to define some of the more "common" or "boiler plate" operations, for example, the handling of state changes is going to pretty common, so, we'll put those into out base implementation...
public abstract class AbstractTrackModel implements TrackModel {
private State state;
private List<ChangeListener> changeListeners;
public AbstractTrackModel() {
state = State.STOPPED;
changeListeners = new ArrayList<>(8);
}
protected void setState(State state) {
if (this.state == state) {
return;
}
this.state = state;
fireStateDidChange();
}
#Override
public State getState() {
return state;
}
#Override
public void addChangeListener(ChangeListener changeListener) {
changeListeners.add(changeListener);
}
#Override
public void removeChangeListener(ChangeListener changeListener) {
changeListeners.remove(changeListener);
}
protected void fireStateDidChange() {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : changeListeners) {
listener.stateChanged(evt);
}
}
}
Next, we need a concrete implementation, so the following is a simple implementation that makes use of the javax.sound.sampled.AudioSystem and javax.sound.sampled.CLip APIs
public class AudioSystemClipTrackModel extends AbstractTrackModel {
private Clip clip;
private AudioInputStream stream;
public AudioSystemClipTrackModel(String named) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
this.stream = AudioSystem.getAudioInputStream(getClass().getResource(named));
this.clip = AudioSystem.getClip();
}
protected AudioInputStream getStream() {
return stream;
}
#Override
public Clip getClip() {
return clip;
}
#Override
public boolean isOpen() {
return getClip().isOpen();
}
#Override
public void open() throws LineUnavailableException, IOException {
if (isOpen()) {
return;
}
getClip().open(getStream());
}
#Override
public void close() {
if (!isOpen()) {
return;
}
getClip().close();
}
#Override
public void play() throws IOException {
if (!isOpen()) {
throw new IOException("Track is not open");
}
clip.start();
setState(State.PLAYING);
}
#Override
public void stop() {
getClip().stop();
getClip().setFramePosition(0);
setState(State.STOPPED);
}
#Override
public void pause() {
getClip().stop();
setState(State.PAUSED);
}
}
Wow, we haven't even got to the UI yet! But the nice thing about this, is these classes will all work nicely without them.
Next, we look at the UI...
public class PlayerPane extends JPanel {
private TrackModel model;
private JLabel stateLabel;
private ChangeListener changeListener;
private PlayerControlsPane controlsPane;
public PlayerPane(TrackModel model) {
changeListener = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
configureState();
}
};
stateLabel = new JLabel("...");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = gbc.REMAINDER;
controlsPane = new PlayerControlsPane(model);
add(stateLabel, gbc);
add(controlsPane, gbc);
setModel(model);
}
public void setModel(TrackModel model) {
if (this.model != null) {
this.model.removeChangeListener(changeListener);
}
this.model = model;
this.model.addChangeListener(changeListener);
configureState();
controlsPane.setModel(model);
}
public TrackModel getModel() {
return model;
}
protected void configureState() {
switch (getModel().getState()) {
case STOPPED:
stateLabel.setText("Stopped");
break;
case PLAYING:
stateLabel.setText("Playing");
break;
case PAUSED:
stateLabel.setText("Paused");
break;
}
}
}
public class PlayerControlsPane extends JPanel {
private TrackModel model;
private JButton playButton;
private JButton stopButton;
private JButton pauseButton;
private ChangeListener changeListener;
public PlayerControlsPane(TrackModel model) {
changeListener = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
configureState();
}
};
setLayout(new GridLayout(1, -1));
playButton = Style.makeButton();
playButton.setText("Play");
playButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
model.play();
} catch (IOException ex) {
JOptionPane.showMessageDialog(PlayerControlsPane.this, "Track is not playable");
}
}
});
stopButton = new ButtonBuilder()
.withTitle("Stop")
.withActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.stop();
}
})
.build();
pauseButton = Style.makeButton(new PauseAction(model));
add(playButton);
add(stopButton);
add(pauseButton);
setModel(model);
}
public void setModel(TrackModel model) {
if (this.model != null) {
this.model.removeChangeListener(changeListener);
}
this.model = model;
this.model.addChangeListener(changeListener);
configureState();
}
public TrackModel getModel() {
return model;
}
protected void configureState() {
switch (getModel().getState()) {
case STOPPED:
playButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
break;
case PLAYING:
playButton.setEnabled(false);
stopButton.setEnabled(true);
pauseButton.setEnabled(true);
break;
case PAUSED:
playButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
break;
}
}
}
public class PauseAction extends AbstractAction {
private TrackModel model;
public PauseAction(TrackModel model) {
this.model = model;
putValue(NAME, "Pause");
// Bunch of other possible keys
}
#Override
public void actionPerformed(ActionEvent e) {
model.pause();
}
}
The UI is, for demonstration purposes, broken into three elements.
You have the primary player component, which contains a PlayerControlsPane and makes use of the PauseAction (again, for demonstration).
The important thing here is, the player and control components are making us of the same model. They each attach a "observer" to the model and update their states independently based on the changes to the model.
This plays into the concept of Dependency injection, for example and example.
It also allows the player and controls to have different layout managers, without a lot of juggling.
Now, do you need to do this with ever UI you make, no, but you should aim to do as many as possible, perfect practice makes perfect 😉
Parting comments...
Extending from a top level container, like JFrame is generally discouraged, you're not adding any new functionality to the class and you're locking yourself into a single use case. What happens if you want to put the player into a different container? Well, in your case, you can't, you're stuck.
This also supports Composition Over Inheritance, for reference...
Composition over inheritance
Prefer composition over inheritance?
... and I'm sure you can find lots more discussion on the subject.
And, this File filePath = new File("src/Tracks/Track.wav"); is going to be problematic. Never reference src in your code, it won't exist once the program is built and exported.
When the resource is embedded like this, you will need to use Class#getResource or Class#getResourceAsStream, depending on your API requirements - this is demonstrated in the code examples
You should probably also look at the Model–view–controller concept.
Runnable example...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import static javax.swing.Action.NAME;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
TrackModel track = new AudioSystemClipTrackModel("/sound/your embedded audio file");
track.open();
frame.add(new PlayerPane(track));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public interface TrackModel {
enum State {
STOPPED, PLAYING, PAUSED;
}
public Clip getClip();
public State getState();
public void open() throws LineUnavailableException, IOException;
public void close();
public boolean isOpen();
public void play() throws IOException;
public void stop();
public void pause();
public void addChangeListener(ChangeListener changeListener);
public void removeChangeListener(ChangeListener changeListener);
}
public abstract class AbstractTrackModel implements TrackModel {
private State state;
private List<ChangeListener> changeListeners;
public AbstractTrackModel() {
state = State.STOPPED;
changeListeners = new ArrayList<>(8);
}
protected void setState(State state) {
if (this.state == state) {
return;
}
this.state = state;
fireStateDidChange();
}
#Override
public State getState() {
return state;
}
#Override
public void addChangeListener(ChangeListener changeListener) {
changeListeners.add(changeListener);
}
#Override
public void removeChangeListener(ChangeListener changeListener) {
changeListeners.remove(changeListener);
}
protected void fireStateDidChange() {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : changeListeners) {
listener.stateChanged(evt);
}
}
}
public class AudioSystemClipTrackModel extends AbstractTrackModel {
private Clip clip;
private AudioInputStream stream;
public AudioSystemClipTrackModel(String named) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
this.stream = AudioSystem.getAudioInputStream(getClass().getResource(named));
this.clip = AudioSystem.getClip();
}
protected AudioInputStream getStream() {
return stream;
}
#Override
public Clip getClip() {
return clip;
}
#Override
public boolean isOpen() {
return getClip().isOpen();
}
#Override
public void open() throws LineUnavailableException, IOException {
if (isOpen()) {
return;
}
getClip().open(getStream());
}
#Override
public void close() {
if (!isOpen()) {
return;
}
getClip().close();
}
#Override
public void play() throws IOException {
if (!isOpen()) {
throw new IOException("Track is not open");
}
clip.start();
setState(State.PLAYING);
}
#Override
public void stop() {
getClip().stop();
getClip().setFramePosition(0);
setState(State.STOPPED);
}
#Override
public void pause() {
getClip().stop();
setState(State.PAUSED);
}
}
public class Style {
public static JButton makeButton() {
JButton btn = new JButton();
// Apply any configuration you need
// by default
return btn;
}
public static JButton makeButton(Action action) {
JButton btn = makeButton();
btn.setAction(action);
return btn;
}
}
public class ButtonBuilder {
private String title;
private ActionListener actionListener;
public ButtonBuilder withTitle(String title) {
this.title = title;
return this;
}
public ButtonBuilder withActionListener(ActionListener actionListener) {
this.actionListener = actionListener;
return this;
}
// And any additional properties you might like to configure
public JButton build() {
JButton btn = new JButton();
if (title != null) {
btn.setText(title);
}
if (actionListener != null) {
btn.addActionListener(actionListener);
}
// Configure the button as you see fit
return btn;
}
}
public class PlayerPane extends JPanel {
private TrackModel model;
private JLabel stateLabel;
private ChangeListener changeListener;
private PlayerControlsPane controlsPane;
public PlayerPane(TrackModel model) {
changeListener = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
configureState();
}
};
stateLabel = new JLabel("...");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = gbc.REMAINDER;
controlsPane = new PlayerControlsPane(model);
add(stateLabel, gbc);
add(controlsPane, gbc);
setModel(model);
}
public void setModel(TrackModel model) {
if (this.model != null) {
this.model.removeChangeListener(changeListener);
}
this.model = model;
this.model.addChangeListener(changeListener);
configureState();
controlsPane.setModel(model);
}
public TrackModel getModel() {
return model;
}
protected void configureState() {
switch (getModel().getState()) {
case STOPPED:
stateLabel.setText("Stopped");
break;
case PLAYING:
stateLabel.setText("Playing");
break;
case PAUSED:
stateLabel.setText("Paused");
break;
}
}
}
public class PlayerControlsPane extends JPanel {
private TrackModel model;
private JButton playButton;
private JButton stopButton;
private JButton pauseButton;
private ChangeListener changeListener;
public PlayerControlsPane(TrackModel model) {
changeListener = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
configureState();
}
};
setLayout(new GridLayout(1, -1));
playButton = Style.makeButton();
playButton.setText("Play");
playButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
model.play();
} catch (IOException ex) {
JOptionPane.showMessageDialog(PlayerControlsPane.this, "Track is not playable");
}
}
});
stopButton = new ButtonBuilder()
.withTitle("Stop")
.withActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
model.stop();
}
})
.build();
pauseButton = Style.makeButton(new PauseAction(model));
add(playButton);
add(stopButton);
add(pauseButton);
setModel(model);
}
public void setModel(TrackModel model) {
if (this.model != null) {
this.model.removeChangeListener(changeListener);
}
this.model = model;
this.model.addChangeListener(changeListener);
configureState();
}
public TrackModel getModel() {
return model;
}
protected void configureState() {
switch (getModel().getState()) {
case STOPPED:
playButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
break;
case PLAYING:
playButton.setEnabled(false);
stopButton.setEnabled(true);
pauseButton.setEnabled(true);
break;
case PAUSED:
playButton.setEnabled(true);
stopButton.setEnabled(false);
pauseButton.setEnabled(false);
break;
}
}
}
public class PauseAction extends AbstractAction {
private TrackModel model;
public PauseAction(TrackModel model) {
this.model = model;
putValue(NAME, "Pause");
// Bunch of other possible keys
}
#Override
public void actionPerformed(ActionEvent e) {
model.pause();
}
}
}

Need assistance java Thread slot machine

I need to make a slot machine that implements thread in java and jframe
this is what iv'e done so far kindly tell me what i need to do in order make the images change per .5 seconds when i press the the play and stop when i press stop. If all the three images are the same it'll say you won. This is what iv'e got so far how will i change this numbers or text to images in jlabel.
public class MySlotNumber extends JFrame{
private MyJLabel x;
private MyJLabel y;
private MyJLabel z;
private JButton btn;
public MySlotNumber(){
super("ABC");
setLayout(new FlowLayout());
Font font = new Font("arial",Font.ITALIC,50);
x = new MyJLabel();
x.setFont(font);
y = new MyJLabel();
y.setFont(font);
z = new MyJLabel();
z.setFont(font);
btn = new JButton("PLAY");
btn.setFont(font);
add(x);
add(y);
add(z);
add(btn);
final Thread thx = new Thread(x);
final Thread thy = new Thread(y);
final Thread thz = new Thread(z);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("PLAY")){
if(thx.isAlive()){
thx.resume();
thy.resume();
thz.resume();
} else {
thx.start();
thy.start();
thz.start();
}
btn.setText("STOP");
} else {
thx.suspend();
thy.suspend();
thz.suspend();
btn.setText("PLAY");
System.out.println(x.getText());
}
}
});
}
- - - - - - --
public class MyJLabel extends JLabel implements Runnable{
private Random r;
private int ctr;
private final int T = 500;
public MyJLabel(){
setText("0");
ctr = 0;
r= new Random();
}
#Override
public void run() {
while(true){
try {
Thread.sleep(T);
} catch (InterruptedException ex) {
Logger.getLogger(MyJLabel.class.getName()).log(Level.SEVERE, null, ex);
}
//ctr++;
ctr = r.nextInt(9)+1;
setText(String.valueOf(ctr));
}
}
}
Here is one way to put a picture on a JLabel and change it when you click a button. (I am using window builder for eclipse, so it may look a little odd.) It is not recommended you use absolute paths because when you move your project or the file, it breaks (I did this just for proof of concept).
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.BorderLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Test {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Test window = new Test();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public Test() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 1379, 643);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel lblNewLabel = new JLabel("");
lblNewLabel.setIcon(new ImageIcon("C:\\Users\\Andrew\\Pictures\\Random Pictures\\Capture.JPG"));
frame.getContentPane().add(lblNewLabel, BorderLayout.CENTER);
JButton btnClickMe = new JButton("click me");
btnClickMe.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
lblNewLabel.setIcon(new ImageIcon("C:\\Users\\Andrew\\Pictures\\Random Pictures\\I'm pretty sure he did.JPG"));
}
});
frame.getContentPane().add(btnClickMe, BorderLayout.EAST);
}
}

Java program hangs on stopping audio

I have written this code for a simple music player, the problem is that when I click openLabel and open a song and then when I pause it by clicking playLabel the program stops execution (program hangs).
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.Random;
import javax.sound.sampled.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileNameExtensionFilter;
public class A extends MouseAdapter implements ChangeListener, Runnable {
private ImageIcon playImage = new ImageIcon(getClass().getResource("Images/play.png"));
private ImageIcon pauseImage = new ImageIcon(getClass().getResource("Images/pause.png"));
private ImageIcon openImage = new ImageIcon(getClass().getResource("Images/open.png"));
private JLabel playLabel = new JLabel(playImage);
private JLabel openLabel = new JLabel(openImage);
public JFrame frame = new JFrame();
public JPanel colorPanel = new JPanel();
private enum Status {ON,OFF,PAUSE,END};
private Status playStatus=Status.OFF;
private JSlider slider = new JSlider();
public Clip songClip;
Thread screenThread = new Thread(this);
public static void main(String arg[]) throws Exception {
new A();
}
public A() throws Exception {
setFrame();
setComponents();
}
public void setFrame() {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.setCursor(new Cursor(Cursor.HAND_CURSOR));
frame.getContentPane().setBackground(Color.BLACK);
frame.setVisible(true);
}
public void setComponents() {
slider.setBounds(0,640,1000,15);
slider.setBackground(Color.BLACK);
slider.addChangeListener(this);
slider.setValue(0);
playLabel.setBounds(450,665,100,100);
playLabel.addMouseListener(this);
openLabel.setBounds(540,690,60,60);
openLabel.addMouseListener(this);
colorPanel.setBackground(Color.BLACK);
colorPanel.setBounds(0,100,1500,500);
frame.add(openLabel);
frame.add(playLabel);
frame.add(colorPanel);
frame.add(slider);
}
public void mouseClicked(MouseEvent clicked) {
if (clicked.getSource() == openLabel) {
openLabel.setIcon(openImage);
open();
}
else if (clicked.getSource()==playLabel && playStatus != Status.OFF) {
if (playStatus == Status.PAUSE) {
songClip.start();
screenThread.resume();
playStatus=Status.ON;
playLabel.setIcon(pauseImage);
}
else if (playStatus == Status.ON) {
songClip.stop();
screenThread.suspend();
playStatus=Status.PAUSE;
playLabel.setIcon(playImage);
}
else if (playStatus==Status.END) {
songClip.setMicrosecondPosition(0);
slider.setValue(0);
songClip.start();
screenThread.resume();
playStatus = Status.ON;
playLabel.setIcon(pauseImage);
}
}
}
public void stateChanged(ChangeEvent e) {
if (playStatus != Status.OFF) {
JSlider jslider = (JSlider)e.getSource();
int position = jslider.getValue();
songClip.setMicrosecondPosition(position * 1000000);
}
}
public void open() {
JFileChooser chooseSong = new JFileChooser();
chooseSong.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooseSong.setFileFilter(new FileNameExtensionFilter(null, "wav"));
int chooseButton = chooseSong.showOpenDialog(null);
File songPath = chooseSong.getSelectedFile();
if ( (chooseButton!=JFileChooser.CANCEL_OPTION) && (songPath!=null) && (songPath.getName() != null) ) {
try {
playLabel.setIcon(pauseImage);
if (playStatus != Status.OFF)
songClip.close();
AudioInputStream songFile = AudioSystem.getAudioInputStream(songPath);
songClip = AudioSystem.getClip();
songClip.open(songFile);
int clipLength = (int)(songClip.getMicrosecondLength() / 1000000);
slider.setMinimum(0);
slider.setMaximum(clipLength);
songClip.start();
if (playStatus == Status.OFF)
screenThread.start();
else if (playStatus != Status.OFF)
screenThread.resume();
playStatus=Status.ON;
}
catch(Exception exp) {
Toolkit.getDefaultToolkit().beep();
JOptionPane.showMessageDialog(null, String.format("ERROR = %s",exp.getClass()));
System.exit(0);
}
}
}
public void run() {
while (true) {
colorPanel.setBackground(new Color(new Random().nextInt(256), new Random().nextInt(256), new Random().nextInt(256)));
if (songClip.getMicrosecondPosition() == songClip.getMicrosecondLength()) {
screenThread.suspend();
playStatus=Status.END;
playLabel.setIcon(playImage);
}
}
}
}
This method has been deprecated, as it is inherently deadlock-prone.
This is the reason that is given for the deprecation of Thread.stop(), .suspend() and .resume(). As you are using these in your code it could be the problem.
You are calling screenThread.suspend(); in the block that responds to clicking the play/pause button. Thread methods suspend() and resume() are deadlock prone - that is, using them often causes hard-to-diagnose issues like the one you're having.
You need to remove the use of these methods by instead polling a variable, as described on this page.

Gridlayout, different action to each cell on same mousevent

I have a Gridlayout filled with images loaded from File and I want to add a different, for example, popup, on mouseEvent mouseClicked for each image. How do I determine which image I'm clicking? I've tried adding GetComponentAt(Point), but Eclipse keeps showing that as an unidentified method for the mouseAdapter, and how do I determine the fields for the if statement?
This is what I have:
public class testclass implements ItemListener {
JPanel template;
final static String title = "Title";
public void testclass (Container window){
JPanel index = new JPanel();
String index2[] = {title};
JComboBox index3 = new JComboBox(index2);
index3.setEditable(false);
index3.addItemListener(this);
index.add(index3);
File folder = new File("images/");
File[] listOfFiles = folder.listFiles();
String nr;
final JPanel panel = new JPanel(new GridLayout(4, 4, 4, 4));
for (int i = 0; i < listOfFiles.length; i++) {
nr = "images/" + listOfFiles[i].getName();
final ImageIcon images = new ImageIcon(nr);
final JLabel display[] = new JLabel[1];
for (int n = 0; n < 1; n++){
display[n] = new JLabel(images);
panel.add(display[n]);
} }
panel.addMouseListener(new MouseAdapter()
{ public void mouseClicked (MouseEvent e)
{ //JPanel panel = (JPanel) getComponentAt(e.getPoint());
JOptionPane.showMessageDialog(null, "Message");
}});
template = new JPanel(new CardLayout());
template.add(panel, title);
window.add(index, BorderLayout.PAGE_START);
window.add(template, BorderLayout.CENTER);
}
public void itemStateChanged(ItemEvent event){
CardLayout change = (CardLayout)(template.getLayout());
change.show(template, (String)event.getItem());
}
private static void userinterface() {
JFrame showwindow = new JFrame("Window");
showwindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
testclass show = new testclass();
show.testclass(showwindow.getContentPane());
showwindow.pack();
showwindow.setVisible(true);
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch(Exception e){
}
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
userinterface();
}
});
}
}
Edit: Proper Answer
Thankyou for providing code. I have adjusted some things so hopefully you can understand them.
Firstly I have added a new object, ImageButton. From when I added the actionListener the functionality you wanted was already done (minus the good looks, you will have to play around with that, or ask another question)
Added a 'dir' string so that you don't have to keep copy and pasting the directory address.
I had to adjust the size of the window through the userInterface() method, you should look at being able to shorten the sequence of being able to adjust the parameters, maybe another GUI object to keep all that information together.
A couple of things:
The code you wrote was good but it would need for you to adjust alot of (getting the size of windows and repeatedly check for different sizes if you adjusted the window size where a mouse click could click on other image that you don't want!) in order for a mouseListener to work, I am guessing with a wide range of images you would be providing.
Putting comments in your code can help both you and someone trying to help you.
Anyways, please upvote/accept answer if this helps out, which I'm sure it does.
Good Luck!
Put these files into your eclipse and run them it should work if you adjust the dir string to follow your original directory path.
testclass.java
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class testclass implements ItemListener {
JPanel template;
final static String title = "Title";
final String dir = "images/";
public void testclass(Container window) {
JPanel index = new JPanel();
String[] index2 = { title };
JComboBox index3 = new JComboBox(index2);
index3.setEditable(false);
index3.addItemListener(this);
index.add(index3);
index.setSize(500, 500);
File folder = new File(dir);
File[] listOfFiles = folder.listFiles();
String nr;
final JPanel panel = new JPanel(new GridLayout(4, 4, 4, 4));
for (int i = 0; i < listOfFiles.length; i++) {
nr = dir + listOfFiles[i].getName();
panel.add(new ImageButton(nr));
}
template = new JPanel(new CardLayout());
template.add(panel, title);
window.add(template, BorderLayout.CENTER);
window.add(index, BorderLayout.NORTH);
window.setVisible(true);
}
public void itemStateChanged(ItemEvent event) {
CardLayout change = (CardLayout) (template.getLayout());
change.show(template, (String) event.getItem());
}
private static void userinterface() {
JFrame showwindow = new JFrame("Window");
showwindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
testclass show = new testclass();
show.testclass(showwindow.getContentPane());
showwindow.pack();
showwindow.setVisible(true);
showwindow.setSize(500, 500);
}
public static void main(String[] args) {
try {
UIManager
.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
}
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
userinterface();
}
});
}
}
ImageButton.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
public class ImageButton extends JButton {
private String fileName;
private ImageIcon image;
private JButton button;
public ImageButton(String fileName) {
setFileName(fileName);
setImage(new ImageIcon(fileName));
setButton(new JButton(getImage()));
this.setIcon(getImage());
this.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(null, ae.getSource());
}
});
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public ImageIcon getImage() {
return image;
}
public void setImage(ImageIcon image) {
this.image = image;
}
public JButton getButton() {
return button;
}
public void setButton(JButton button) {
this.button = button;
}
}

Repaint thread works fine when triggering business function, but freezes when triggered by reference

It's a bit difficult to explain but here is the situation:
I'm programming a Cellular Automation program and have a mainScreen class that extends the JFrame and that JFrame includes the custom Jpanel that is also a thread that continues to repaint his self. The main class (App) creates the mainScreen.
Now is the strange thing that it is working (real-time view of the generations) when i call the business function (that is envolving the cells in a while loop) from the App class, but when i call the same function from the MainScreen class (through keyboard input) then the repainting doesn't accour, but i see console output that the generations are envolving.. And the program isn't responding to the close cross of the window, and in the other scenario it closes just like normal when running the algorithm.
So, why isn't my Jpanel repainting?
Hope you guys (girls) can help.
Class relation: App<-MainScreen<-MapPanel
App
package Business;
import GUI.MainScreen;
import java.util.Random;
public class App {
public static final int _CELL_SIZE = 5;
public static final int _WIN_WIDTH = 800;
public static final int _WIN_HEIGTH = 600;
public static final int _HELP_HEIGTH = 72;
public static final double _LIFE_START = 0.1F;
private MainScreen _mainScreen;
private Cell[][] _map;
private int _generation;
private Random _random;
private boolean _running;
public App() {
_generation = 0;
_running = false;
_random = new Random(System.currentTimeMillis());
newMap();
_mainScreen = new MainScreen(this);
//envolveCells();
//cout();
}
public void envolveCells() {
_generation = 0;
_running = true;
Cell[][] newMap = new Cell[getNumOfRows()][getNumOfCells()];
while(_running) {
newMap = new Cell[getNumOfRows()][getNumOfCells()];
//envolve cells
for(int row = 0; row < getNumOfRows(); row++) {
for(int cell = 0; cell < getNumOfCells(); cell++) {
System.out.println(_map[row][cell].envolve());
newMap[row][cell] = new Cell(_map[row][cell].envolve());
newMap[row][cell].setNeighbors(_map[row][cell].getNeighbors());
}
}
_map = newMap;
_generation++;
try {
Thread.sleep(100);
} catch(Exception ex) { }
}
}
package GUI;
import Business.App;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class MainScreen extends JFrame implements KeyListener {
private App _app;
private MapPanel _mapPanel;
public MainScreen(App app){
_app = app;
_mapPanel = new MapPanel(app);
setTitle("Cellular Automaton sandbox - Sven van Zoelen");
setSize(App._WIN_WIDTH, App._WIN_HEIGTH + App._HELP_HEIGTH);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(_mapPanel);
addKeyListener(this);
setVisible(true);
}
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == 'r') {
System.out.println("Run mode");
_app.envolveCells();
}
}
MapPanel
package GUI;
import Business.App;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class MapPanel extends JPanel implements Runnable {
private App _app;
private Font _font = new Font("Arial", Font.PLAIN, 10);
public MapPanel(App app) {
_font = new Font("Arial", Font.PLAIN, 10);
_app = app;
new Thread(this).start();
}
....
public void run() {
while(true) {
repaint();
try {
Thread.sleep(50);
} catch(Exception ex) { }
}
}
}
Your "business logic" looks like it should run at a separate thread from the GUI. Thus, don't invoke it directly from the GUI thread (in the action listener), but put it in a new Thread there.
As long as your action listener (or key-listener, in this case) does not return, your GUI will not be painted, as painting occurs in the same thread as input-handling.
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == 'r') {
new Thread("envolver") { public void run() {
System.out.println("Run mode");
_app.envolveCells();
}).start();
}
}

Categories