Modifying independent JPanels from the JFrame - java

I've got a JFrame with two separate JPanels. One of the JPanels is filled with JButtons while the other has a couple of text fields. I added mouse listeners to the buttons via the JFrame and I want to make it so that when an event is fired from the first JPanel, the text fields in the second JPanel are changed. The two panels have their own classes. How would I go about doing this?

Use MVC, Model-View-Control, separation of concerns.
Have the Control, which holds your listeners, change the state of the model.
The Views -- your GUI's, have listeners added to them by the control, so that user input is transmitted to the control and thereby to the model.
The View can also either directly add listeners to the model so that they can change their display if the model changes, or often this is done indirectly through the control.
Don't add MouseListeners to JButtons. Use ActionListeners as that's what they're for. For example, if you disable a JButton, any ActionListeners attached to it won't work -- which is correct behavior. The same is not true for MouseListeners.
For more specific help, consider creating and posting a minimal example program.
Edit
For example:
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.SwingPropertyChangeSupport;
public class MvcMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MvcView view = new MvcView();
MvcModel model = new MvcModel();
MvcControl control = new MvcControl(view, model);
view.createAndDisplay();
}
});
}
}
class MvcView {
private MvcModel model;
private ButtonPanel buttonPanel = new ButtonPanel();
private TextFieldPanel textFieldPanel = new TextFieldPanel();
private JPanel mainPanel = new JPanel();
public MvcModel getModel() {
return model;
}
public ButtonPanel getButtonPanel() {
return buttonPanel;
}
public TextFieldPanel getTextFieldPanel() {
return textFieldPanel;
}
public MvcView() {
mainPanel.setLayout(new GridLayout(0, 1));
mainPanel.add(textFieldPanel);
mainPanel.add(buttonPanel);
}
public void setModel(MvcModel model) {
this.model = model;
model.addPropertyChangeListener(new ModelListener());
}
public void createAndDisplay() {
JFrame frame = new JFrame("MVC Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (ButtonTitle.class.getCanonicalName().equals(evt.getPropertyName())) {
ButtonTitle newValue = model.getButtonTitle();
textFieldPanel.textFieldSetText(newValue.getTitle());
}
}
}
}
enum ButtonTitle {
START("Start"), STOP("Stop"), PAUSE("Pause");
private String title;
private ButtonTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
#SuppressWarnings("serial")
class ButtonPanel extends JPanel {
public ButtonPanel() {
setBorder(BorderFactory.createTitledBorder("Button Panel"));
setLayout(new GridLayout(1, 0, 5, 0));
for (ButtonTitle btnTitle : ButtonTitle.values()) {
add(new JButton(new ButtonAction(btnTitle)));
}
}
private class ButtonAction extends AbstractAction {
private ButtonTitle btnTitle;
public ButtonAction(ButtonTitle btnTitle) {
super(btnTitle.getTitle());
this.btnTitle = btnTitle;
}
public void actionPerformed(java.awt.event.ActionEvent e) {
Object oldValue = null;
ButtonTitle newValue = btnTitle;
ButtonPanel.this.firePropertyChange(
ButtonTitle.class.getCanonicalName(), oldValue, newValue);
};
}
}
#SuppressWarnings("serial")
class TextFieldPanel extends JPanel {
private JTextField textField = new JTextField(15);
public TextFieldPanel() {
setBorder(BorderFactory.createTitledBorder("TextField Panel"));
add(textField);
}
public void textFieldSetText(String text) {
textField.setText(text);
}
}
class MvcControl {
private MvcView view;
private MvcModel model;
public MvcControl(MvcView view, MvcModel model) {
this.view = view;
this.model = model;
view.setModel(model);
view.getButtonPanel().addPropertyChangeListener(new ButtonPanelListener());
}
private class ButtonPanelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (ButtonTitle.class.getCanonicalName().equals(evt.getPropertyName())) {
ButtonTitle newValue = (ButtonTitle) evt.getNewValue();
model.setButtonTitle(newValue);
}
}
}
}
class MvcModel {
private ButtonTitle buttonTitle;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(
this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public ButtonTitle getButtonTitle() {
return buttonTitle;
}
public void setButtonTitle(ButtonTitle buttonTitle) {
ButtonTitle oldValue = this.buttonTitle;
ButtonTitle newValue = buttonTitle;
this.buttonTitle = buttonTitle;
pcSupport.firePropertyChange(ButtonTitle.class.getCanonicalName(),
oldValue, newValue);
}
}
The example is lacking in use of interfaces which would allow for a further separation of concerns resulting in looser coupling (a good thing).

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

Observer Pattern on MVC for specific fields

On the MVC pattern, which is the best option for the Model to notify the View (if this is the right approach in the first place) where, from all the fields of data the Model is storing, only a couple of them are updated. Specifically when we only want to update specific fields of the View.
I am currently using a MVC pattern with Observer/Subscriber (JAVA Swing) as described here: https://stackoverflow.com/a/6963529 but when the Model updates, it changes everything in the View when the update() funcion is called, it's impossible to determine which field from the Model changed in order to update only the required field in the View.
I read this topic: https://softwareengineering.stackexchange.com/a/359008 and this as well: https://stackoverflow.com/a/9815189 which I think it's usefull, but for the later, I can't understand very well how can I set a propertyChangeListener on a variale (int, float, etc). Also related to this: https://stackoverflow.com/a/9815189
The Main class where the software start to run:
public class Main {
public static void main(String[] args) {
Model m = new Model();
View v = new View(m);
Controller c = new Controller(m, v);
c.initController();
}
}
So the code that I have on Model is this:
public class Model extends Observable {
//...
private float speed;
private int batteryPercentage;
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
setChanged();
notifyObservers();
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
this.batteryPercentage = batteryPercentage;
setChanged();
notifyObservers();
}
}
The view knows the Model:
public class View implements Observer {
private Model model;
private JTextField txtFldSpeed;
private JTextField txtFldBattery;
private JFrame mainWindow;
public View(Model m) {
this.model = m;
initialize();
}
private void initialize() {
mainWindow = new JFrame();
mainWindow.setTitle("New Window");
mainWindow.setMinimumSize(new Dimension(1280, 720));
mainWindow.setBounds(100, 100, 1280, 720);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel tPanel1 = new JPanel();
tPanel1.setBorder(new LineBorder(new Color(0, 0, 0)));
tPanel1.setLayout(null);
mainWindow.getContentPane().add(tPanel1);
mainWindow.getContentPane().add(tPanel1);
txtFldSpeed = new JTextField();
txtFldSpeed.setEditable(false);
txtFldSpeed.setBounds(182, 11, 116, 22);
tPanel1.add(txtFldSpeed);
txtFldBattery = new JTextField();
txtFldBattery.setEditable(false);
txtFldBattery.setBounds(182, 43, 116, 22);
tPanel1.add(txtFldBattery);
mainWindow.setVisible(true);
}
#Override
public void update(Observable o, Object arg) {
txtFldSpeed.setText(Float.toString(model.getSpeed()) + " kn");
txtFldBattery.setText(Integer.toString(model.getBatteryPercentage()) + " %");
}
}
The Controller adds the View as a Observer of the Model:
public class Controller {
private Model model;
private View view;
public Controller(Model m, View v) {
this.model = m;
this.view = v;
}
public void initController() {
model.addObserver(view);
model.setSpeed(10);
}
}
What I am expecting is something that, when the Model is updated, let's say, function setSpeed() is called, the View is told that she needs to update itself on that specific field and not every "changable" field (like the txtFldBattery.
I want to do this because on the View, there are fields being updated a couple of times per second, and because I need to update everything on the view, a JComboBox which doesn't need to update that often, keeps closing when trying to select a option.
I would use SwingPropertyChangeSupport, make each of the model's state fields a "bound property" so that each state field can be listened to separately.
For instance, say you have a model that looked like this:
public class MvcModel {
public static final String SPEED = "speed";
public static final String BATTERY = "battery";
public static final int MAX_SPEED = 40;
private float speed;
private int batteryPercentage;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
float oldValue = this.speed;
float newValue = speed;
this.speed = speed;
pcSupport.firePropertyChange(SPEED, oldValue, newValue);
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
int oldValue = this.batteryPercentage;
int newValue = batteryPercentage;
this.batteryPercentage = batteryPercentage;
pcSupport.firePropertyChange(BATTERY, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
Both the speed and the batteryPercent fields are "bound fields" in that any changes to these fields will trigger the property change support object to fire a notification message to any listeners that have registered with the support object, as reflected in the public void setXxxx(...) methods.
This way the controller could register listeners on the model for whatever properties it wants to listen to, and then notify the view of any changes. For example:
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
The set up could look something like:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class MVC2 {
private static void createAndShowGui() {
MvcModel model = new MvcModel();
MvcView view = new MvcView();
MvcController controller = new MvcController(model, view);
controller.init();
JFrame frame = new JFrame("MVC2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getMainDisplay());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MvcView {
private JPanel mainPanel = new JPanel();
private JSlider speedSlider = new JSlider(0, MvcModel.MAX_SPEED);
private JSlider batterySlider = new JSlider(0, 100);
private JProgressBar speedBar = new JProgressBar(0, MvcModel.MAX_SPEED);
private JProgressBar batteryPercentBar = new JProgressBar(0, 100);
public MvcView() {
speedSlider.setMajorTickSpacing(5);
speedSlider.setMinorTickSpacing(1);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.setPaintTrack(true);
batterySlider.setMajorTickSpacing(20);
batterySlider.setMinorTickSpacing(5);
batterySlider.setPaintTicks(true);
batterySlider.setPaintLabels(true);
batterySlider.setPaintTrack(true);
speedBar.setStringPainted(true);
batteryPercentBar.setStringPainted(true);
JPanel inputPanel = new JPanel(new GridLayout(0, 1));
inputPanel.add(createTitledPanel("Speed", speedSlider));
inputPanel.add(createTitledPanel("Battery %", batterySlider));
JPanel displayPanel = new JPanel(new GridLayout(0, 1));
displayPanel.add(createTitledPanel("Speed", speedBar));
displayPanel.add(createTitledPanel("Battery %", batteryPercentBar));
mainPanel.setLayout(new GridLayout(1, 0));
mainPanel.add(createTitledPanel("Input", inputPanel));
mainPanel.add(createTitledPanel("Display", displayPanel));
}
private JComponent createTitledPanel(String title, JComponent component) {
JPanel titledPanel = new JPanel(new BorderLayout());
titledPanel.setBorder(BorderFactory.createTitledBorder(title));
titledPanel.add(component);
return titledPanel;
}
public JComponent getMainDisplay() {
return mainPanel;
}
public void setSpeed(float speed) {
speedBar.setValue((int) speed);
}
public void setBatteryPercent(int batteryPercent) {
batteryPercentBar.setValue(batteryPercent);
}
public JSlider getSpeedSlider() {
return speedSlider;
}
public JSlider getBatterySlider() {
return batterySlider;
}
}
class MvcController {
private MvcModel model;
private MvcView view;
public MvcController(MvcModel model, MvcView view) {
this.model = model;
this.view = view;
model.addPropertyChangeListener(MvcModel.BATTERY, new BatteryListener());
model.addPropertyChangeListener(MvcModel.SPEED, new SpeedListener());
view.getSpeedSlider().addChangeListener(chngEvent -> {
int value = view.getSpeedSlider().getValue();
model.setSpeed(value);
});
view.getBatterySlider().addChangeListener(chngEvent -> {
int value = view.getBatterySlider().getValue();
model.setBatteryPercentage(value);
});
}
public void init() {
view.getSpeedSlider().setValue(10);
view.getBatterySlider().setValue(100);
model.setSpeed(10);
model.setBatteryPercentage(100);
}
class SpeedListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
class BatteryListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
int batteryPercent = model.getBatteryPercentage();
view.setBatteryPercent(batteryPercent);
}
}
}
Side note: Observer and Observable have been deprecated in the most recent version of Java and so should their use should probably be avoided.
In your update method implementation you can determine with first argument o which Observable has changed and with second argument arg which value changed when you call: notifyObservers(this.speed);
Note that notifyObservers's signature accepts Object, and float primitive is not a subclass of Object.

Using PropertyChangeListener to refresh JFrame (without triggering infinite loop)

Using java, I have a JFrame containing several JPanels which include various JComboBoxes, JTextFields, etc... which connect to entries in xml files, organized and viewed by date. Everything syncs up and is working but I've been having trouble getting the JFrame to update/refresh when changes are being made to the entries (i.e. adding/removing etc), although it does refresh when I change the date. I've got to the point where I have a PropertyChangeListener that gets triggered (prints to console) but when I try to use that Listener to refresh the frame I think I'm only revalidating the listener?
Here's the code from the JFrame (I've used comments to indicate the failed segments):
package interfaceComponents;
import java.beans.*;
import javax.swing.*;
import org.jdesktop.swingx.JXDatePicker;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.io.IOException;
import java.util.*;
import java.time.*;
import java.time.format.*;
public class DailyView extends Frame {
private static final long serialVersionUID = 7827570917642254745L;
private final JXDatePicker calendar = new JXDatePicker();
private JLabel focusPoint;
public DailyView(LocalDate d) throws IOException {
DefaultDateModel model = new DefaultDateModel(d);
OperatorMenus menus = new OperatorMenus();
setJMenuBar(menus);
JPanel body = new JPanel();
body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS));
DayView anchorDay = new DayView(0);
anchorDay.setModel(model);
DayView nextDay = new DayView(1);
nextDay.setModel(model);
body.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("OperatorView.propertyChange");
//***This is where I'm trying to TRIGGER the REFRESH***
refreshFrame(); }
});
body.add(anchorDay);
body.add(nextDay);
add(new JScrollPane(body), BorderLayout.CENTER);
JPanel footer = new JPanel();
NavButtons navPanel = new NavButtons(model);
focusPoint = new JLabel(DateTimeFormatter.ofPattern("E, dd MMM yyyy").format(model.getDate()));
focusPoint.setForeground(Color.RED);
footer.setLayout(new BorderLayout());
footer.add(focusPoint, BorderLayout.CENTER);
footer.add(navPanel, BorderLayout.EAST);
footer.setBackground(Color.BLACK);
add(footer, BorderLayout.SOUTH);
pack(); }
public DailyView() throws IOException { this(LocalDate.now()); }
//interfaces
public interface DateModel {
public LocalDate getDate();
public void addObserver(Observer o);
public void removeObserver(Observer o); }
public interface MutableDateModel extends DateModel {
public void setDate(LocalDate date); }
//methods
public void refreshFrame() { //***This is where I'm trying to TRIGGER the REFRESH***
this.revalidate();
this.repaint(); }
//inner classes
public class DefaultDateModel extends Observable implements MutableDateModel {
private LocalDate date;
public DefaultDateModel(LocalDate d) { date = d; }
#Override
public void setDate(LocalDate d) {
date = d;
setChanged();
notifyObservers(); }
#Override
public LocalDate getDate() {
return date; }
#Override
public void removeObserver(Observer o) {
deleteObserver(o); }
}
public class ShiftFocus extends AbstractAction implements Observer {
private static final long serialVersionUID = 680383526965967229L;
private MutableDateModel model;
private int shift;
public ShiftFocus(MutableDateModel m, int i) {
setModel(m);
shift = i; }
public void actionPerformed(ActionEvent event) {
MutableDateModel model = getModel();
if (model != null) {
model.setDate(model.getDate().plusDays(shift));
calendar.setDate(Date.from(model.getDate().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
pack(); }
}
public void setModel(MutableDateModel value) {
if (model != null) {
model.removeObserver(this); }
this.model = value;
if (model != null) {
model.addObserver(this); }
}
public MutableDateModel getModel() {
return model; }
#Override
public void update(Observable o, Object arg) {
focusPoint.setText(DateTimeFormatter.ofPattern("E, dd MMM yyyy").format(model.getDate())); }
}
class NavButtons extends JPanel implements Observer {
private static final long serialVersionUID = 914087518688373731L;
//instance variables
private JToolBar toolBar = new JToolBar("Navigation");
private JButton weekBack = new JButton("<<");
private JButton dayBack = new JButton("<");
private JButton returnToday = new JButton("Today");
private JButton nextDay = new JButton(">");
private JButton nextWeek = new JButton(">>");
private MutableDateModel model;
//constructor
public NavButtons(MutableDateModel model) {
weekBack.addActionListener(new ShiftFocus(model, -7));
dayBack.addActionListener(new ShiftFocus(model, -1));
returnToday.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
MutableDateModel m = getModel();
m.setDate(LocalDate.now());
setModel(m);
pack(); }
});
nextDay.addActionListener(new ShiftFocus(model, 1));
nextWeek.addActionListener(new ShiftFocus(model, 7));
toolBar.add(weekBack);
toolBar.add(dayBack);
toolBar.add(returnToday);
toolBar.add(nextDay);
toolBar.add(nextWeek);
calendar.setEditable(true);
calendar.setFormats("E, dd MMM yyyy");
calendar.setDate(Date.from(model.getDate().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
calendar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
MutableDateModel model = getModel();
if (model != null) {
model.setDate(LocalDate.parse(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getDate())));
pack(); }
}
});
toolBar.add(calendar);
toolBar.add(new GalileoMode());
add(toolBar);
setModel(model); }
public void setModel (MutableDateModel value) {
if (model != null) {
model.removeObserver(this); }
this.model = value;
if (model != null) {
model.addObserver(this); }
}
public MutableDateModel getModel() {
return model; }
#Override
public void update(Observable o, Object arg) {/* models data changes */}
}
}
EDIT
After some feedback, have tried this so far:
thisFrame = this;
body.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("OperatorView.propertyChange");
// thisFrame.removeAll();
// thisFrame.dispose();
// thisFrame.remove(body);
// thisFrame.add(new JScrollPane(body), BorderLayout.CENTER);
thisFrame.getContentPane().validate();
// thisFrame.revalidate();
thisFrame.getContentPane().repaint();
// thisFrame.refreshFrame();
}
});
with DailyView thisFrame; declared as an instance variable at the top.
EDIT
In case there's anyone out there reading this, I am getting the propertyChangeListener to trigger certain events that would refresh the underlying panels, but that causes an infinite loop as the propertyChangeListener gets called again. (re)validate() and repaint() don't seem to have this issue but they're not refreshing the panels' content...anyone who could point me in the right direction/link to a similar question, etc would be greatly appreciated.
When you use the 'this' pointer, it always refers to the class you are in. I believe your problem is coming because the 'this' pointer does refer to your listener and not the JPanel. Perhaps your problem can be solved by
Frame thisFrame = this;
body.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("OperatorView.propertyChange");
//***This is where I'm trying to TRIGGER the REFRESH***
thisFrame.refreshFrame(); }
});

