Sunday, November 13, 2011

Sharing .xaml in WPF and Silverlight

Sharing code between WPF and Silverlight is not difficult. Good how-to is in the Prism guide. However sharing more complex .xaml is not so easy. You have to handle different namespaces and properties.

Handling different namespaces

Silverlight toolkit components are accessible via special namespace. So if you want to use a WrapPanel in Silverlight, you write something like this:

<UserControl x:Class="UnifiedXaml.TestControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
    <toolkit:WrapPanel></toolkit:WrapPanel>
</UserControl>

WPF world is easier:

<UserControl x:Class="UnifiedXaml.TestControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <WrapPanel></WrapPanel>
</UserControl>

It's clear those two .xaml files are so similar that they should be just one file. Here is one tiny line that makes it possible:

namespace UnifiedXaml
{
    public class MyWrapPanel: WrapPanel { }
}

From now on, you can share the .xaml file:

<UserControl x:Class="UnifiedXaml.TestControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ux="clr-namespace:UnifiedXaml">
    <ux:MyWrapPanel></ux:MyWrapPanel>
</UserControl>

Handling different properties

When you follow Prism guidelines, you have separate projects for WPF and Silverlight parts. That means the WPF/SL application resources dictionaries are also separate. So the easiest way to handle different properties is to use styles. Shared .xaml file specifies just the control and its style. All necessary properties for WPF are defined in the style stored in the WPF application resources dictionary and the same is done for Silverlight.

Friday, September 10, 2010

Anonymous function as default argument in methods

I was quite surprised by Python recently when I tried to use anonymous function that uses a class method (not classmethod) as a default argument for another class method and it worked. Example is worth thousands words so here it is:
class ListObject(object):
    items = ((1, 'one'), (2, 'two'), (3, 'three'))

    def get_num(self, item):
        return item[0]

    def get_text(self, item):
        return item[1]

    def get_list(self, fn=lambda s, i: ListObject.get_num(s, i)):
        return [fn(self, j) for j in self.items]

lo = ListObject()
print 'numbers:', lo.get_list()
print 'texts:', lo.get_list(ListObject.get_text)
print 'texts:', lo.get_list(lambda s, i: i[1])
print 'items:', lo.get_list(lambda s, i: i)
When you run this piece of code you get what you want:
numbers: [1, 2, 3]
texts: ['one', 'two', 'three']
texts: ['one', 'two', 'three']
items: [(1, 'one'), (2, 'two'), (3, 'three')]
By default, get_list method uses get_num method and apply it to all items. But you can supply your own fucntion to apply it on items.

Of course, this is very stupid example but it shows the principle. And this principle is quite handy for UI automation I am currently working on :-)

Sunday, May 16, 2010

Distributing Silverlight application written in IronPython

When you have Silverlight application written in IronPython, it is a good idea to split it to several files so browser can cache them separately. Later, when you change something in your application, users will download only a small part. During my attemts with IronPython and Silverligt, I have found several catches. That's why I describe here my way how to distribute IronPython Silverlight application.

I distribute my application as one .html file, one .xap file, and several .zip files. I use .zip because IIS already knows what to do with .zip files. The files are:

  1. index.html
  2. app.xap
  3. IronPython.zip - contains files from IronPython-2.6.1\Silverlight\bin:
    IronPython.dll
    IronPython.Modules.dll
    
  4. Microsoft.Scripting.zip - contains files from IronPython-2.6.1\Silverlight\bin:
    Microsoft.Dynamic.dll
    Microsoft.Scripting.dll
    Microsoft.Scripting.Core.dll
    Microsoft.Scripting.ExtensionAttribute.dll
    Microsoft.Scripting.Silverlight.dll
    
  5. SLToolkit.zip - contains files form Silverlight toolkit or SDK; in our case just
  6. System.Windows.Controls.dll
    

Let's create a small application, that uses ChildWindow control from Silverlight toolkit:

C:\IronPython-2.6.1\Silverlight\script\sl.bat python childwindow
Change the app.py and app.xml:

app.py

from System.Windows import Application
from System.Windows.Controls import UserControl

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

a = 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"
  xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
  <controls:ChildWindow >
    <StackPanel>
      <TextBlock Text="Text in ChildWindow"/>
      <Button x:Name="btnNewWindow" Content="New window"/>
    </StackPanel>
  </controls:ChildWindow>
</UserControl>

We don't want to Chiron automatically add necesary .dll files into .xap so we have to add our own AppManifest.xaml and languages.config into childwindow\app folder:

AppManifest.xaml

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  RuntimeVersion="2.0.31005.0"
  EntryPointAssembly="Microsoft.Scripting.Silverlight"
  EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication"
  ExternalCallersFromCrossDomain="ScriptableOnly">
  <Deployment.Parts>
  </Deployment.Parts>
  <Deployment.ExternalParts>
    <ExtensionPart Source="Microsoft.Scripting.zip" />
    <ExtensionPart Source="SLToolkit.zip" />
  </Deployment.ExternalParts>
</Deployment>

languages.config

<Languages>
  <Language names="IronPython,Python,py"
    languageContext="IronPython.Runtime.PythonContext"
    extensions=".py"
    assemblies="IronPython.dll;IronPython.Modules.dll"
    external="IronPython.zip"/>
</Languages>

Now create all three .zip files and add them into childwindow folder.

To test the application with Chiron, run

C:\IronPython-2.6.1\Silverlight\bin\Chiron.exe /e: /d:childwindow

The /e: switch is important - it tells Chiron to not put any assembly into generated .xap file. Check the application on http://localhost:2060/index.html.

To generate .xap file for distribution, run:

C:\IronPython-2.6.1\Silverlight\bin\Chiron.exe /e: /d:childwindow\app /z:app.zap

If you want to use anything from external assemblies in the code, you have to add manually reference to those assemblies. For example, if you want to add a button that creates a new ChildWindow, you have to change you code like this:

app.py

from System.Windows import Application
from System.Windows.Controls import UserControl

class App:
    def __init__(self):
        self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml")
        self.root.btnNewWindow.Click += self.OnClick

    def OnClick(self, sender, event):
        import clr
        clr.AddReference('System.Windows.Controls')
        from System.Windows.Controls import ChildWindow
        self.root.panel.Children.Add(ChildWindow(Content='new window'))

a = 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"
  xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
  <controls:ChildWindow >
    <StackPanel x:Name="panel">
      <TextBlock Text="Text in ChildWindow"/>
      <Button x:Name="btnNewWindow" Content="New window"/>
    </StackPanel>
  </controls:ChildWindow>
</UserControl>

If you comment out the clr.AddReference line, ImportError appears. See the explanation in Jimmy's email.

You can download the example here but note the .zip files do not contain and .dlls.