1

It appears that there are two situations where assignment to a bound groovy property through @Bindable does not call the listeners:

(1) In the case where the property is assigned within the class itself, such as this.prop = newval, or, simply, prop = newval

(2) In the case where the property's value does not change obj.prop = oldval

Is there a way around this? Ideally it would support the simple (.)prop= syntax.

Code example:

import java.beans.*
import groovy.beans.*

int changes = 0
def obj = Binding.newInstance()
obj.propertyChange = { changes++ }
obj.prop = "1st change"   // change recorded
obj.twoChanges()          // no changes recorded
obj.prop = obj.prop       // no change recorded
assert changes == 4       // fails: changes is 1

class Binding {
  @Bindable String prop
  def twoChanges() {
    prop = "2nd change"
    this.prop = "3rd change"
  }
}
inyourcorner
  • 683
  • 9
  • 26

3 Answers3

2

By creating a hidden @Bindable property (mprop) and providing only a getter and setter for prop without an actual property named prop, I was able to use your syntax unchanged. This works for everything except where the property is set to the same value (3 out of 4 changes detected):

import java.beans.*
import groovy.beans.*

int changes = 0
def obj = Binding.newInstance()
obj.propertyChange = { changes++ }
obj.prop = "1st change"   // change recorded
obj.twoChanges()          // two changes recorded
obj.prop = obj.prop       // no change recorded
assert changes == 4       // fails: changes is 3


class Binding {
  @Bindable String mprop           // changed from prop to mprop
  def setProp(x) {setMprop(x)}     // new setter
  def getProp() {getMprop()}       // new getter
  def twoChanges() {
    prop = "2nd change"
    this.prop = "3rd change"
  }
}
greymatter
  • 840
  • 1
  • 10
  • 25
1

For (1), @Bindable is an AST which generates, among other stuff, custom setters, and when you access a property inside a class, it doesn't go through the setter. This works:

import java.beans.*
import groovy.beans.*

int changes = 0
def obj = Binding.newInstance()
obj.propertyChange = { changes++ }
obj.prop = "1st change"   // change recorded
obj.twoChanges()          // no changes recorded
obj.prop = obj.prop       // no change recorded
assert changes == 3       // fails: changes is 1

class Binding {
  @Bindable String prop
  def twoChanges() {
    setProp( "2nd change" )
    this.setProp( "3rd change"  )
  }
}

For (2), well, it seems to be the standard behavior of PropertyChangeListener, since the property wasn't changed. Maybe you could supply a custom object, with a custom equals, or just create a custom setter.

Community
  • 1
  • 1
Will
  • 14,348
  • 1
  • 42
  • 44
  • (1): I could use `[]`s within the class I guess, or just have a `set()` and force all set's to go through it. The issue there is that a lot of the code written wants to set the values directly, in the example. (2): that's an interesting idea. In this case the properties are primitives, and I think what's being suggested here is custom-class properties, unless I'm reading it wrong, which is possible. At this point I'm thinking about writing my own `@Editable` which would generate accessors around a hidden data property ($prop) maybe. Is the `@Bindable` code public? I couldn't seem to find it. – inyourcorner Apr 08 '14 at 17:39
  • I found the implementation right in the groovy-all jar. – inyourcorner Apr 08 '14 at 17:50
0

Here's how I ended up doing it, without needing to use @Bindable. I'm open to suggestions, especially this.with {}.

class Obj {
  int val = 0
  static int numCalls = 0
  List assigns = []

  void setProperty(String name, Object value) {
    this.@"$name" = value
    assigns << [name: value]
    numCalls ++
  }

  int nop() {
    this.with { 
      val = val 
    }
  }

  int inc() {
    this.with { 
      this.val += 1  
    }
    return this.val
  }

}

def obj = Obj.newInstance()
assert obj.val == 0 && Obj.numCalls == 0
assert obj.inc() == 1 && obj.val == 1 && Obj.numCalls == 1
assert obj.nop() == 1 && obj.val == 1 && Obj.numCalls == 2 && obj.assigns.size() == 2
inyourcorner
  • 683
  • 9
  • 26