Unable to resize JPanel in java Swing

I just started using Java Swing, and I was going through the following post: Dynamic fields addition in java/swing form.
I implement the code in this post with some modification, and it worked fine. As mentioned in the post, JPanel is not resizing itself when we add more rows. Can someone throw more light on this issue with easy to understand explanation that how can we resize JPanel as soon as we hit +/- button? Here is the code :
Row class
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTextField;
#SuppressWarnings("serial")
public class Row extends JPanel {
private JTextField quantity;
private JTextField item;
private JTextField price;
private JButton plus;
private JButton minus;
private RowList parent;
public Row(String initialQuantity, String initalPrice, String initialItem, RowList list) {
this.parent = list;
this.plus = new JButton(new AddRowAction());
this.minus = new JButton(new RemoveRowAction());
this.quantity = new JTextField(10);
this.item = new JTextField(10);
this.price = new JTextField(10);
this.quantity.setText(initialQuantity);
this.price.setText(initalPrice);
this.item.setText(initialItem);
add(this.plus);
add(this.minus);
add(this.quantity);
add(this.item);
add(this.price);
}
public class AddRowAction extends AbstractAction {
public AddRowAction() {
super("+");
}
public void actionPerformed(ActionEvent e) {
parent.cloneRow(Row.this);
}
}
public class RemoveRowAction extends AbstractAction {
public RemoveRowAction() {
super("-");
}
public void actionPerformed(ActionEvent e) {
parent.removeItem(Row.this);
}
}
public void enableAdd(boolean enabled) {
this.plus.setEnabled(enabled);
}
public void enableMinus(boolean enabled) {
this.minus.setEnabled(enabled);
}
}
RowList class
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class RowList extends JPanel{
private List<Row> rows;
public RowList() {
this.rows = new ArrayList<Row>();
Row initial = new Row("1","0.00","", this);
//addHeaders();
//addGenerateBillButton();
addItem(initial);
}
public void addHeaders(){
JLabel qty = new JLabel("Quantity");
//qty.setBounds(10, 0, 80, 25);
add(qty);
JLabel item = new JLabel("Item");
//item.setBounds(70, 0, 80, 25);
add(item);
JLabel price = new JLabel("Price");
//price.setBounds(120, 0, 80, 25);
add(price);
}
public void addGenerateBillButton(){
JButton billGenerationButton = new JButton("Generate Bill");
add(billGenerationButton);
}
public void cloneRow(Row row) {
Row theClone = new Row("1","0.00","", this);
addItem(theClone);
}
private void addItem(Row row) {
rows.add(row);
add(row);
refresh();
}
public void removeItem(Row entry) {
rows.remove(entry);
remove(entry);
refresh();
}
private void refresh() {
revalidate();
repaint();
if (rows.size() == 1) {
rows.get(0).enableMinus(false);
}
else {
for (Row e : rows) {
e.enableMinus(true);
}
}
}
}
Main class
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Enter Items");
RowList panel = new RowList();
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
You can call the pack() of the frame again.
Try this: Add this in your Row class
public class AddRowAction extends AbstractAction
{
public AddRowAction()
{
super("+");
}
public void actionPerformed(ActionEvent e)
{
parent.cloneRow(Row.this);
((JFrame) SwingUtilities.getRoot(parent)).pack(); // <--- THIS LINE
}
}
public class RemoveRowAction extends AbstractAction
{
public RemoveRowAction()
{
super("-");
}
public void actionPerformed(ActionEvent e)
{
parent.removeItem(Row.this);
((JFrame) SwingUtilities.getRoot(parent)).pack(); // <--- THIS LINE
}
}
You can get the root component (JFrame) using the SwingUtilities.getRoot(comp) from the child component and call the pack() method after your new Row is added to your RowList.
This would resize your JPanel. But your RowList will be horizontal. This is where LayoutManager comes into play.
You can know more about different LayoutManagers here.
To fix this problem, in your RowList panel, set your layout to:
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
Within your refresh method call doLayout() method after revalidate()

