In the previous article, I wrote about IronPython and databinding in WPF applications. The last note was it does not work in Silverlight. Thanks to Shri Borde (IronPython/IronRuby dev lead) who updated clrtype module, the note is not true any more.
Let's create a small Silverlight app in IronPython from scratch. I use IronPython 2.6 RC2. Follow http://lists.ironpython.com/pipermail/users-ironpython.com/2009-October/011543.html to avoid bugs in IronPython 2.6 RC2.
Create a new project:
C:\IronPython-2.6\Silverlight\script\sl.bat python BindTest
Change the app.xaml to
<usercontrol x:Class="System.Windows.Controls.UserControl" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <stackpanel x:Name="DataPanel" Orientation="Horizontal"> <textblock Text="Size"/> <textblock Text="{Binding size}"/> <textbox x:Name="tbSize" Text="{Binding size, Mode=TwoWay}" /> <button x:Name="Button" Content="Set Initial Value"></Button> </StackPanel> </UserControl>
The difference comparing to WPF version is we have to specify binding mode because the default mode for TextBox in Silverlight is OneWay. And we cannot use UpdateSourceTrigger=PropertyChanged because Silverlight does not have such UpdateSourceTrigger.
Silverlight binding is limited comparing to WPF. That's why we have to create CLR properties to Silverlight be able to see them. DevHawk has a nice serie about clr types on his blog.
Creating CLR property with clrtype.py is easy. Shri described it on IronPython mailing list. Because I use my enhanced @notify_property decorator, I can write:
class ViewModel(NotifyPropertyChangedBase): __metaclass__ = clrtype.ClrClass _clrnamespace = "BindTest" def __init__(self): super(ViewModel, self).__init__() # must be string to two-way binding work # correctly self.size = '10' @notify_property @clrtype.returns(str) def size(self): return self._size @size.setter @clrtype.accepts(str) def size(self, value): self._size = value print 'Size changed to %r' % self.size
The NotifyPropertyChangedBase class is the same as for WPF version. The enhanced @notify_property decorator calls automatically clrtype.accepts() for getter and clrtype.returns() for setter so we do not need to call them manually for every property:
class notify_property(property): def __init__(self, getter): def newgetter(slf): #return None when the property does not # exist yet try: return getter(slf) except AttributeError: return None getter = clrtype.accepts()(getter) clrtype.propagate_attributes(getter, newgetter) super(notify_property, self).__init__(newgetter) def setter(self, setter): def newsetter(slf, newvalue): # do not change value if the new value is # the same, trigger PropertyChanged event # when value changes oldvalue = self.fget(slf) if oldvalue != newvalue: setter(slf, newvalue) slf.OnPropertyChanged(setter.__name__) setter = clrtype.returns()(setter) clrtype.propagate_attributes(setter, newsetter) return property( fget=self.fget, fset=newsetter, fdel=self.fdel, doc=self.__doc__)
Then App looks similarly to the WPF counterpart:
class App: def __init__(self): self._vm = ViewModel() self.root = Application.Current.LoadRootVisual( UserControl(), "app.xaml") self.DataPanel.DataContext = self._vm self.Button.Click += self.OnClick def OnClick(self, sender, event): # must be string to two-way binding work # correctly self._vm.size = '10' def __getattr__(self, name): # provides easy access to XAML elements # (e.g. self.Button) return self.root.FindName(name) a = App()
Run Chiron with the BindTest app
C:\IronPython-2.6\Silverlight\script\sl.bat python BindTest
and check the application in the browser on http://localhost:2060/index.html.
Whatever you write into the text box appears in the label in front of the text box when the text box loses the focus. When you click the button, the value is reseted. You can also change the value from the console:
a._vm.size= '3'
Download app.xaml and app.py. You also need clrtype.py and pyevent.py (from C:\IronPython-2.6\Tutorial\pyevent.py) in the BindTest folder.
6 comments:
Cool - I'm actually using this at work today. I'll let you know if I have any problems... :-)
Hmmm... when I try this in Silverlight I get "Could not add reference to assembly Microsoft.Dynamic" from clrtype.py.
Also the assertion on line 68 of clrtype.py is incorrectly written (it is written as tuple that will *always* evaluate to True).
Dammit. So I make the add reference to Microsoft.Dynamic fully qualified (version and public key).
Next I get the error:
TypeError: Cannot create instances of DynamicOperations because it has no public constructors
Which is weird, because when I look at DynamicOperations with reflector it has a public constructor that takes a language context. This is how it is being used in clrtype.
I need to *double check* we are using IronPython 2.6 RC 2.
Ok, we weren't using 2.6 RC2. It works now, thanks.
@Michael: Good it works for you.
Post a Comment