3

I have multiple JavaFX panes and canvases that reference a complex object with data they need, and I want them to redraw when the object changes.

This would call for the object to be Observable, but which class do I use? JavaFX seems to mostly have ObservableValue subclasses, which wrap a value and allow swapping it out. I don't want to swap out the complex object, just notify the listeners when changes occur. I could do that by implementing addListener, but I'm sure there's a subclass that does it for me already.

class ComplexObject /* extends SomeObservableClass */ {
    public int getValue1 { complex calculations... };
    public int getValue2 { ... };

    public void setNewValue1(int newValue) { ... }
}

class ComplexRenderer extends Canvas implements InvalidationListener {
    private ComplexObject complexObject;

    public void setComplexObject(ComplexObject complexObject) {
        this.complexObject = complexObject;
        complexObject.addListener(this);
    }

    public void draw() { ... }
}

Which class should ComplexObject extend? Is there something that maintains the list of listeners and has something like fireValueChangedEvent() so I can make it notify all listeners?

Everything I see in JavaFX seems to be geared towards properties, which don't seem the right choice here.

EboMike
  • 76,846
  • 14
  • 164
  • 167
  • Not really sure what you mean by "cannot swap." I'm not sure if you mean swapping the whole `ComplexObject` or maybe some values inside it. Do you need to do the calculations for `getValue()` only when it is called? Or can it be calculated and stored before that is called? – Jai Feb 07 '17 at 10:12
  • "Cannot swap" means that I want to keep the same `ComplexObject`, just change the values inside it. It seems to me that `ObjectPropertyBase` and its friends are designed to be wrappers, so you replace the entire object via `set()`, which will then fire the invalidation listeners. I _could_ store the complex calculations and expose the results as individual properties, but the original intent was to just have the complex object and do computations on demand. – EboMike Feb 07 '17 at 19:16
  • That means my answer should be close to what you are looking for. You don't track the change in `ComplexObject` reference. Instead, you track the change of the references of its internal values (primitives are to be boxed) and objects. If you need to track individual property, you would need to add individual listener to them. Otherwise you can expose a `BooleanProperty` that you can listen for any changes in any of the properties. Of course, internally, you would need to bind those properties to the `BooleanProperty`. – Jai Feb 08 '17 at 00:52

2 Answers2

5

Not really sure what you meant by swapping, and not really sure if I understood you right.

class ComplexObject {
    private IntegerProperty value1 = new SimpleIntegerProperty();
    private IntegerProperty value2 = new SimpleIntegerProperty();
    private BooleanProperty internalChanged = new SimpleBooleanProperty(false);

    public ComplexObject() {
        this.internalChanged.bind(Bindings.createBooleanBinding(() ->
            this.internalChanged.set(!this.internalChanged.get()), this.value1, this.value2));
    }

    public IntegerProperty value1Property() { return this.value1; }
    public int getValue1() { return this.value1.get(); }
    public void setValue1(int value) { return this.value1.set(value); }

    public IntegerProperty value2Property() { return this.value2; }
    public int getValue2() { return this.value2.get(); }
    public void setValue2(int value) { return this.value2.set(value); }

    public void setNewValue1(int newValue) { /* What value is this??? */ }

    public BooleanProperty internalChangedProperty() { return this.internalChanged; }
}

class ComplexRenderer extends Canvas implements InvalidationListener {
    private ComplexObject complexObject;

    public void setComplexObject(ComplexObject complexObject) {
        this.complexObject = complexObject;
        complexObject.internalChangedProperty().addListener(this);
    }
    @Override public void invalidated(Observable observable) {
        // Something inside complex object changed
    }

    public void draw() { ... }
}
Jai
  • 8,165
  • 2
  • 21
  • 52
  • Thanks! I guess in a simplified form, I'll use the bool to alert listeners and use regular ints and accessors for the actual values. Questions: 1. Does this mean I need to toggle the bool each time there's a change? IIRC listeners only trigger when the data actually changes. 2. This seems a bit like hacking the system to make it do what I want. Is this a generally common way to handle invalidation listeners? – EboMike Feb 08 '17 at 05:57
  • 1
    @EboMike Well, technically you can implement everything without `ObservableValue` library. In that case, you need to manually write your own getters/setters, and make setters change a boolean field to true. If you're following my answer, the binding (`this.internalChanged.bind(Bindings.createBooleanBinding(() -> this.internalChanged.set(true), this.value1, this.value2));`) should handle it for you. Of course, you need to add all properties into this. Internally, you can imagine as `Bindings.createBooleanBinding()` subscribing to changelistener of all of the properties. – Jai Feb 08 '17 at 06:06
  • I did it, and it worked! (Well, I'm toggling the boolean instead of just setting it to true, but same thing). I still feel like I'm completely abusing the system, but it works, so I don't know just how much I should care. Thank you! – EboMike Feb 08 '17 at 07:48
0

Maybe you can have a look at the Interface ObjectPropertyBase<T> and the classes ObjectPropertyBase<T> and SimpleObjectProperty<T> which implements Observable.

However you have to define when your object changes and listening logic.

I'm sorry it's just a trace of work, but I hope it may be useful.

Massimo Petrus
  • 1,881
  • 2
  • 13
  • 26
  • Thanks! Yes, I looked at those. However, these also implement `ObservableValue`, and it falls in line with the ones I mentioned in my question - they wrap an object, so my understanding is that you're expected to replace the object itself inside the wrapper to indicate that something changed. – EboMike Feb 07 '17 at 07:34