JRBuilder: Binding Demo 4
Home «  Prev    Contents    Next  » Download
JRBuilder: Binding Demo 4
Even more on Binding: Translators and Converters. Unit and Type.
This example introduces translators, along with the special attributes unit and type. It also discusses converters, but does not demonstrate them.

In the binding examples up to this point, properties that have been bound in a given binding have had the same data type. In the case of numeric values, they've also had the same unit of measure: the value 17 on one slider corresponded to the value 17 on any other; no translation (or mapping) was required. But that's not always the case.

The Bindery lets you define converters and translators to connect properties that differ in their data types and in their nature. In Bindery terms, translators operate on what I've called unit (I'm open to a better term), translating, or mapping, between values that are dissimilar in some way. On the other hand, (again in Bindery terms), converters operate on values that are the same except for data type; for example, converting from Fixnum to Float, or from a Date object to the String representation of a Date.

This (admittedly contrived) example shows the use of translators to translate between JSlider units of 0-100 and java.awt.Color component units of 0-255. I'll ask you to forget for a few moments that you can simply set the minimum and maximum values returned by the slider.

You can see from the prototypes that each defines unit and type. Unit and type can be either Symbols or Classes. Translators match on unit; they must define from and to units, and they should define from and to types. Converters match on type; they must define from and to types.

One problem you may have noticed is that the two translators are exactly the same, except that their from and to units are swapped. You could give the sliders and the color panel the same unit; but they really are different things, and in an application with more things going on, that could cause problems (recall that unit and type are part of the matching criteria for prototypes). See the next example for my current solution.

require 'jrbuilder'

bindery = Bindery::Bindery.new
bindery.binding_context('my_swing_lib') {

  property_change(java.awt.Component) {
    unit           :shade 
    type           Fixnum
    property_name  'background'
    min_value      0
    max_value      255
    
    predicate { |event,from_attrs|
      event.property_name == from_attrs[:property_name].to_s
    }
    getter { |obj,attribs|
      color = obj.send(attribs[:property_name].to_sym)
      color.send(attribs[:component])    
    }
    setter { |obj,value,attribs|
      prop = attribs[:property_name]
      old_color = obj.send(prop.to_sym)
      red = old_color.red
      green = old_color.green
      blue = old_color.blue
      case attribs[:component]
        when :red   : red = value
        when :green : green = value
        when :blue  : blue = value      
      end
      obj.send((prop.to_s+'=').to_sym,java.awt.Color.new(red,green,blue))
    }
  }
  state_changed(javax.swing.JSlider,'value') {
    unit       :slider
    type       Fixnum
    min_value  0
    max_value  100
  }
  translator(:shade,:slider,Fixnum,Fixnum) { |value,from_attrs,to_attrs|
    from_min = from_attrs[:min_value]
    from_range = (from_attrs[:max_value] - from_min + 1).to_f
    to_min = to_attrs[:min_value]
    to_range = (to_attrs[:max_value] - to_min + 1).to_f
    (((value - from_min) * (to_range/from_range)) + to_min).round
  }
  translator(:slider,:shade,Fixnum,Fixnum) { |value,from_attrs,to_attrs|
    from_min = from_attrs[:min_value]
    from_range = (from_attrs[:max_value] - from_min + 1).to_f
    to_min = to_attrs[:min_value]
    to_range = (to_attrs[:max_value] - to_min + 1).to_f
    (((value - from_min) * (to_range/from_range)) + to_min).round
  }
}

ctx = JRBuilder.new_swing_context(bindery)
ctx.enter {
  attr_reader :my_frame

  red_slider_a = nil
  green_slider_a = nil
  blue_slider_a = nil

  red_slider_b = nil
  green_slider_b = nil
  blue_slider_b = nil
  
  color_box = nil
  
  @my_frame = frame("Binding Demo 4") { size 400,300
    border :empty, 10,10,10,10
    on_window_closing { my_frame.dispose }
    menu_bar { 
      menu("File") {  mnemonic :VK_F; background :WHITE
        menu_item("Exit") { mnemonic :VK_X; background :WHITE
          on_click { my_frame.dispose }
        }
    } }
    y_box {
      x_box {
        text_area('The sliders in groups A and B are not connected directly; '+
                  'all events flow through the color panel',2,10) {
          editable false; line_wrap true; wrap_style_word true; border :etched,:LOWERED
        }
      }
      y_strut 10
      x_box { border :compound, border(:bevel,:RAISED), border(:empty, 10,10,10,10)
        y_box {
          border(:titled, 'Slider Group A') { border :etched, :LOWERED }
          y_box { border :empty, 10,10,10,10
            red_slider_a = slider { background :RED; value 0 }
            y_strut 10
            green_slider_a = slider { background :GREEN; value 0 }
            y_strut 10
            blue_slider_a = slider { background :BLUE; value 0 }
          }
        }
        color_box = panel {
          border :bevel, :LOWERED
          fixed_size 100,100
          background :BLACK
        }
        y_box {
          border(:titled, 'Slider Group B') { border :etched, :LOWERED }
          y_box { border :empty, 10,10,10,10
            red_slider_b = slider { background :RED; value 0 }
            y_strut 10
            green_slider_b = slider { background :GREEN; value 0 }
            y_strut 10
            blue_slider_b = slider { background :BLUE; value 0 }
          }
        }
      }
      y_strut 35
    }
  }
  # new binding context, inherits from our swing lib
  binding_context('my_swing_lib','my_app_ctx') {
    # each slider is bound to the color box. notice that none
    # of the sliders are bound together -- all the events
    # flow through the color box.
    bind('red_a') {
      proto(red_slider_a)
      proto(color_box) { unit :shade; component :red }
    }
    bind('green_a') {
      proto(green_slider_a)
      proto(color_box) { unit :shade; component :green }
    }
    bind('blue_a') {
      proto(blue_slider_a)
      proto(color_box) { unit :shade; component :blue }
    }
    bind('red_b') {
      proto(red_slider_b)
      proto(color_box) { unit :shade; component :red }
    }
    bind('green_b') {
      proto(green_slider_b)
      proto(color_box) { unit :shade; component :green }
    }
    bind('blue_b') {
      proto(blue_slider_b)
      proto(color_box) { unit :shade; component :blue }
    }
  }
}

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