Hey yall<br><br>Seems IronRuby objects do not yet work with WPF
databinding. One approach to a workaround is to emit a wrapper type
that defines CLR properties. When WPF calls the getter/setter the call
is forwarded ot the contained contained ruby object. This will allow
defining business objects in ruby and databing them to the UI. <br>
<br>This might be useful to thers, so here is my approach. The first
part is C# code that defines a type given a list of property names. The
second part is ruby code that creates a wrapper instance for a given
ruby object and caches the type so that you dont generate a type for
each element in an array..<br>
<br>The C# code to generate a type:<br><br>using System;<br>using System.Collections.Generic;<br>using System.Linq;<br>using System.Text;<br>using System.Reflection;<br>using System.Reflection.Emit;<br><br>namespace GenerateType<br>
{<br><br> public class TypeGenerator<br> {<br> public delegate object GetPropertyDelegate(string propertyName);<br> public delegate object SetPropertyDelegate(string propertyName, object value);<br><br>
public static Type Generate(string className, List<string> properties)<br> {<br> AssemblyName asmName = new AssemblyName("BindingTypes");<br> AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.<div id=":11m" class="ArwC7c ckChnd">
DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);<br>
ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("Types");<br> TypeBuilder typeBuilder = modBuilder.DefineType(className,<br> TypeAttributes.Public |<br> TypeAttributes.Class |<br>
TypeAttributes.AutoClass |<br> TypeAttributes.AnsiClass |<br> TypeAttributes.BeforeFieldInit |<br> TypeAttributes.AutoLayout);<br>
<br><br> FieldBuilder getFieldBuilder = typeBuilder.DefineField("OnGet", typeof(GetPropertyDelegate), FieldAttributes.Public);<br> FieldBuilder setFieldBuilder = typeBuilder.DefineField("OnSet", typeof(SetPropertyDelegate), FieldAttributes.Public);<br>
<br> MethodAttributes getSetAttr =<br> MethodAttributes.Public | MethodAttributes.SpecialName |<br> MethodAttributes.HideBySig;<br> <br><br> foreach (string propertyName in properties)<br>
{<br> PropertyBuilder propBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, typeof(object), new Type[] {});<br><br> MethodBuilder getter =<br> typeBuilder.DefineMethod("get_" + propertyName,<br>
getSetAttr,<br> typeof(string),<br> Type.EmptyTypes);<br><br> ILGenerator ilGen = getter.GetILGenerator();<br>
<br> ilGen.Emit(OpCodes.Ldarg_0);<br> ilGen.Emit(OpCodes.Ldfld, getFieldBuilder);<br> ilGen.Emit(OpCodes.Ldstr, propertyName);<br> ilGen.Emit(OpCodes.Callvirt, typeof(GetPropertyDelegate).GetMethod("Invoke"));<br>
ilGen.Emit(OpCodes.Ret);<br><br> // Define the "set" accessor method for CustomerName.<br> MethodBuilder setter =<br> typeBuilder.DefineMethod("set_" + propertyName,<br>
getSetAttr,<br> null,<br> new Type[] { typeof(string) });<br><br> ilGen = setter.GetILGenerator();<br>
<br> ilGen.Emit(OpCodes.Ldarg_0);<br> ilGen.Emit(OpCodes.Ldfld, setFieldBuilder);<br> ilGen.Emit(OpCodes.Ldstr, propertyName);<br> ilGen.Emit(OpCodes.Ldarg_1);<br>
ilGen.Emit(OpCodes.Callvirt, typeof(SetPropertyDelegate).GetMethod("Invoke"));<br> ilGen.Emit(OpCodes.Pop);<br> ilGen.Emit(OpCodes.Ret);<br><br> // Last, we must map the two methods created above to our PropertyBuilder to <br>
// their corresponding behaviors, "get" and "set" respectively. <br> propBuilder.SetGetMethod(getter);<br> propBuilder.SetSetMethod(setter);<br><br> }<br>
<br> return typeBuilder.CreateType(); <br> }<br> }<br>}<br><br><br>And here is the ruby code to generate the wrapper type<br><br>require 'mscorlib'<br>require 'C:\Documents and Settings\Josh\My Documents\Visual Studio 2008\Projects\GenerateType\GenerateType\bin\Release\GenerateType.dll'<br>
require 'PresentationFramework, Version=<a href="http://3.0.0.0/" target="_blank">3.0.0.0</a>,Culture=neutral, PublicKeyToken=31bf3856ad364e35'<br>require 'PresentationCore, Version=<a href="http://3.0.0.0/" target="_blank">3.0.0.0</a>, Culture=neutral, PublicKeyToken=31bf3856ad364e35'<br>
require 'WindowsBase, Version=<a href="http://3.0.0.0/" target="_blank">3.0.0.0</a>, Culture=neutral, PublicKeyToken=31bf3856ad364e35'<br><br>include System<br>include System::Collections::Generic<br>include GenerateType<br>
include System::Collections::ObjectModel<br>
include System::Windows<br>include System::Windows::Controls<br>include System::Windows::Data<br>include System::Windows::Input<br><br>class WrapperGenerator<br> def initialize<br> @wrapper_cache = {}<br> end<br><br>
def wrap(ruby_object)<br> if ruby_object.is_a? Array<br> ruby_object.map {|o| wrap(o) }<br> else<br> cache(ruby_object) unless cached(ruby_object)<br> wrapper_class = cached(ruby_object)<br> wrapper_class.new(ruby_object)<br>
end<br> end<br><br> def invalidate<br> @wrapper_cache.clear<br> end<br>private<br> def cached(object)<br> @wrapper_cache[<a href="http://object.class.name/" target="_blank">object.class.name</a>]<br> end<br>
<br> def cache(object)<br>
@wrapper_cache[<a href="http://object.class.name/" target="_blank">object.class.name</a>] = generate_wrapper(object)<br> end<br><br> def generate_wrapper(object)<br> wrapper_name = "#{<a href="http://object.class.name/" target="_blank">object.class.name</a>}Wrapper"<br>
properties = List.of(System::String).new<br><br> (object.methods - Object.instance_methods).each {|m| properties.add m.to_s}<br><br> wrapper_base_type = TypeGenerator.generate("#{wrapper_name}Base", properties)<br>
base_instance = Activator.create_instance wrapper_base_type<br><br> eval <<EOS<br> class #{wrapper_name} < base_instance.class<br> def initialize(original)<br> self.on_get = lambda do |prop|<br>
original.send prop<br> end<br><br> self.on_set = lambda do |prop, val|<br> original.send "\#{prop}=", val<br> end<br> end<br> end<br> return #{wrapper_name} # return the class<br>
EOS<br> <br> end<br>end<br><br>class Person <br> attr_accessor :first_name, :last_name<br><br> def initialize(first_name, last_name)<br> @first_name, @last_name = first_name, last_name<br> end<br><br> def full_name<br>
"#{last_name}, #{first_name}"<br> end<br>end<br><br><br># A sample UI<br><br>people = []<br><br>10.times { |n| people << Person.new("John #{n}", "Smith") }<br><br>wrapper = WrapperGenerator.new<br>
<br>w = Window.new<br>combo = ComboBox.new<br>combo.items_source = wrapper.wrap(people) # wrap people objects<br>combo.display_member_path = "full_name"<br>combo.margin = 20<br>w.content = combo<br><br>Application.new.run w</div>