Mercutio
Mercutio

Reputation: 1296

Triggering Java GUI to update

There's probably a better question/answer for this but what I've been finding hasn't worked out and I have had trouble phrasing the question for google query. Basically I have a JFrame with several panels and components that pull their data from xml files. I use the JFrame's instance variable private Date focusDate = new Date(); to store which day's info I'd like each panel to display, so far so good.

My problem comes now that I'm trying to set the various actions of the navigation components to update after I change 'focusDate'. I have a toolbar in a JPanel NavButtons navPanel = new NavButtons(focusDate); which I setup as an Inner Class and the console reports focusDate being changed but I can't get the JFrame to validate(), repaint(), etc... when I call my setFocus(Date d) method.

I can include more of my code if that would be helpful but here's the method in question:

public void setFocus(Date d) throws IOException {
    focusDate = d;

    dispose();
//  validate();  //Tried revalidate too, but DisplayView extends JFrame
//  repaint(); 
//  revalidate();
//  pack();
//  DisplayView view = new DisplayView(focusDate);
    setVisible(true); }

and here's how I'm setting the ActionListener in the constructor:

public NavButtons(Date d) {
    newDate = LocalDate.parse(new SimpleDateFormat("yyyy-MM-dd").format(d));

        weekBack.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) { 
                newDate = newDate.plusDays(-7); 
                try { setFocus(Date.from(newDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
                } catch (IOException e) {
                    e.printStackTrace(); } 
                //validate();
                //repaint(); 
                }
        });

I'm not very familiar with swing, so I'm sure this is some minor detail I'm just not getting but if someone can explain how to re-trigger the argument passing and update the child components of the frame to an amateur that would be most appreciated.

Update Here's the entire JFrame

package interfaceComponents;

import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.*;
import java.io.IOException;
import java.text.*;
import java.util.*;
import java.time.*;

public class DisplayView extends JFrame {
    //instance variables
    private Date focusDate = new Date();

    //constructor
    public DisplayView(Date d) throws IOException {
        DisplayMenus menus = new DisplayMenus();
        setJMenuBar(menus);

        JPanel body = new JPanel();
        body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS));
        body.add(new DayView(focusDate));
        LocalDate focusNextDay = LocalDate.now();
        body.add(new DayView(Date.from(focusNextDay.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()))); 
        add(new JScrollPane(body), BorderLayout.CENTER);

        JPanel footer = new JPanel();
        NavButtons navPanel = new NavButtons(focusDate);
        JLabel focusPoint = new JLabel(new SimpleDateFormat("E, dd MMM yyyy").format(focusDate).toString());
        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 DisplayView() throws IOException { this(new Date()); }

    public void setFocus(Date d) throws IOException { 
        focusDate = d;

        SwingUtilities.updateComponentTreeUI(this);
//      dispose();
//      invalidate();
//      validate();                                     //Tried revalidate too, but DisplayView extends JFrame
        repaint(); 
//      revalidate();
//      pack();
//      DisplayView view = new DisplayView(focusDate);
//      setVisible(true);
        }
    public Date getFocus() { return focusDate; }    

    class NavButtons extends JPanel {
        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 JButton calendar = new JButton("L");
        private LocalDate newDate = LocalDate.now();

        public NavButtons(Date d) {
            newDate = LocalDate.parse(new SimpleDateFormat("yyyy-MM-dd").format(d));

            weekBack.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) { 
                    newDate = newDate.plusDays(-7); 
                    try { setFocus(Date.from(newDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
                    } catch (IOException e) {
                        e.printStackTrace(); } 
//                  invalidate();
//                  validate();
//                  repaint(); 
                    }
            });
            dayBack.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) { newDate = newDate.plusDays(-1); }
            });
            returnToday.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) { newDate = LocalDate.now(); }
            });
            nextDay.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) { newDate = newDate.plusDays(1); }
            });
            nextWeek.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) { newDate = newDate.plusDays(7); }
            });

            toolBar.add(weekBack);
            toolBar.add(dayBack);
            toolBar.add(returnToday);
            toolBar.add(nextDay);
            toolBar.add(nextWeek);
            toolBar.add(new GalileoMode());
            toolBar.add(calendar);
            add(toolBar); }         
    }
}

As @MadProgrammer pointed out, I should have the date stored in a separate object (shown here):

package interfaceComponents;

import java.util.*;

public class FocusDate {
    //instance variables
    Date focus = new Date();

    //constructors
    //intentionally blank: public FocusDate() {}

    //methods:
    public void setFocus(Date d) {
        focus = d; }
    public Date getFocus() {
        return focus; }
}

and I edited the Frame as such:

public class DisplayView extends JFrame {
    //instance variables
    FocusDate focus = new FocusDate();
//  private Date focusDate = new Date();

