WPF and MVVM in IronPython
I’ve been getting to grips with IronPython recently, and wanted to see how easy it would be to use the MVVM pattern. What we need is a basic library of MVVM helper functions. First is a class to load an object from a XAML file.
import clr
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")
from System.IO import File
from System.Windows.Markup import XamlReader
class XamlLoader(object):
def __init__(self, xamlPath):
stream = File.OpenRead(xamlPath)
self.Root = XamlReader.Load(stream)
def __getattr__(self, item):
"""Maps values to attributes.
Only called if there *isn't* an attribute with this name
"""
return self.Root.FindName(item)
In addition to loading the XAML, I’ve added a helper method to make it easy to access any named items within your XAML file, just in case the MVVM approach is proving problematic and you decide to work directly with the controls.
Next we need a base class for our view models to inherit from, which implements INotifyPropertyChanged
. I thought it might be tricky to inherit from .NET interfaces that contain events, but it turns out to be remarkably simple. We just inplement add_PropertyChanged
and remove_PropertyChanged
, and then we can raise notifications whenever we want.
from System.ComponentModel import INotifyPropertyChanged
from System.ComponentModel import PropertyChangedEventArgs
class ViewModelBase(INotifyPropertyChanged):
def __init__(self):
self.propertyChangedHandlers = []
def RaisePropertyChanged(self, propertyName):
args = PropertyChangedEventArgs(propertyName)
for handler in self.propertyChangedHandlers:
handler(self, args)
def add_PropertyChanged(self, handler):
self.propertyChangedHandlers.append(handler)
def remove_PropertyChanged(self, handler):
self.propertyChangedHandlers.remove(handler)
The next thing we need is a way of creating command objects. I created a very basic class that inherits from ICommand
and allows us to run our own function when Execute
is called. Obviously it could easily be enhanced to properly support CanExecuteChanged
and command parameters.
from System.Windows.Input import ICommand
class Command(ICommand):
def __init__(self, execute):
self.execute = execute
def Execute(self, parameter):
self.execute()
def add_CanExecuteChanged(self, handler):
pass
def remove_CanExecuteChanged(self, handler):
pass
def CanExecute(self, parameter):
return True
And now we are ready to create our application. Here’s some basic XAML. I’ve only named the grid to demonstrate accessing members directly, but it obviously is not good MVVM practice.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="IronPython MVVM Demo"
Width="450"
SizeToContent="Height">
<Grid Margin="15" x:Name="grid1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" FontSize="24" Content="First Name:" />
<Label Grid.Row="0" Grid.Column="1" FontSize="24" Content="{Binding FirstName}" />
<Label Grid.Row="1" Grid.Column="0" FontSize="24" Content="Surname:" />
<Label Grid.Row="1" Grid.Column="1" FontSize="24" Content="{Binding Surname}" />
<Button Grid.Row="2" FontSize="24" Content="Change" Command="{Binding ChangeCommand}" />
</Grid>
</Window>
Now we can make our ViewModel
. It will have FirstName
and Surname
attributes as well as an instance of our Command
object:
class ViewModel(ViewModelBase):
def __init__(self):
ViewModelBase.__init__(self)
self.FirstName = "Joe"
self.Surname = "Smith"
self.ChangeCommand = Command(self.change)
def change(self):
self.FirstName = "Dave"
self.Surname = "Brown"
self.RaisePropertyChanged("FirstName")
self.RaisePropertyChanged("Surname")
And finally we are ready to create our application. Simply load the XAML in with the XamlLoader
and set the DataContext
. I also demonstrate setting the background colour here, to show how easy it is to access named elements in the XAML:
from System.Windows import Application
from System.Windows.Media import Brushes
xaml = XamlLoader('WpfMvvmDemo.xaml')
xaml.Root.DataContext = ViewModel()
xaml.grid1.Background = Brushes.DarkSalmon
app = Application()
app.Run(xaml.Root)
Now we run it and see:
And click the button to see:
And that’s all there is to it. It may even be simpler than doing MVVM from C#.
Comments
Thanks for posting this. I am trying to learn Ironpython with very little previous rigorous programming experience, and it really helps to have code like this to take a look at (I was looking for examples of databinding, Ironpython, and MVVC).
indang-farmerThere seems to be a typo in the xaml. Some HTML snuck in as a hyper-reference where grid1 is defined.
Removed the HTML and the code worked fine. Thanks.
Thank you very much for your effort!!
Code Docta(Nick C.)