Have I got the right idea with SwingWorker

I just wanted to double check my implementation for using a SwingWorker thread is done the correct and clean way. Also, just need verification my implementation of the model-view-controller pattern is correct and clean as well. Everything seems to be working as it should and it seems a nice simple implementation to me.
The Model class.
package Model;
public class Model
{
private int counter;
private boolean go = true;
public Void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
return null;
}
public int getCounter()
{
return counter;
}
public String getCounterToString()
{
return Integer.toString(counter);
}
public void setGo(boolean value)
{
this.go = value;
}
}
The View class.
package View;
import java.awt.*;
import javax.swing.*;
public class View extends JFrame
{
private JPanel topPanel, bottomPanel;
private JTextArea messageArea;
private JButton startButton, cancelButton;
private JLabel messageLabel;
private JScrollPane scrollPane;
public View()
{
setSize(250, 220);
setTitle("View");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
topPanel = new JPanel();
bottomPanel = new JPanel();
messageArea = new JTextArea(8, 20);
messageArea.setEditable(true);
scrollPane = new JScrollPane(messageArea);
messageLabel = new JLabel("Message Area");
topPanel.setLayout(new BorderLayout());
topPanel.add(messageLabel, "North");
topPanel.add(scrollPane, "South");
startButton = new JButton("START");
cancelButton = new JButton("CANCEL");
bottomPanel.setLayout(new GridLayout(1, 2));
bottomPanel.add(startButton);
bottomPanel.add(cancelButton);
Container cp = getContentPane();
cp.add(topPanel, BorderLayout.NORTH);
cp.add(bottomPanel, BorderLayout.SOUTH);
}
public JButton getStartButton()
{
return startButton;
}
public JButton getCancelButton()
{
return cancelButton;
}
public void setMessageArea(String message)
{
messageArea.append(message + "\n");
}
}
The Controller class.
package Controller;
import java.awt.event.*;
import javax.swing.SwingWorker;
import Model.*;
import View.*;
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
private SwingWorker<Void, Void> worker;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true);
worker = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground()
{
//theView.setMessageArea(theModel.getCounterToString());
return theModel.go();
}
#Override
protected void done()
{
//theView.setMessageArea(theModel.getCounterToString());
}
};
worker.execute();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
The Main class.
package swinginfiniteloop;
import Model.*;
import View.*;
import Controller.*;
public class Main
{
public static void main(String[] args)
{
Model model = new Model();
View view = new View();
Controller controller = new Controller(model, view);
view.setVisible(true);
}
}

Categories