Wednesday, May 12, 2010

Silverlight validation with IronPython

Validation support in Silverlight is done via Visual State Manager. All invalid fields have red rectangle around themselves. Unfortunately, this does not work out of the box in IronPython. We have to push it a little bit.

To demonstrate how, I have created a small example. Create a Silverlight app template and change app.py and app.xaml:

C:\IronPython-2.6.1\Silverlight\script\sl.bat python validation
app.py
import clrtype
import pyevent
from System.Windows import Application
from System.Windows.Controls import UserControl
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs

class ValidationClass(INotifyPropertyChanged):
    __metaclass__ = clrtype.ClrClass
    PropertyChanged = None

    def __init__(self, win):
        self.win = win
        self._text = 'text'
        self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()

    def add_PropertyChanged(self, value):
        self.PropertyChanged += value

    def remove_PropertyChanged(self, value):
        self.PropertyChanged -= value

    def OnPropertyChanged(self, propertyName):
        if self.PropertyChanged is not None:
            self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))

    @property
    @clrtype.accepts()
    @clrtype.returns(str)
    def text(self):
        return self._text

    @text.setter
    @clrtype.accepts(str)
    @clrtype.returns()
    def text(self, value):
        if not value.startswith('text'):
            raise Exception('Value must start with text!')
        self._text = value
        self.OnPropertyChanged('text')

class App:
    def __init__(self):
        self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")
        self.root.DataContext = ValidationClass(self.root)

App()
app.xaml
<UserControl x:Class="System.Windows.Controls.UserControl"
  xmlns="http://schemas.microsoft.com/client/2007"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>
    <TextBox x:Name="tbValidate1" Width="100" Height="25" 
      Text="{Binding text, Mode=TwoWay, ValidatesOnExceptions=True,
      NotifyOnValidationError=True}" />
    <TextBox Width="100" Height="25" />
    <TextBlock Text="{Binding text}" HorizontalAlignment="Center" />
  </StackPanel>
</UserControl>

When you run this application (C:\IronPython-2.6.1\Silverlight\script\server.bat /d:validation), you'll find out the validation does not work. There is no red rectangle when you enter wrong value; e.g. wrong.

Note the second empty TextBox is there so you can move focus out of the first one to update bound property.

For whatever reason, the invalid component is not switched into invalid state. Could be IronPython bug, could be something else. Anyway to fix it, you have to switch the control into invalid state manually. Add the BindingValidationError event:

from System.Windows import VisualStateManager
from System.Windows.Controls import ValidationErrorEventAction

...

class App:
    def __init__(self):
        self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")
        self.root.DataContext = ValidationClass(self.root)
        self.root.BindingValidationError += self.OnBindingValidationError

    def OnBindingValidationError(self, sender, event):
        if event.Action == ValidationErrorEventAction.Added:
            VisualStateManager.GoToState(event.OriginalSource, 'InvalidUnfocused', True)
        else:
            VisualStateManager.GoToState(event.OriginalSource, 'Valid', True

Now when you enter wrong value into TextBox, you can see red rectangle around the control. You also see, the bound variable has the old, correct value text:

You can download the whole source here.

No comments: