This discussion is archived
5 Replies Latest reply: Jan 20, 2013 3:54 PM by James_D RSS

A better way to do ChangeListeners?

mfortner Newbie
Currently Being Moderated
I find myself having to implement multiple change listeners in a single class, and I was wondering if there was a way to do this that wouldn't trigger compilation problems. For example, let's say you have a LineChart, and you want the chart to listen for changes to the data, but also changes to the user's smoothing algorithm selection. You might try declaring your class like this:
public class MyGraph implements DataChangeListener, SmoothingChangeListener {....
where you've declared DataChangeListener, and SmoothingChangeListener as
DataChangeListener extends ChangeListener<SomeDataStructure>{...}

SmoothingChangeListener extends ChangeListener<ISmoothable>{...}
The compiler will tell you immediately that MyGraph can only implement one ChangeListener.

In real life, I have classes that have to listen for multiple types of events, and both for the sake of semantic clarity and for the sake of feasibility, they need to implement those change listeners. I've used event buses in certain cases, but they tend to obscure what the class is actually listening to.

Is there a better way of doing this?
  • 1. Re: A better way to do ChangeListeners?
    James_D Guru
    Currently Being Moderated
    I far prefer to use anonymous inner classes, or at most named inner classes, rather than having the surrounding class implement the listener interface(s).

    So for example:
    public class MyGraph {
     ...
     XYChart someChart ;
     XYChart.Series<DataType> someDataSeries ;
     ...
     someDataSeries.dataProperty().addListener(new ChangeListener<...>() {
       public void changed(...) {
        ...
       }
     });
    
     someChart.animatedProperty().addListener(new ChangeListener<Boolean>() {
       public void changed(...) {
         ...
       }
     });
    
    }
    This often means you can reduce the scope of the references to your controls. It's also going to be much easier to replace the code with lambda expressions if and when you update to Java 8.
  • 2. Re: A better way to do ChangeListeners?
    mfortner Newbie
    Currently Being Moderated
    Hmm. Not sure how that would work. I typically register MyGraph with whatever classes are producing the events... usually in the controller code somewhere.
    dataSource.addListener(myGraph);
    smoothingProperty.addListener(myGraph);
  • 3. Re: A better way to do ChangeListeners?
    James_D Guru
    Currently Being Moderated
    Event handling code is, almost by definition, controller code. So it makes sense (to me at least) to have event handling code in the controller. You can put the code I showed previously
     someDataSeries.dataProperty().addListener(new ChangeListener<...>() {
       public void changed(...) {
        ...
       }
     });
     
     someChart.animatedProperty().addListener(new ChangeListener<Boolean>() {
       public void changed(...) {
         ...
       }
     });
    directly in your controller. (I suppose I thought "MyGraph" was your controller, though the name should have implied otherwise to me.)

    However, if you really want to have the event handlers defined elsewhere, you can still do so, while using anonymous inner classes (or lambdas when we all upgrade next September):
    public class MyGraph {
      private final ChangeListener<SomeDataType> dataListener = new ChangeListener<SomeDataType>() {
        @Override 
        public void changed(...) { ... }
      };
      private final ChangeListener<ISmoothable> smoothingListener = new ChangeListener<ISmoothable>() {
       @Override
       public void changed(...) { ... }
      };
      ...
      public ChangeListener<SomeDataType> dataListener() { return dataListener ; }
      public ChangeListener<ISmoothable> smoothingListener() { return smoothingListener ; }
      ...
    }
    and you can then do
    dataSource.addListener(myGraph.dataListener());
    smoothingProperty.addListener(myGraph.smoothingListener());
    But I recommend the approach I first suggested: I use this all the time.
  • 4. Re: A better way to do ChangeListeners?
    mfortner Newbie
    Currently Being Moderated
    Ahh. In my code I tend to have separate controller classes. I use a fair amount of FXML, and this tends to reinforce that coding style. For me a class like MyGraph simply reacts to data changes, or configuration changes. And the controller glues the graph, and a smoothing configuration panel together. MyGraph, the DataSource and the Smoothing configuration don't really know that much about each other and tend to act as arms-length collaborators.
  • 5. Re: A better way to do ChangeListeners?
    James_D Guru
    Currently Being Moderated
    I think using anonymous inner classes would be perfect in that scenario, and will get around the problem of not being able to define one class to implement a listener interface twice. You can just define them exactly where you need (i.e. in the appropriate controller). As you have it, you actually have more coupling because the controller has to know about the class that implements the listener interface(s) (MyGraph in the example you use).

    Incidentally, and you may know this: the problem arises because of the way Java implements generics, using type erasure. You could, I think, just define your class to implement ChangeListener<?>. Then in the (now rather general) changed(...) method, you could check to see which observable had changed and use some downcasts as appropriate. This is similar to the style that was first advocated when the AWT event handling model changed from Java 1.0 to Java 1.1, but the trend towards using anonymous inner classes defined at the point of use started to take over pretty quickly after that.

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points