JRBuilder: Hello World Demo 2
Home «  Prev    Contents    Next  » Download
JRBuilder: Hello World Demo 2
A beefed-up Hello World application. Binding with Bob and Sue, and Wombats.
This example builds on the previous Hello World example, and introduces binding.

Binding enables you to link together, or bind, data in different objects, so their values remain synchronized. Binding in JRBuilder is supplied by the Bindery, defined in a separate module (included in the download).

The Bindery currently supports two binding strategies, intercept and listen (aka observe). (A third, poll, is in the works.) Both strategies are employed in this example.

The intercept strategy is used with plain old Ruby objects (POROs?). In it, the setter for the desired property is aliased and replaced with one that relays all changes to the Bindery, where other participants in the binding (known as correspondents in the Bindery module) are updated to reflect the change.

The listen strategy is used with Java objects proxied by JRuby (though it could conceivably be adapted to other event models). In it, an event listener is registered with an object, and notifies the Bindery of any changes when it receives an event. Again, the Bindery propagates the change to the other correspondents in the binding. As with event handlers (see previous example), any event method defined in a xxxListener interface may be specified.

It is also possible to use the intercept strategy with proxied Java objects, but the results will be unreliable in many (if not most) cases. That's because the setter methods of the JavaProxy will be intercepted, not those of the Java object itself. If a setter will be called only by other JRuby objects, that's fine; but if another Java object (or possibly the object itself, or a subcomponent) calls the setter, that event will not be intercepted, and the binding will effectively be broken.

So it is better to look for a xxxListener interface that will fire an event when a property of interest changes. The java.beans.PropertyChangeListener, implemented all the way down to java.awt.Component, and used in this and other examples, covers many such properties. (A future version of Bindery may be able to intercept Java methods using AOP-style bytecode weaving, perhaps with a little help from a future version of JRuby :). I've wondered if that might be the approach being taken by the JSR 295 folks, but I have been unable to find a detailed specification for that project.)

Ok, looks like I've typed my way down to the interesting part of the example. In JRBuilder/Bindery, n number of objects (where n > 1) may be bound in a single binding. (A given object may also be used in multiple bindings.) In this case, we're binding three objects: two POROs and a Java object. The property method name, (or prop for short), specifies an intercept binding strategy. Any other method name specifies a listen binding strategy, and is an event-method name in a xxxListener interface, in this case PropertyChangeListener.

The optional predicate, used only for listen-type correspondents, is a Boolean expression that determines whether an event will be processed; if it evaluates to false or nil, the event will be ignored. This is a necessity for property_change, which receives events for many different properties.

A predicate may accept a second parameter, attributes, in addition to event, where the attributes are specified with the correspondent, or inherited from one or more prototypes, or both. Prototypes and attributes are discussed in a later example.

You may have noticed that bob, sue, and hello are initially declared as local variables, and later copied to instance variables. That's because Bindery methods can't see the instance variables and methods inside the JRBuilder::Context class. An earlier, unpublished version of Bindery, derived from JRBuilder::Context, could do so, but I chose to (temporarily) sacrifice that feature for the greater good of allowing multiple JRBuilder contexts, and other applications, to share a common Bindery. I plan to reinstate this feature, and tighten up JRBuilder/Bindery integration in general, in the next version.

This example has demonstrated Ruby-to-Ruby and Ruby-to-Java binding. The next example demonstrates Java-to-Java binding, to silly excess.

require 'jrbuilder'

ctx = JRBuilder.new_swing_context
ctx.enter {
  attr_reader :my_frame
  
  bgcolor = color(240,238,220)
  
  class MyClass
    attr_reader :name,:value
    def initialize(name)
      @name = name    
    end
    def value=(val)
      @value = val
      puts "#{@name} set value = #{@value}"    
    end
  end

  bob = MyClass.new('Bob')
  sue = MyClass.new('Sue')
  hello = nil # label
  @running = false # flag for demo
  @exiting = false # flag for demo
 
  @my_frame = frame('Hello World Demo 2') { size 320, 240
    on_window_closing { @running = false; @exiting = true; my_frame.dispose }
    content_pane { layout :box,_parent,:Y_AXIS; background bgcolor;
      border :bevel, :RAISED }
    menu_bar { background bgcolor
      menu('File') {  mnemonic :VK_F; background :WHITE
        menu_item('Exit') { mnemonic :VK_X; background :WHITE
          on_click { @running = false; @exiting = true; my_frame.dispose }
        }
      }
      menu('Run') { mnemonic :VK_R; background :WHITE
        @start = menu_item('Start') { mnemonic :VK_S; background :WHITE
          enabled(!@running && !@exiting)
          on_click {  @running = true unless @exiting  }
        }
        @stop = menu_item('Stop') { mnemonic :VK_P; background :WHITE
          enabled(@running && !@exiting)
          on_click { @running = false } 
        }
      }
    }
    y_glue
    panel {layout :box,_parent,:X_AXIS; background :WHITE 
      hello = label('Hello, World!') { align :CENTER
        foreground :BLUE;  border :bevel, :RAISED;
        font('Dialog',:BOLD,40)
      }
    }
    y_strut 20
    button('Reverse!') { align :CENTER
      on_click { hello.text = hello.text.reverse }          
    }
    y_glue
  }
  # ok, let's hook some things together
  bind {
    property_change(hello,'text') {
      predicate { |event| event.property_name == 'text' }    
    }
    property(bob,'value')
    property(sue,'value')
  }
  @bob = bob
  @sue = sue
  @hello = hello

  def run_demo
    @running = true
    @start.enabled = false
    @stop.enabled = true
    while !@exiting
      sleep 2
      @stop.enabled = @running
      @start.enabled = !@running
      next unless @running
      @bob.value = 'Wombat!'
      sleep 2
      next unless @running
      @sue.value = 'Platypus!'
      sleep 2
      next unless @running
      @hello.text = 'Hobbit!'
      sleep 2
      next unless @running
      @sue.value = 'Wow!'
      sleep 2
      next unless @running
      @hello.text = 'Hello, World!'
    end
  end
}

ctx.my_frame.show
ctx.run_demo
Home «  Prev    Contents    Next  » Download