From Silverlight to Javascript and Back Again

The Scriptable Attributes

Where did he come from?

 

 

The Scriptable Attributes

We can call into Silverlight from Javascript using one technique, and a slightly different one for calling from Silverlight into Javascript. Both of them are based on marking classes and methods with .NET attributes. These are useful techniques for creating hybrid applications that are partly written in Silverlight and partially written in Javascript. As Python code running in Silverlight runs faster than Javascript (and Python is a nicer language of course), you could even use Silverlight as an optional accelerator for RIAs (Rich Internet Applications) - falling back to Javascript when Silverlight is unavailable.

Unfortunately we can't set attributes from IronPython, so we have to use a bit of C#. Fortunately we can create a stub-class, and then subclass from IronPython. The C# is simple enough that, even if you have never seen C# before, you should be able to understand it.

Note

As well as the techniques shown in this article, you can add Python event handlers to Javascript, and call Javascript functions, all from within Silverlight and without having to use C#. These techniques can still be useful however.

The Scriptable attributes lives in the System.Windows.Browser namespace. C# can be compiled using Visual Studio 2008 (or MonoDevelop). From Visual Studio you will also need Visual Studio tools for Silverlight installed. Fortunately you don't need these installed to compile assemblies for Silverlight - we'll see how in a few moments.

Scriptable C# Class

C# for a Scriptable class with a Scriptable method that takes and returns a string look like:

using System;
using System.Windows.Browser;

namespace Scriptable
{
    [ScriptableTypeAttribute]
    public class ScriptableForString
    {
        [ScriptableMemberAttribute]
        public string method(string value)
        { return this._method(value); }

        public virtual string _method(string value)
        { return "override me"; }
    }
}

This uses the ScriptableTypeAttribute and ScriptableMemberAttribute attributes

We need a class marked as Scriptable with a method marked as Scriptable. The method should call a virtual method that we can override in an IronPython subclass. The Python subclass will still be marked as Scriptable, as will its Scriptable method. If we want to pass and return arguments they need to be statically typed, and can only be a primitive like a string or an integer. This isn't really a problem though because we can pass or return JSON as a string. There is a JSON serializer and deserializer available with Silverlight, and using JSON from Javascript is easy of course!

Using from IronPython

To use this from IronPython we need to import the class from the assembly we have compiled, adding a reference to it in the normal way (and making sure that it is included in the manifest / xap of course):

import clr
clr.AddReference("Scriptable")
from Scriptable import ScriptableForString

class SomeClass(ScriptableForString):
    def _method(self, string):
        ...
        return result

some_class = SomeClass()

If you are using clr.AddReference to access the assembly then the assembly must be in the application manifest file (AppManifest.xaml). Another alternative is to just include the assembly in the xap but not add it to manifest, and use clr.AddReferenceToFile to add the reference.

Registering the Scriptable Object

Because our scriptable object needs to be visible from both IronPython and from Javascript we need to register it on the Silverlight control. This is done from IronPython with the following code:

from System.Windows.Browser import HtmlPage

HtmlPage.RegisterScriptableObject(
    "some_class", some_class
)

Calling from Javascript

Once we have registered the scriptable object, it is then available on the control to be called from Javascript. This is made easier by giving the Silverlight control an id in the object tag in the html.

control = document.getElementById(
            'SilverlightPlugin'
          );
result = control.Content.some_class.method(
            value
         );

You can only do your hooking up after the Silverlight control has loaded. The easiest way to do this is inside a Javascript function that you specify in the onload parameter of the Silverlight control element:

<script>
    function onload() {
        control = document.getElementById(
                    'SilverlightPlugin'
                  );
        result = control.Content.some_class.method(
                    value
                 );
    }
</script>

<object id="SilverlightPlugin" data="data:application/x-silverlight,"
 type="application/x-silverlight-2" width="450" height="540">
    <param name="source" value="app.xap"/>
    <param name="onerror" value="onSilverlightError" />
    <param name="onload" value="onload" />
    ...

