Search code examples
javaswinguser-interfacerepaintpropertychangelistener

Java Swing -- Writing a Ui that will repaint itself based on changes to a custom data object


First things first -- I had posted a question earlier wherein i had asked for help as to why my code was not working and this question is acting upon the advice i got in that question.

My use case is this.

Im writing a MethodEditor module wherein every Method is denoted by a custom data object that has 2 data members --

  1. List of input variable names
  2. String(name of the result variable that this method sets).

These Method objects are generated as a result of filling out some data on a JDialog.
These Method objects are stored in a container that has 1 data member that is a List<Method> The container resides in a controller JPanel from where the aforementioned JDialog is invoked.

From a UI perspective I want to display every Method object in the form of a Jbutton on click of which a JDialog will open and allow the user to edit it.

The MethodEditor acts on a List and generated a vertical arrangement of JButtons, 1 for each Method in List<Method>. This List<Method> is passed to the MethodEditor from the aforementioned controller JPane.

I had in my earlier question implemented the MethodEditor as a JPanel that would add a PropertyChangeListener to the List<Method> and would repaint itself everytime there was a PropertyChange event but my approach did not work, the repaint would not happen.

Is there another way to get my use case implemented or is there any fix that i could do to my code posted in the earlier question ?


Solution

  • Your problem has been mentioned before as has its solution. You must actually add or remove the component to your observer panel from within the PropertyChangeListener. Calling revalidate() and repaint() will not magically add or remove components unless you explicitly do this before these methods are called.

    For example:

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Font;
    import java.awt.GridLayout;
    import java.awt.Point;
    import java.awt.event.ActionEvent;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.beans.IndexedPropertyChangeEvent;
    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import javax.swing.*;
    import javax.swing.event.SwingPropertyChangeSupport;
    
    public class ListenToTest {
       public static final String[] ITEMS = {"Sunday", "Monday", "Tuesday", "Wednesday", 
          "Thursday", "Friday", "Saturday"};
       private JPanel mainPanel = new JPanel();
       private ObserverPanel observerPanel = new ObserverPanel();
       private ListenToModel model = new ListenToModel();
    
       public ListenToTest() {
          observerPanel.setModel(model);
    
          for (String item : ITEMS) {
             model.addItem(item);
          }
    
          JPanel btnPanel = new JPanel();
          btnPanel.add(new JButton(new AddAction("Add")));
          btnPanel.add(new JButton(new RemoveAction("Remove")));
    
          mainPanel.setLayout(new BorderLayout());
          mainPanel.add(new JScrollPane(observerPanel.getMainComponent()));
          mainPanel.add(btnPanel, BorderLayout.PAGE_END);
       }
    
       public JComponent getMainComponent() {
          return mainPanel;
       }
    
       private class AddAction extends AbstractAction {
          public AddAction(String title) {
             super(title);
          }
    
          @Override
          public void actionPerformed(ActionEvent arg0) {
             String text = JOptionPane.showInputDialog(mainPanel, "Enter a String");
             if (text != null) {
                model.addItem(text);
             }
          }
       }
    
       private class RemoveAction extends AbstractAction {
          public RemoveAction(String title) {
             super(title);
          }
    
          @Override
          public void actionPerformed(ActionEvent arg0) {
             int index = observerPanel.getSelectedIndex();
             if (index >= 0) {
                model.removeItem(index);
             }
          }
       }
    
       private static void createAndShowGui() {
          ListenToTest mainPanel = new ListenToTest();
    
          JFrame frame = new JFrame("ListenToModelTest");
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(mainPanel.getMainComponent());
          frame.pack();
          frame.setLocationByPlatform(true);
          frame.setVisible(true);
       }
    
       public static void main(String[] args) {
          SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                createAndShowGui();
             }
          });
       }
    }
    
    class ObserverPanel {
       public static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 18);
       protected static final Color SELECTED_COLOR = new Color(150, 150, 255);
       private JPanel mainPanel = new JPanel();
       private ListenToModel model;
       private GridLayout gridLayout = new GridLayout(0, 1);
       private int selectedIndex = -1;
    
       public ObserverPanel() {
          mainPanel.setLayout(gridLayout);
          mainPanel.addMouseListener(new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
                Point p = e.getPoint();
                Component[] components = mainPanel.getComponents();
                for (int i = 0; i < components.length; i++) {
                   if (mainPanel.getComponentAt(p).equals(components[i])) {
                      selectedIndex = i;
                      components[i].setBackground(SELECTED_COLOR);
                   } else {
                      components[i].setBackground(null);
                   }
                }
             }
          });
       }
    
       public int getSelectedIndex() {
          return selectedIndex;
       }
    
       public void setModel(ListenToModel model) {
          this.model = model;
          model.addPropertyChangeListener(new ObserverPanelListener());
       }
    
       public JComponent getMainComponent() {
          return mainPanel;
       }
    
       private class ObserverPanelListener implements PropertyChangeListener {
          public void propertyChange(PropertyChangeEvent evt) {
             if (evt.getPropertyName().equals(ListenToModel.ADD)) {
                JLabel label = createLabel(evt);
                for (Component comp : mainPanel.getComponents()) {
                   comp.setBackground(null);
                }
                int index = ((IndexedPropertyChangeEvent)evt).getIndex();
                mainPanel.add(label, index);
                label.setBackground(SELECTED_COLOR);
                selectedIndex = index;
             } else if (evt.getPropertyName().equals(ListenToModel.REMOVE)) {
                int index = ((IndexedPropertyChangeEvent)evt).getIndex();
                mainPanel.remove(index);
                for (Component comp : mainPanel.getComponents()) {
                   comp.setBackground(null);
                }
                selectedIndex = -1;
             } else if (evt.getPropertyName().equals(ListenToModel.REMOVE_ALL)) {
                mainPanel.removeAll();
                selectedIndex = -1;
             }
             mainPanel.revalidate();
             mainPanel.repaint();
          }
    
          private JLabel createLabel(PropertyChangeEvent evt) {
             String newValue = evt.getNewValue().toString();
             JLabel label = new JLabel(newValue);
             label.setFont(LABEL_FONT);
             int eb = 20;
             label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.blue), 
                   BorderFactory.createEmptyBorder(eb , eb, eb, eb)));
             label.setFocusable(true);
             label.setOpaque(true);
             return label;
          }
       }
    }
    
    class ListenToModel implements Iterable<String> {
       public static final String ADD = "add";
       public static final String REMOVE = "remove";
       public static final String REMOVE_ALL = "remove all";
       private SwingPropertyChangeSupport spcSupport = new SwingPropertyChangeSupport(
             this);
       private List<String> modelNucleus = new ArrayList<String>();
    
       public void addItem(String item) {
          modelNucleus.add(item);
          spcSupport.fireIndexedPropertyChange(ADD, modelNucleus.size() - 1, null,
                item);
       }
    
       public void addItem(int index, String item) {
          if (index < 0 || index > modelNucleus.size()) {
             // TODO: throw an exception
          } else {
             modelNucleus.add(index, item);
             spcSupport.fireIndexedPropertyChange(REMOVE, index, null, item);
          }
    
       }
    
       public void removeItem(int index) {
          if (index < 0 || index >= modelNucleus.size()) {
             // TODO: throw an exception
          } else {
             String oldValue = modelNucleus.remove(index);
             spcSupport.fireIndexedPropertyChange(REMOVE, index, oldValue, null);
          }
       }
    
       public void removeAll() {
          modelNucleus.clear();
          spcSupport.firePropertyChange(REMOVE_ALL, null, null);
       }
    
       public void addPropertyChangeListener(PropertyChangeListener listener) {
          spcSupport.addPropertyChangeListener(listener);
       }
    
       public void removePropertyChangeListener(PropertyChangeListener listener) {
          spcSupport.removePropertyChangeListener(listener);
       }
    
       @Override
       public Iterator<String> iterator() {
          return modelNucleus.iterator();
       }
    }