Make JButton update JLabel text in other class in Java

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.

  • 1

    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.

    – 

  • 3

    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

    – 

  • 2

    to.setBounds(50, 190, 200, 30); seems pointless since you’re using a GroupLayout layout manager, although, to be honest, I wouldn’t use GroupLayout, it’s not really meant as a developer codable solution, I’d use other layout managers

    – 

  • Off-topic: Consider following Java naming conventions

    – 

  • 1

    For example

    – 




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 interfaces, 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…

enter image description here

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) {

        }
    }
}

Leave a Comment