If you don't want to call the method straight away you can set up a flag inside the onload function that will tell you whether or not you can call into Silverlight.

Note

Apparently onload is unreliable and can sometimes be fired before the Silverlight engine is ready to execute code.

C# Scriptable Event

The techniques we have just looked at are fine for calling from Javascript into Silverlight. To go the other way (from Silverlight calling into Javascript) we need to create a scriptable event.

using System;
using System.Windows.Browser;

namespace Scriptable
{
    [ScriptableTypeAttribute]
    public class ScriptableEvent
    {
        [ScriptableMemberAttribute]
        public event EventHandler Event;

        public virtual void OnEvent(ScriptableEventArgs e)
        {
            Event(this, e);
        }
    }
}

This scriptable event needs to work with scriptable event args.

[ScriptableTypeAttribute]
public class ScriptableEventArgs : EventArgs
{
    private string _val;

    [ScriptableMemberAttribute]
    public string val
    {
        get { return _val; }
        set { _val = value; }
    }
}

We need to register this on both the IronPython and the Javascript side. The Javascript needs to attach an event handler to the event we have exposed. When we fire the event from inside Silverlight, the handler will receive the eventargs we pass in from the IronPython side and be able to access the scriptable members we gave it. The Javascript side can modify those members to return values.

Using from IronPython

No need to subclass this time.

event = ScriptableEvent()

# This must also be registered
HtmlPage.RegisterScriptableObject(
    "event", event)

args = ScriptableEventArgs()
args.val = 'some string'
event.OnEvent(args)

The Javascript

In order to use this event, we have to assign a Java script function to the event exposed on the control.

We can only assign one function to this event (no multicast). This function will receive two arguments (like normal .NET events), the sender and the event we pass in.

function some_function(sender, args) {
    value = args.val;
    ...
    args.val = new_value;
}

control = document.getElementById('SilverlightPlugin');
control.Content.event.Event = some_function;

When event.OnEvent(args) is called from Python, the javascript function (some_function) is called and receives the arguments sender and our event args. The Javascript can modify the attributes on the event to return values. After the call returns, IronPython can look at the attributes on the event args to retrieve any return values.

Just like with calling from Javascript into Silverlight, we can only hook up the event to its Javascript function once the Silverlight control has loaded and you have registered the event. Again, the usual way to do this is in an onload function with the same caveat as previously.

Compiling C# with Notepad

Having to download Visual Studio 2008 (and that is one hefty download) just to compile a few lines of C# is a nuisance. Fortunately we can get round this by using the .NET 2 compiler to do it for us. (I think that the compiler, csc.exe, comes with the .NET 2 SDK - but it may even be included in a normal .NET 2 install.)

The following batch file tells csc to compile with references to the Sivlerlight DLLs instead of the standard framework ones. It compiles all the C# files in the directory (*.cs) into an assembly specified by the /out argument (/out:SilverlightApp.dll).

set sl=C:\Program Files\Microsoft Silverlight\2.0.31005.0
set csc=C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe

%csc% /out:Scriptable.dll
    /t:library /nostdlib+ /noconfig /r:"%sl%\mscorlib.dll"
    /r:"%sl%\System.dll" /r:"%sl%\System.Core.dll"
    /r:"%sl%\System.Net.dll" /r:"%sl%\System.Windows.Browser.dll"
    *.cs

pause

The lines that start %csc% need to be all on one line - I've split it into multiple lines here for readability. The paths I've included above are the paths that I found csc and the Silverlight assemblies on my machine (Windows Vista). You may need to modify them for yours. I haven't tried this with Mono on the Mac, but I would be interested to hear from anyone who gets it working (or otherwise).

From compiling C# for use with IronPython, the next logical step is embedding the IronPython interpreter in a C# Silverlight application:

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Fri Nov 27 18:32:35 2009.

Counter...