    //constructor
    public DisplayView(Date d) throws IOException {
//      focusDate = d;
        Date focusDate = focus.getFocus();

...

    public void setFocus(Date d) throws IOException { 
//      focusDate = d;
        focus.setFocus(d);

Still not doing something right though...

Upvotes: 2

Views: 1613

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347204

So, the basic idea is to wrap the "current" date value in an observable pattern and pass this to each interested party. This allows the navigation to make changes to the date value, but not need to know about any other part of the program, decoupling it and allow for a more flexible outcome.

So, I started with two basic contracts...

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

}

One is non-mutable (for those parts of the programs that don't need to be able to change the date) and one is mutable, for those parts of the program that do (like the navigation)

Then I created a "default" implementation of the model...

public class DefaultDateModel extends Observable implements MutableDateModel {

    private LocalDate date;

    public DefaultDateModel(LocalDate date) {
        this.date = date;
    }

    @Override
    public void setDate(LocalDate date) {
        this.date = date;
        setChanged();
        notifyObservers();
    }

    @Override
    public LocalDate getDate() {
        return date;
    }

    @Override
    public void removeObserver(Observer o) {
        // I like the "remove" ;)
        deleteObserver(o);
    }

}

I'm been lazy and using the Observer and Observable API from java.util, you can create your own based on your needs, but this just suits the example.

What this means is, I can create an instance of DefaultDateModel and pass it to those parts of the program that want a DateModel and those that want a MutableDateModel without needing to create separate instances, neat.

Because we're working to interface contract, those parts of the program that only want a DateModel will only ever be able to access the methods defined within the interface (and if the cast it, then they are doing the wrong thing)...

As an example...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Observable;
import java.util.Observer;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class DisplayView extends JFrame {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                DisplayView view = new DisplayView();
                view.setDefaultCloseOperation(EXIT_ON_CLOSE);
                view.pack();
                view.setLocationRelativeTo(null);
                view.setVisible(true);
            }
        });
    }

    //constructor
    public DisplayView(LocalDate d) {
//      DisplayMenus menus = new DisplayMenus();
//      setJMenuBar(menus);

        DefaultDateModel model = new DefaultDateModel(d);

        JPanel body = new JPanel();
        body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS));

        DayView nowView = new DayView();
        nowView.setModel(model);
        DayView nextView = new DayView(1);
        nextView.setModel(model);

        body.add(nowView);
        body.add(nextView);
        add(new JScrollPane(body), BorderLayout.CENTER);

        JPanel footer = new JPanel();
        NavButtons navPanel = new NavButtons(model);
        JLabel focusPoint = new JLabel(DateTimeFormatter.ISO_DATE.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 DisplayView() {
        this(LocalDate.now());
    }

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

    }

    public class DefaultDateModel extends Observable implements MutableDateModel {

        private LocalDate date;

        public DefaultDateModel(LocalDate date) {
            this.date = date;
        }

        @Override
        public void setDate(LocalDate date) {
            this.date = date;
            setChanged();
            notifyObservers();
        }

        @Override
        public LocalDate getDate() {
            return date;
        }

        @Override
        public void removeObserver(Observer o) {
            // I like the "remove" ;)
            deleteObserver(o);
        }

    }

    public class DayView extends JPanel implements Observer {
        private JLabel dateLabel;
        private DateModel model;
        private int offset;

        public DayView(int offset) {
            this.offset = offset;
            dateLabel = new JLabel("...");
            setLayout(new GridBagLayout());
            add(dateLabel);
        }

        public DayView() {
            this(0);
        }

        public void setModel(DateModel value) {
            if (model != null) {
                model.removeObserver(this);
            }
            this.model = value;
            if (model != null) {
                model.addObserver(this);
            }
            updateLabel();
        }

        public DateModel getModel() {
            return model;
        }

        protected void updateLabel() {
            DateModel model = getModel();
            if (model != null) {
                LocalDate offsetDate = model.getDate().plusDays(offset);
                dateLabel.setText(DateTimeFormatter.ISO_DATE.format(offsetDate));
            } else {
                dateLabel.setText("...");
            }
        }

        @Override
        public void update(Observable o, Object arg) {
            updateLabel();
        }

    }

    class NavButtons extends JPanel implements Observer {

        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 JButton calendar = new JButton("L");

        private MutableDateModel model;

        public NavButtons(MutableDateModel model) {
            weekBack.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    MutableDateModel model = getModel();
                    if (model != null) {
                        LocalDate newDate = model.getDate().minusDays(7);
                        model.setDate(newDate);
                    }
                }
            });
            dayBack.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    MutableDateModel model = getModel();
                    if (model != null) {
                        LocalDate newDate = model.getDate().minusDays(1);
                        model.setDate(newDate);
                    }
                }
            });
            returnToday.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    MutableDateModel model = getModel();
                    if (model != null) {
                        LocalDate newDate = LocalDate.now();
                        model.setDate(newDate);
                    }
                }
            });
            nextDay.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    MutableDateModel model = getModel();
                    if (model != null) {
                        LocalDate newDate = model.getDate().plusDays(1);
                        model.setDate(newDate);
                    }
                }
            });
            nextWeek.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    MutableDateModel model = getModel();
                    if (model != null) {
                        LocalDate newDate = model.getDate().plusDays(7);
                        model.setDate(newDate);
                    }
                }
            });

            toolBar.add(weekBack);
            toolBar.add(dayBack);
            toolBar.add(returnToday);
            toolBar.add(nextDay);
            toolBar.add(nextWeek);
//          toolBar.add(new GalileoMode());
            toolBar.add(calendar);
            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 has change!!
        }

        protected void setFocus(LocalDate newDate) {
            // No idea what this is suppose to do...
        }

    }
}

Oh, I'd kind of avoid moving between java.util.Date and java.time.LocalDate if you can, for your purposes, I'd stick with LocalDate, but that's me ;)

Upvotes: 4

Related Questions