Java Swing — Writing a Ui that will repaint itself

2020-04-15 16:12发布

问题:

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 ?

回答1:

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


回答2:

I'm really lost (from your last three question),

may be, could we imagine that you have got three separated Models for one GUI, in this case doesn't important if you want to change number of elements in the GUI, or change properties for one JComponent,

output from PropertyChangeListener quite guarantee that output could be done on EDT

import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.*;

public class MVC_ProgressBarThread {

    private MVC_ProgressBarThread() {
        MVC_View view = new MVC_View();
        MVC_Model model = new MVC_Model();
        MVC_Control control = new MVC_Control(view, model);
        view.setControl(control);
        JFrame frame = new JFrame("MVC_ProgressBarThread");
        frame.getContentPane().add(view);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MVC_ProgressBarThread mVC_ProgressBarThread = new MVC_ProgressBarThread();
            }
        });
    }
}

class MVC_View extends JPanel {

    private static final long serialVersionUID = 1L;
    private MVC_Control control;
    private JProgressBar progressBar = new JProgressBar();
    private JButton startActionButton = new JButton("Press Me and Run this Madness");
    private JLabel myLabel = new JLabel("Nothing Special");

    public MVC_View() {
        startActionButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                buttonActionPerformed();
            }
        });
        JPanel buttonPanel = new JPanel();
        startActionButton.setFocusPainted(false);
        buttonPanel.add(startActionButton);
        setLayout(new BorderLayout(10, 10));
        add(buttonPanel, BorderLayout.NORTH);
        progressBar.setStringPainted(true);
        add(progressBar, BorderLayout.CENTER);
        myLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
        myLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        add(myLabel, BorderLayout.SOUTH);
    }

    public void setControl(MVC_Control control) {
        this.control = control;
    }

    private void buttonActionPerformed() {
        if (control != null) {
            control.doButtonAction();
        }
    }

    public void setProgress(int progress) {
        progressBar.setValue(progress);
    }

    public void setProgressLabel(String label) {
        progressBar.setString(label);
    }

    public void setIconLabel(Icon icon) {
        myLabel.setIcon(icon);
    }

    public void start() {
        startActionButton.setEnabled(false);
    }

    public void done() {
        startActionButton.setEnabled(true);
        setProgress(100);
        setProgressLabel("   Done !!!   ");
        setIconLabel(null);
    }
}

class MVC_Control {

    private MVC_View view;
    private MVC_Model model;

    public MVC_Control(final MVC_View view, final MVC_Model model) {
        this.view = view;
        this.model = model;
        model.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent pce) {
                if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
                    view.setProgress((Integer) pce.getNewValue());
                }
                if (MVC_Model.PROGRESS1.equals(pce.getPropertyName())) {
                    view.setProgressLabel((String) pce.getNewValue());
                }
                if (MVC_Model.PROGRESS2.equals(pce.getPropertyName())) {
                    view.setIconLabel((Icon) pce.getNewValue());
                }
            }
        });
    }

    public void doButtonAction() {
        view.start();
        SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {

            @Override
            protected Void doInBackground() throws Exception {
                model.reset();
                model.startSearch();
                return null;
            }

            @Override
            protected void done() {
                view.done();
            }
        };
        swingworker.execute();
    }
}

class MVC_Model {

    public static final String PROGRESS = "progress";
    public static final String PROGRESS1 = "progress1";
    public static final String PROGRESS2 = "progress2";
    private static final int MAX = 11;
    private static final long SLEEP_DELAY = 1000;
    private int progress = 0;
    private String label = "Start";
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private PropertyChangeSupport pcs1 = new PropertyChangeSupport(this);
    private PropertyChangeSupport pcs2 = new PropertyChangeSupport(this);
    private final String[] petStrings = {"Bird", "Cat", "Dog",
        "Rabbit", "Pig", "Fish", "Horse", "Cow", "Bee", "Skunk"};
    private int index = 1;
    private Queue<Icon> iconQueue = new LinkedList<Icon>();
    private Icon icon = (UIManager.getIcon("OptionPane.questionIcon"));

    public void setProgress(int progress) {
        int oldProgress = this.progress;
        this.progress = progress;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS,
                oldProgress, progress);
        pcs.firePropertyChange(evt);
    }

    public void setProgressLabel(String label) {
        String oldString = this.label;
        this.label = label;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS1,
                oldString, label);
        pcs1.firePropertyChange(evt);
    }

    public void setIconLabel(Icon icon) {
        Icon oldIcon = this.icon;
        this.icon = icon;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS2,
                oldIcon, icon);
        pcs2.firePropertyChange(evt);
    }

    public void reset() {
        setProgress(0);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
        pcs1.addPropertyChangeListener(listener);
        pcs2.addPropertyChangeListener(listener);
    }

    public void startSearch() {
        iconQueue.add(UIManager.getIcon("OptionPane.errorIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.informationIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.warningIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.questionIcon"));
        for (int i = 0; i < MAX; i++) {
            int newValue = (100 * i) / MAX;
            setProgress(newValue);
            setProgressLabel(petStrings[index]);
            index = (index + 1) % petStrings.length;
            setIconLabel(nextIcon());
            try {
                Thread.sleep(SLEEP_DELAY);
            } catch (InterruptedException e) {
            }
        }
    }

    private Icon nextIcon() {
        Icon icon1 = iconQueue.peek();
        iconQueue.add(iconQueue.remove());
        return icon1;
    }
}


回答3:

Please verify that the PropertyChangeEvent does not fire. If it does fire, but the repaint does not happen, it might help to postpone the repaint a bit like this:

//change this
button.repaint();

// to this
SwingUtilties.invokeLater(new Runnable() { public void run() { button.repaint(); }});

If the change event does not fire - disregard all after 'Please' ;)