The Silverlight APIs
Experimenting with the IronPython Web IDE
Silverlight & IronPython
The IronPython Web IDE
In this article we will explore some of the APIs available to us in the CoreCLR that is the heart of Silverlight.
We will do this with the aid of the IronPython Web IDE:
www.voidspace.org.uk/ironpython/webide/webide.html
You can download the Web IDE as a project:
It comes with several pieces of example code, but also lets you explore the Silverlight API and load and save Python code.
It uses the Javscript EditArea syntax highlighting Python editor.
Standard out is diverted, so that print statements are sent to the 'debugging pane' at the bottom of the screen. Tracebacks are also printed there, to aid debugging.
The examples here are all shortened forms of the full code in the Web IDE, so I highly recommend checking it out. In all these examples, root is the root visual element. This has already been set using code that looks like:
from System.Windows.Controls import Canvas
root = Canvas()
Application.Current.RootVisual = root
The root element doesn't need to be a canvas. Some of the Microsoft examples use a UserControl or subclass. The programming model for Silverlight is based on WPF. It isn't identical, but in many cases some code and XAML could be shared between desktop and Silverlight applications. Those applications would only run on Windows though, there is currently no effort to port WPF to Mono (although it is possible that the Moonlight effort may change this of course).
The Video Player
One of the impressive features of Silverlight is the video player. From code this is called the MediaElement, and we can use it from IronPython.
from System import TimeSpan, Uri, UriKind
m = MediaElement()
u = Uri('HelloWorld.wmv', UriKind.Relative)
t = TimeSpan(0)
m.Source = u
m.Position = t
The example in the Web IDE also shows setting events on the MediaElement to handle it being clicked and restarting the video when it ends (causing it to loop).
Accessing the Browser DOM
Changing HTML elements and setting style attributes. HtmlPage.Document gives us access to the browser DOM. We can access elements as attributes on the document (just like in Javascript - something you can't do from C#!), or use the GetElementById method. CSS attributes are set with the SetStyleAttribute method on elements.
html = '<strong>Set from IronPython</strong>'
HtmlPage.Document.experimental.innerHTML = html
e = HtmlPage.Document.GetElementById('experimental')
e.SetStyleAttribute('border', 'solid black 2px')
# Call a javascript function "writesomestuff"
HtmlPage.Window.CreateInstance("writesomestuff", 'One last thing...\n')
The last part of the code (HtmlPage.Window.CreateInstance) calls a Javascript function defined in the web-page.
We can also attach event handlers to Javascript events from Silverlight:
from System.Windows.Browser.HtmlPage import Document
from System.Windows.Controls import TextBlock
root.Children.Clear()
t = TextBlock()
t.Text = 'Nothing yet...'
root.Children.Add(t)
def OnClick(sender, event):
text = Document.input_field.value
t.Text = text
handler = EventHandler(OnClick)
Document.OkButton.AttachEvent("onclick", handler)
These are two ways of communicating between Javascript and the Silverlight control (calling functions and setting handlers that call back into Silverlight); very useful for creating hybrid applications that use both Silverlight and Javascript for their UI and functionality.
Open File Dialog
The only way you can access files on the local filesystem is by presenting the user with an OpenFileDialog. If the user selects a file(s) then you can read from them without having access to their names (their location on the filesystem).
OpenFileDialog
)
dialog = OpenFileDialog()
dialog.Filter = "Python files (*.py)|*.py|All files (*.*)|*.*"
dialog.Multiselect = True
if dialog.ShowDialog() == True:
data = dialog.File.OpenText().ReadToEnd()
print data
else:
print 'The user cancelled'
Inexplicably in-between the move from Silverlight 1.1 to 2.0 we lost the ability to set the Title on the dialog. Presumably it was judged a security risk...
There is no SaveFileDialog, but you can hack it up by bouncing data off a CGI script that sends the right headers back to the browser! (See this blog entry for an example.)
IsolatedFileStorage
IsolatedStorageFile, IsolatedStorageFileStream
)
from System.IO import (
FileMode, StreamReader, StreamWriter
)
store = IsolatedStorageFile.GetUserStoreForApplication()
isolated = IsolatedStorageFileStream(name, FileMode.OpenOrCreate, store)
writer = StreamWriter(isolated)
writer.Write(data)
writer.Close()
isolated.Close()
Silverlight provides 1 megabyte of local storage ('in the browser') per application, which presumably means per URL. Your application can request that this limit be raised, but it requires user confirmation.
You access it through the IsolatedStorageFile class. Isolated storage provides a filesystem stored in the browser cache. You can create subdirectories and read / list / change files in those directories.
Accessing Server Resources with the WebClient
The APIs for accessing server resources from Silverlight are asynchronous. This means that we need to configure the request and provide callbacks for events that are raised as the request is executed. This means a slightly different style of programming than you may be used to, even for straightforward operations. On the other hand it does avoid blocking the user interface and so it can be a good change. One of the core classes for accessing web services and server resources is the WebClient.
from System.IO import StreamReader
from System.Net import WebClient
uri = Uri('/', UriKind.Relative)
web = WebClient()
def completed(s, e):
print 'Completed'
print 'Error?', e.Error
print 'Cancelled?', e.Cancelled
print e.Result
def changed(s, e):
print 'Bytes Recieved', e.BytesReceived
print 'Progress Percentage', e.ProgressPercentage
web.DownloadStringCompleted += completed
web.DownloadProgressChanged += changed
web.DownloadStringAsync(uri)
The example in the web IDE also shows the APIs for working with a stream instead of strings - useful if you want to download binary data (like an image).
Because events are asynchronous they are also raised on another thread. If you want to interact with the user interface (the WPF event loop) then we need to know a little bit about the WPF threading model.
Silverlight and Threading
In order to invoke code back onto the GUI thread (the main thread) we need to use the Dispatcher.
from System.Threading import Thread, ThreadStart
text = TextBlock()
text.Text = "Nothing yet"
text.FontSize = 24
root.Children.Clear()
root.Children.Add(text)
def wait():
Thread.Sleep(3000)
def SetText():
text.Text = 'Hello from another thread'
text.Dispatcher.BeginInvoke(SetText)
t = Thread(ThreadStart(wait))
t.Start()
This model can also be used for keeping calculations on a background thread whilst leaving the user interface responsive.
Reading Files from the 'xap'
Silverlight doesn't give us access to the user's filesystem (except through the open file dialog). The file type is still present from IronPython though, and we can use it to read files contained in the 'xap'. This provides a convenient way of packaging data for your app:
# Read from a file in the 'xap'
handle = file('app.py')
data = handle.read()
handle.close()
t = TextBlock()
t.Text = data
root.Children.Clear()
root.Children.Add(t)
There are several other examples in the Web IDE that we haven't covered here. These include setting the position of objects on a canvas, loading XAML and so on. Another potentially useful example is the Button class that is used to make the wonderful blue and yellow buttons in the Web IDE. This is contained in the file button.py and illustrates initialising a control from XAML (amongst other things).
The next article looks at the scriptable attributes, and how we can use them to communicate between Silverlight and Javascript. This uses some C#, but we can compile this from the command line without needing to install Visual Studio.
For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.
Last edited Sat Aug 14 14:30:50 2010.
Counter...

