I’m writing a program for my company that references an excel file for project information.
I’ve organized the program into 4 JPanels, each in it’s own class. i.e. Panel_1, Panel_2, etc.
I have a button in panel_1 that allows the user to select the project info file and that works just fine.
what I want to do is have that button apply the info from the project to the JLabels in the other JPanels 2, 3, & 4.
UI class
public class UI extends JFrame implements ActionListener {
//Variables declared outside constructor, and to be used in multiple Panels
static String JobNo = "";
static String JobName = "";
static String Attention = "";
static String Contractor = "";
public UI() {
//Define JFrame
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().setLayout(new GroupLayout(this.getContentPane()));
this.setSize(FW + 36, FH);
this.getContentPane().setBackground(col1);
//Add panels to JFrame
Panel_1 panel1 = new Panel_1();
this.add(panel1);
Panel_2 panel2 = new Panel_2();
this.add(panel2);
Panel_3 panel3 = new Panel_3();
this.add(panel3);
Panel_4 panel4 = new Panel_4();
this.add(panel4);
this.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
}
Panel_1 class
public class Panel_1 extends JPanel implements ActionListener{
Panel_1() {
//Define Panel_1
this.setBounds(UI.offSet, UI.offSet, UI.p1W, UI.p1H);
this.setBackground(UI.col2);
this.setLayout(new GroupLayout(this));
JLabel title = new JLabel("Please select Project Folder:");
title.setBounds(20, 10, 300, 15);
title.setForeground(UI.colText);
this.add(title);
//Text field to display project file location
UI.field1 = new JTextField();
UI.field1.setBounds(20, 35, 570, 20);
UI.field1.setBackground(UI.colTF);
UI.field1.setForeground(UI.colText);
UI.field1.setText(UI.sDefPath);
UI.field1.setCaretColor(UI.colText);
UI.field1.setBorder(BorderFactory.createLoweredBevelBorder());
UI.field1.addActionListener(this);
this.add(UI.field1);
//Browse for project file
UI.butBrowse1 = new JButton("Browse");
UI.butBrowse1.setBounds(600, 35, 80, 20);
UI.butBrowse1.setBackground(UI.colBut);
UI.butBrowse1.setForeground(UI.colBT);
UI.butBrowse1.setBorder(BorderFactory.createEmptyBorder());
UI.butBrowse1.setFocusable(false);
UI.butBrowse1.addActionListener(this);
this.add(UI.butBrowse1);
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
if (e.getSource()==UI.butBrowse1) {
//Choose project file location
JFileChooser fileChoose1 = new JFileChooser(UI.sDefPath);
fileChoose1.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
Action details = fileChoose1.getActionMap().get("viewTypeDetails");
details.actionPerformed(null);
fileChoose1.setPreferredSize(new Dimension(500, 600));
fileChoose1.showOpenDialog(null);
UI.sPath = fileChoose1.getSelectedFile().getAbsolutePath();
//Assign file paths from selected folder
UI.field1.setText(UI.sPath);
UI.sPIPath = UI.sPath + UI.sPIadd;
UI.sPDFPath = UI.sPath + UI.sPDFadd;
UI.sSubPath = UI.sPath + UI.sSubadd;
//Test assigned file paths
System.out.println(UI.sPIPath);
System.out.println(UI.sPDFPath);
System.out.println(UI.sSubPath);
//Assigning data from xlsx
//These are the variable that I need to display in Panel_4
UI.JobNo = XLSX_Reader.XLSX_Reader(UI.sPIPath, 0, 1);
UI.JobName = XLSX_Reader.XLSX_Reader(UI.sPIPath, 1, 1) + "\n" + XLSX_Reader.XLSX_Reader(UI.sPIPath, 2, 1);
UI.Contractor = XLSX_Reader.XLSX_Reader(UI.sPIPath, 4, 1);
UI.Attention = XLSX_Reader.XLSX_Reader(UI.sPIPath, 8, 1);
//testing xlsx reader
System.out.println(UI.JobNo);
System.out.println(UI.JobName);
System.out.println(UI.Contractor);
System.out.println(UI.Attention);
}
}
Panel_4 class
public class Panel_4 extends JPanel implements ActionListener{
Panel_4() {
//Define Panel_4
this.setBounds((UI.offSet * 2) + UI.p1W, UI.offSet, UI.p4W, UI.p4H);
this.setBackground(UI.col2);
this.setLayout(new GroupLayout(this));
//Doesn't update when Panel_1 assigns value to UI.Contractor
JLabel to = new JLabel("To: " + UI.Contractor);
to.setBounds(50, 190, 200, 30);
to.setForeground(UI.colText);
this.add(to);
//Doesn't update when Panel_1 assigns value to UI.JobNo
JLabel Job = new JLabel("Job No. " + UI.JobNo);
Job.setBounds(500, 110, 100, 15);
Job.setForeground(UI.colText);
this.add(Job);
//Doesn't update when Panel_1 assigns value to UI.Attention
JLabel Att = new JLabel("Attention " + UI.Attention);
Att.setBounds(350, 150, 100, 15);
Att.setForeground(UI.colText);
this.add(Att);
//Doesn't update when Panel_1 assigns value to UI.JobName
JLabel Re = new JLabel("Re: " + UI.JobName);
Re.setBounds(350, 190, 100, 15);
Re.setForeground(UI.colText);
this.add(Re);
If they were in the same class, I would normally do something like:
String pName = "Sam";
JLabel name = new JLabel(pName);
name.setBounds(...);
this.add(name);
JButton button = new JButton();
button.addActionListener(this);
this.add(button);
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource()==UI.butBrowse1) {
pName = "Dave";
name.setText(pName);
But I don’t know if that’s possible across classes.
First, you want to decouple your code. There should be no direct relationship between your “input” UI and your “output” UI. This will make it much easier to re-use and maintain the code.
To this end you should be making use of the “model-view-controller” concept. That is, the model provides the data and the views present the data in some meaningful way.
Add in the “observer pattern” and objects can monitor the model and take action when some state changes.
For example…
public interface Model {
public interface Observer {
public void modelDidChange(Model model);
}
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public LocalDateTime getDate();
}
This is a very simple demonstration, but, the Model
is a container for a LocalDateTime
value. It also provides “observability” through the use of the Model.Observer
so interested parties can be informed when the model changes.
At some point, we’re probably going to want to change the value of the model, in this case, we can make use of a specialised model…
public interface MutableModel extends Model {
public void setDate(LocalDateTime date);
}
This basically describes a model whose value can be changed.
Why is this important? This isolates responsibility, not all consumers of the model will want to, or should be allowed to, modify the state of the model, here we support the “separation of concerns/responsibility” principle.
I also, personally, like interface
s, as they provide a description of the contract without exposing the implementation, making them incredibly flexible.
Next we need an implementation of the models. How you do it probably isn’t important, but I started with an AbstractModel
…
public abstract class AbstractModel implements Model {
private List<Observer> observers = new ArrayList<>();
protected List<Observer> getObservers() {
return observers;
}
@Override
public void addObserver(Observer observer) {
getObservers().add(observer);
}
@Override
public void removeObserver(Observer observer) {
getObservers().remove(observer);
}
protected void fireModelDidChange() {
for (Observer observer : getObservers()) {
observer.modelDidChange(this);
}
}
}
This implements the core “boiler plate” functionality that all the other models will need. Note, I did not implement the getDate
method, this is deliberate, as how this value is managed should be down to the actual implementation.
Next, I implemented a concept of a “mutable” model…
public class DefaultMutableModel extends AbstractModel implements MutableModel {
private LocalDateTime date;
@Override
public LocalDateTime getDate() {
return date;
}
public void setDate(LocalDateTime date) {
this.date = date;
fireModelDidChange();
}
}
which I think is otherwise self-explanatory.
That’s all nice and good, but how do we actually use it?
Well, basically, we could create an instance of the model and pass it the various objects who need it, this is a concept of “dependency injection”, for example…
ActionPane
public class ActionPane extends JPanel {
private MutableModel model;
public ActionPane(MutableModel model) {
this.model = model;
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(32, 32, 32, 32));
JButton btn = new JButton("Tick");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getModel().setDate(LocalDateTime.now());
}
});
add(btn);
}
public MutableModel getModel() {
return model;
}
}
OutputPane
public class OutputPane extends JPanel {
private Model model;
private JLabel label;
public OutputPane(Model model) {
this.model = model;
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(32, 32, 32, 32));
add(getLabel());
model.addObserver(new Model.Observer() {
@Override
public void modelDidChange(Model model) {
label.setText(DateTimeFormatter.ISO_LOCAL_TIME.format(model.getDate()));
}
});
}
protected JLabel getLabel() {
if (label != null) {
return label;
}
label = new JLabel("------------");
return label;
}
public Model getModel() {
return model;
}
}
The two panels are seperate and have no idea of each other, yet, via the model, the can communicate.
Now, we simple create a model and the UI…
MutableModel model = new DefaultMutableModel();
add(new ActionPane(model));
add(new OutputPane(model));
And that’s it. When the model’s state is changed, the UI will be updated.
Runnable example…
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MainPane extends JPanel {
public MainPane() {
MutableModel model = new DefaultMutableModel();
setLayout(new GridLayout(1, 2));
add(new ActionPane(model));
add(new OutputPane(model));
}
}
public interface Model {
public interface Observer {
public void modelDidChange(Model model);
}
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public LocalDateTime getDate();
}
public interface MutableModel extends Model {
public void setDate(LocalDateTime date);
}
public abstract class AbstractModel implements Model {
private List<Observer> observers = new ArrayList<>();
protected List<Observer> getObservers() {
return observers;
}
@Override
public void addObserver(Observer observer) {
getObservers().add(observer);
}
@Override
public void removeObserver(Observer observer) {
getObservers().remove(observer);
}
protected void fireModelDidChange() {
for (Observer observer : getObservers()) {
observer.modelDidChange(this);
}
}
}
public class DefaultMutableModel extends AbstractModel implements MutableModel {
private LocalDateTime date;
@Override
public LocalDateTime getDate() {
return date;
}
public void setDate(LocalDateTime date) {
this.date = date;
fireModelDidChange();
}
}
public class ActionPane extends JPanel {
private MutableModel model;
public ActionPane(MutableModel model) {
this.model = model;
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(32, 32, 32, 32));
JButton btn = new JButton("Tick");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getModel().setDate(LocalDateTime.now());
}
});
add(btn);
}
public MutableModel getModel() {
return model;
}
}
public class OutputPane extends JPanel {
private Model model;
private JLabel label;
public OutputPane(Model model) {
this.model = model;
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(32, 32, 32, 32));
add(getLabel());
model.addObserver(new Model.Observer() {
@Override
public void modelDidChange(Model model) {
label.setText(DateTimeFormatter.ISO_LOCAL_TIME.format(model.getDate()));
}
});
}
protected JLabel getLabel() {
if (label != null) {
return label;
}
label = new JLabel("------------");
return label;
}
public Model getModel() {
return model;
}
}
}
You should note, that no where did I need to call revalidate
, invalidate
or repaint
in order for the label to update. Apart from setting the initial value to a “reasonably” long value, labels are (generally) self updating.
I’ve mentioned a lot of “concepts” and “terms”, I would strongly recommend that you take the time to research them further, as they will invaluable to you in solving your future issues.
“… I’ve organized the program into 4 JPanels, each in it’s own class. i.e. Panel_1, Panel_2, etc. I have a button in panel_1 that allows the user to select the project info file and that works just fine. …”
You can implement the JPanel sub-classes as nested inner-classes to UI.
It will not only centralize the code, the enclosing scope might provide the context needed to swap data between classes.
Here I am using an abstract class to leverage a Panel array.
class UI extends JFrame implements ActionListener {
static Panel[] panels = { new Panel_1(), new Panel_2() };
@Override
public void actionPerformed(ActionEvent e) {
}
abstract static class Panel extends JPanel implements ActionListener { }
static class Panel_1 extends Panel {
@Override
public void actionPerformed(ActionEvent e) {
}
}
static class Panel_2 extends Panel {
@Override
public void actionPerformed(ActionEvent e) {
}
}
}
“… what I want to do is have that button apply the info from the project to the JLabels in the other JPanels 2, 3, & 4. …”
You’ll want to override the JComponent#paintComponents method for the panels you wish to update data within.
static class Panel_2 extends Panel {
JLabel label = new JLabel();
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
label.setText(string);
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
Then, invalidate the panel, and invoke the repaint method.
static class Panel_1 extends Panel {
JButton button = new JButton();
@Override
public void actionPerformed(ActionEvent e) {
string = String.valueOf(System.currentTimeMillis());
panels[1].invalidate();
panels[1].repaint();
}
}
Here is the complete code.
class UI extends JFrame implements ActionListener {
static Panel[] panels = { new Panel_1(), new Panel_2() };
static String string;
UI() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setPreferredSize(new Dimension(1000, 1000));
setLayout(new GridLayout(panels.length, 1));
for (Panel panel : panels) add(panel);
pack();
setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
}
abstract static class Panel extends JPanel implements ActionListener { }
static class Panel_1 extends Panel {
JButton button = new JButton();
{
button.setPreferredSize(new Dimension(300, 100));
button.addActionListener(this);
add(button);
}
@Override
public void actionPerformed(ActionEvent e) {
string = String.valueOf(System.currentTimeMillis());
panels[1].invalidate();
panels[1].repaint();
}
}
static class Panel_2 extends Panel {
JLabel label = new JLabel();
{
label.setPreferredSize(new Dimension(900, 100));
label.setHorizontalAlignment(JLabel.CENTER);
label.setFont(new Font(Font.SERIF, Font.PLAIN, 50));
this.add(label);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
label.setText(string);
}
@Override
public void actionPerformed(ActionEvent e) {
}
}
}
For a quality answer, it would help if you were to present your code as a Complete but Minimal Reproducible Example. Each class, and each method, should have enough code so that we can copy-and-paste the classes into an IDE, and have them compile and run without adding of otherwise repairing code.
Consider the concept of “model-view-controller”, where the model provides the information to the view and the view presents it in some way. Couple that with the “observer pattern” and you have a way to monitor changes to the model, so A could make changes to the model and B would monitor for those changes and then be able to update itself when it changes
to.setBounds(50, 190, 200, 30);
seems pointless since you’re using aGroupLayout
layout manager, although, to be honest, I wouldn’t useGroupLayout
, it’s not really meant as a developer codable solution, I’d use other layout managersOff-topic: Consider following Java naming conventions
For example