问题
I am trying to fire events from inner class, but it is not working. This is my code:
ABSTRACT MODEL:
public abstract class AbstractModel {
public PropertyChangeSupport propertyChangeSupport;
public AbstractModel() {
propertyChangeSupport = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
protected void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
propertyChangeSupport.firePropertyChange(propertyName, oldValue,
newValue);
}
}
MODEL:
public class GUImodel extends AbstractModel {
// Variables
private final ArrayList tempResultsTable = new ArrayList();
private static boolean done;
//
// RUN PROGRAM
//
public ArrayList run(ArrayList iF) {
try {
final BackgroundThread myThread = new BackgroundThread();
myThread.init(iF);
myThread.execute();
} catch (Exception e) {
e.printStackTrace();
}
return tempResultsTable;
}
public void done() {
System.out.println("done() called");
boolean oldValue = done;
done = true;
firePropertyChange(GUIcontroller.DONE_PROPERTY, oldValue, done);
}
class BackgroundThread extends SwingWorker<Void, Void> {
private ArrayList inputsFilesDataList;
public void init(ArrayList iF) {
inputsFilesDataList = iF;
done = false;
}
@Override
public Void doInBackground() throws Exception {
for (int i = 0; i < inputsFilesDataList.size(); i++) {
System.out.println(i);
}
return null;
}
@Override
protected void done() {
try {
boolean oldValue = done;
done = true;
firePropertyChange(GUIcontroller.DONE_PROPERTY, oldValue, done);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
VIEW:
public class GUIview{
...
public void propertyChange(final PropertyChangeEvent event) {
if (event.getPropertyName().equals(GUIcontroller.DONE_PROPERTY)) {
String newTab = (String)event.getNewValue();
updateTab(newTab);
}
}
...
}
ABSTRACT CONTROLLER:
public abstract class AbstractController implements PropertyChangeListener {
public final ArrayList<AbstractFrame> registeredViews;
public final ArrayList<AbstractModel> registeredModels;
public AbstractController() {
registeredViews = new ArrayList();
registeredModels = new ArrayList();
}
public void addModel(AbstractModel model) {
registeredModels.add(model);
model.addPropertyChangeListener(this);
}
public void removeModel(AbstractModel model) {
registeredModels.remove(model);
model.removePropertyChangeListener(this);
}
public void addView(GUIview view) {
registeredViews.add(view);
}
public void removeView(AbstractFrame view) {
registeredViews.remove(view);
}
@Override
public void propertyChange(PropertyChangeEvent event) {
for (AbstractFrame view : registeredViews) {
view.propertyChange(event);
}
}
}
CONTROLLER
public class GUIcontroller extends AbstractController {
public static final String DONE_PROPERTY = "done";
ArrayList inputsFilesList = m_model.loadFromExcel();
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals(GUIcontroller.DONE_PROPERTY)) {
m_view.getResultsModel().updateResultsTableDataList(
m_model.getTempResultsTable());
} else {
for (AbstractFrame view : registeredViews) {
view.propertyChange(event);
}
}
}
public runProgram(){
m_model.run(inputsFilesList);
}
}
MAIN.
public class GUImain {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
createGUI();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public static void createGUI() {
InputsModel inputsModel = new InputsModel();
ResultsModel resultsModel = new ResultsModel();
GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
controller.addModel(model);
GUIview view = new GUIview(controller, model, inputsModel, resultsModel);
controller.addControllerListerners();
view.setVisible(true);
}
}
Any idea about the problem?
I need to run some methods in a background thread, therefore I am using an inner class which extends SwingWorker. Once that thread has finished, I need to fire an event to report some changes to my controller.
The "firePropertyChange(...)" line within the done() method is not being executed.
Related question: If a certain class ClassA extends ClassAA, and its inner class ClassB extends ClassBB, is the inner class ClassB extending ClassAA as well?
回答1:
A silly wild a$$ guess:
Are you adding listeners to the correct SwingPropertyChangeSupport object? This must be the object held by the AbstractModel, not by the BackgroundThread. In other words, for your listeners to be able to receive notification that the property has changed, they must add their PropertyChangeListener to AbstractModel, and your BackgroundThread class must have a method for doing this.
(Edit)
Either that or get rid of PropertyChangeSupport from your AbstractModel class and only use the one held by SwingWorker as suggested by mKorbel. 1+ to his answer.
Otherwise and again, your question is woefully deficient in code/information to allow a truly knowledgeable answer in its current state, and all we can do is guess at possible problems and their answers. Please consider our point of view when asking questions here by asking yourself what information is needed for someone to be able to fully understand the problem and answer it.
Edit 2
Your code proves that my assumptions are in fact correct, that you are adding a PropertyChangeListener to the wrong PropertyChangeSupport object, so that the notification in the SwingWorker -- which never has a PropertyChangeListener assigned to its support -- will have no effect on the listeners which have been added to the AbstractModel's suport object.
This:
firePropertyChange(GUIcontroller.DONE_PROPERTY, oldValue, done);
is called on the SwingPropertyChangeSupport object of the SwingWorker, not on that of the AbstractModel.
One potential solution is to change your fire method to:
GUImodel.this.firePropertyChange(GUIcontroller.DONE_PROPERTY, oldValue, done);
So that the correct support object notifies the correct listeners.
Edit 3
My SSCCE (well a little longer than a true SSCCE) that proves my contention:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.event.SwingPropertyChangeSupport;
public class MvcSscce {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
createGUI();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public static void createGUI() {
GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
controller.addModel(model);
GUIview view = new GUIview(controller);
view.setVisible(true);
}
}
class GUIview {
private JPanel mainPanel = new JPanel();
private JFrame frame = new JFrame("Fubar");
public GUIview(AbstractController controller) {
mainPanel.add(new JButton(controller.getButtonAction()));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
}
public void setVisible(boolean visible) {
frame.setVisible(visible);
}
}
abstract class AbstractModel {
// note this should be a SwingPropertyChangeSupport
public SwingPropertyChangeSupport propertyChangeSupport;
public abstract void run();
public AbstractModel() {
propertyChangeSupport = new SwingPropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
protected void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
}
class GUImodel extends AbstractModel {
private boolean done = false;
public void run() {
done = false;
final BackgroundThread myThread = new BackgroundThread();
myThread.execute();
}
private class BackgroundThread extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 2000;
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(SLEEP_TIME);
return null;
}
@Override
protected void done() {
System.out.println("done() called");
boolean oldValue = done;
done = true;
// fire both property change listeners and see what gets notified
firePropertyChange(GUIcontroller.DONE_PROPERTY, oldValue, done);
GUImodel.this.firePropertyChange(GUIcontroller.DONE_PROPERTY_2,
oldValue, done);
}
}
}
class AbstractController implements PropertyChangeListener {
private AbstractModel model;
public void addModel(AbstractModel model) {
this.model = model;
model.addPropertyChangeListener(this);
}
public Action getButtonAction() {
@SuppressWarnings("serial")
Action buttonAction = new AbstractAction("Press Me") {
@Override
public void actionPerformed(ActionEvent arg0) {
model.run();
}
};
return buttonAction;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String output = String.format("Evt: %s, newValue: %s",
evt.getPropertyName(), evt.getNewValue());
System.out.println(output);
}
}
class GUIcontroller extends AbstractController {
public static final String DONE_PROPERTY_2 = "done property 2";
public static final String DONE_PROPERTY = "done property";
}
Note that after a 2 second delay, the listener is notified, but only of the DONE_PROPERTY_2 property, not the DONE_PROPERTY.
回答2:
add PropertyChangeListener to instance of SwingWorker, and there isn't another Swing Listener implemented for SwingWorker
SwingWorker
returns eventsDONE
,PENDING
,STARTED
fromPropertyChangeListener
from
public void propertyChange(PropertyChangeEvent event) {
you can to distribute proper notifier(s) to theModel
, toView
too becausedone()
,process()
,publish()
guarentee that output is done on Event Dispatch Thread
来源:https://stackoverflow.com/questions/17043681/firing-events-from-inner-class-which-extends-swingworker