Chapter 13. Silverlight: IronPython in the browser

This chapter covers

  • Packaging a dynamic Silverlight application

  • Using the Silverlight APIs

  • Building user interfaces

  • Interacting with the browser DOM

Improved browser capabilities, enabled by faster computers, have not only made complex and rich web applications possible, but have also changed the way we use computers. Techniques like AJAX, and powerful JavaScript libraries that abstract away painful cross-browser issues, have resulted in client-side (in the browser) programming becoming one of the most important fields in modern software development. Despite this, web applications remain restricted by the user interface capabilities of JavaScript and the performance of JavaScript when running large programs. Ways around these difficulties include web-programming frameworks such as Flash, AIR, Flex, and Silverlight, which have their own programming and user interface models.

In case you haven’t heard of it, Silverlight is a cross-platform browser plugin created by Microsoft. It is superficially similar to Flash but with the magic extra ingredient that we can program it with Python. Because Silverlight is based on the .NET framework, it is a major new platform that IronPython runs on.

Silverlight has a user interface model based on Windows Presentation Foundation, and we can use it to do some exciting things, such as media streaming, creating games, and building rich internet applications.

In this chapter, we look at some of what Silverlight can do and how to do it from IronPython. We’ll be exploring, creating, and deploying dynamic Silverlight applications and programming with the Silverlight APIs—some of which are familiar and some of which are new.

Introduction to Silverlight

Silverlight is a cross-platform, cross-browser plugin for embedding into web pages. There are two versions of Silverlight: Silverlight 1 is for media streaming and is programmed with JavaScript. Silverlight 2, the interesting version, has at its heart a cut-down and security-sandboxed version of the .NET framework called the CoreCLR. That means it contains many of the APIs we are already familiar with, including the WPF user interface. More important, the CoreCLR is capable of hosting the Dynamic Language Runtime, so Silverlight can be programmed with DLR languages like IronPython and IronRuby.

In this chapter we explore some of the features that Silverlight provides. Figure 13.1 shows a Tetrislite[1] game written for Silverlight 2, from the Silverlight Gallery.

Tetrislite from the Silverlight Gallery

Figure 13.1. Tetrislite from the Silverlight Gallery

By cross-platform, Microsoft means Windows and Mac OS X. By cross browser, the Microsoft folks mean the Safari, Firefox, and Internet Explorer web browsers. This isn’t the end of the story, though; Silverlight support is in the works for the Opera browser and for the Windows Mobile and Nokia S60 platforms. The Silverlight team members are not working directly to support Linux, but they are working with the Mono team on an officially blessed Mono port of Silverlight called Moonlight.[2]. This will initially work on Firefox on Linux, but the eventual goal is to get Moonlight working on multiple browsers (like Konqueror) and on every platform that Mono runs on.

Microsoft is assisting the Moonlight effort by providing access to the Silverlight test suite and the proprietary video codecs that Silverlight uses. At the time of writing, Moonlight supports the 1.0 engine, and work has begun on the 2.0 engine. Moonlight uses the Mono stack, but a lot of the work involves implementing the security model that provides the Silverlight browser sandboxing. Another big part is the user interface model; this is particularly interesting, as previously the Mono team has said that they have no interest in implementing WPF. Perhaps this will change now that they are implementing a subset of WPF for Moonlight.

So what benefits for web application programming does Silverlight have over traditional JavaScript and AJAX? The first advantage is that it can be programmed in Python, and frankly that’s enough for us. The Python community has long wanted to be able to script the browser with Python rather than JavaScript, and it is at least slightly ironic that it is Microsoft that has made this possible. Perhaps a more compelling reason is that Silverlight is fast. By some benchmarks[3] IronPython code running in Silverlight runs two orders of magnitude faster than JavaScript! As well as having its own user interface model, Silverlight also gives you full access to the browser DOM (Document Object Model), so that everything you can do from JavaScript you can also do from inside Silverlight. In fact, one of the dynamic languages that run on the DLR is an ECMA 3[4]–compliant version of JavaScript called Managed JScript. This makes it easier to port AJAX applications to Silverlight, because a lot of the code can run unmodified.

The features of Silverlight include

  • A user interface model based on WPF

  • APIs including threading, sockets, XML, and JSON

  • A powerful video player

  • Deep zoom and adaptive media streaming

  • Client-side local storage in the browser

  • Access to the browser DOM and techniques to communicate between JavaScript and the Silverlight plugin

The last point is particularly interesting. Although most example Silverlight applications take over the whole web page, this is not the only way to use it. Like Flash, the Silverlight plugin can occupy as little of a web page as you want,[5] and you can embed several plugins (which can communicate with each other) in the same page. In fact, the Silverlight plugin need not be visible at all. By interacting with JavaScript and the browser DOM, you can use all your favorite AJAX tricks from Silverlight, including using existing JavaScript libraries for the user interface, with the business logic implemented in Python.

Moonlight has an additional mode of operation that isn’t (yet!) supported by Silverlight. Moonlight applications called desklets[6] can run outside the browser with full access to the Mono stack.

Creating Silverlight applications with dynamic languages is very simple. Let’s dive into the development process.

Dynamic Silverlight

Silverlight applications are packaged as xap files (compressed zip files), containing the assemblies and resources used by your application. An IronPython application is just a normal application as far as Silverlight is concerned; a DLR assembly provides the entry point and is responsible for running the Python code.

The DLR assemblies, along with the tool for developing and packaging dynamic applications, are known by the wonderful mouthful Silverlight Dynamic Languages SDK and can be downloaded from http://www.codeplex.com/sdlsdk.

The Silverlight Dynamic Languages SDK is comprised of the DLR runtime and IronPython, IronRuby, and Managed JScript assemblies compiled for Silverlight, along with the development tool Chiron.exe. Releases of IronPython should also include binary builds of IronPython for Silverlight and Chiron but won’t include the other languages.

Chiron can create xap files for dynamic applications and will also work as a server, allowing you to use your applications from the filesystem while you are developing them. Chiron runs under Mono, so you can use it on the Mac.

Silverlight lives on the web, so to use it we need to embed it into a web page.

Embedding the Silverlight Control

Embedding a Silverlight control into a web page is straightforward. You use an HTML <object> tag, with parameters that initialize the control and HTML that displays the Install Microsoft Silverlight link and image (shown in figure 13.2) if Silverlight is not installed.

The image displayed to the user if Silverlight is not installed

Figure 13.2. The image displayed to the user if Silverlight is not installed

Listing 13.1 is the typical HTML for embedding the Silverlight control into a web page.

Example 13.1. The HTML to embed the Silverlight control

The HTML to embed the Silverlight control

The onError parameter is a JavaScript function that will be called if an error occurs inside Silverlight (including in your code). The value in initParams is a bit special. debug=true enables better exception messages. As well as getting the error message, you’ll get a stack trace and a snapshot of the code that caused the exception. It works in conjunction with an errorLocation div in your HTML (plus a bit of CSS for styling), which is where the tracebacks from your application are displayed. You can see the necessary HTML/CSS in the sources of the examples for this chapter.

The xap File

The xap file is really just a zip file with a different extension; you can construct xap files manually or they can be autogenerated for you by Chiron. The command-line magic to make Chiron create a xap file from a directory is

binChiron /d:dir_name /z:app_name.xap

If you’re running this on the Mac, then the command line will look something like this:

mono bin/Chiron.exe /d:dir_name /z:app_name.xap

More important, you can use Chiron to test your application from the filesystem, without having to create a xap file. If your application is called app.xap, then your application files should be kept in a directory named app. Chiron will act as a local server and dynamically create xap files as they are requested. The command to launch Chiron as a web server is

Chiron /w

By default, this serves on localhost port 2060. The most important part of the xap file is the entry point, which in dynamic applications will be a file called app.py, app.rb, or app.js, depending on which dynamic language you are programming in.

Your Python application

Our first app.py will be the simplest possible IronPython Silverlight application. Because we’re using the WPF UI system, we work with classes from ystem.Windows namespaces. Listing 13.2 creates a Canvas, with an embedded TextBlock displaying a message.

Example 13.2. A Simple IronPython application for Silverlight

from System.Windows import Application
from System.Windows.Controls import Canvas, TextBlock

canvas = Canvas()
textblock = TextBlock()
textblock.FontSize = 24
textblock.Text = 'Hello World from IronPython'
canvas.Children.Add(textblock)

Application.Current.RootVisual = canvas

The important line, which is different from our previous work with WPF, is the last one, where we set the container canvas as the RootVisual on the current application. This makes our canvas the root (top-level) object in the object tree of the displayed user interface. You’re not stuck with a single Python file for your application though. Imports work normally for Python modules and packages contained in your xap file (or your application directory when developing with Chiron). You can use open or file to access other resources from inside the xap file, but they are sandboxed, and you can’t use them to get at anything on the client computer.

Because we’re using WPF, many applications load XAML for the basic layout. They do so with very similar code to listing 13.2, as shown in listing 13.3.

Example 13.3. IronPython Silverlight application that loads XAML

from System.Windows import Application
from System.Windows.Controls import Canvas

canv = Canvas()
xaml = Application.Current.LoadRootVisual(canv, "app.xaml")
xaml.textblock.Text = 'Hello World from IronPython'

Instead of setting the RootVisual on the application, this code loads the XAML with a call to LoadRootVisual. Listing 13.4 shows the app.xaml file, for a Canvas containing a TextBlock, loaded from the xap file in listing 13.3.

Example 13.4. Silverlight XAML for UI layout

<Canvas x:Class="System.Windows.Controls.Canvas"
   xmlns="http://schemas.microsoft.com/client/2007"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<TextBlock x:Name="textblock" FontSize="30">Hello world from XAML</TextBlock>

</Canvas>

The call to LoadRootVisual returns us an object tree. Like the object trees we worked with from XAML in chapter 9, we can access elements that we marked with x:Name as attributes on the object tree. This allows us to set the text on the TextBlock through xaml.textblock.Text. Many of the techniques we learned when working with the WPF libraries for desktop applications are relevant to Silverlight, but they are far from identical. One of the major differences is that we have fewer controls to work with. Let’s look at some of the APIs available for creating Silverlight applications, including the extended set of controls.

Silverlight controls

Silverlight ships with a set of standard controls based on WPF and contained in the System.Windows.Controls namespace. Figure 13.3 shows examples of the standard controls.

Some of the standard Silverlight controls

Figure 13.3. Some of the standard Silverlight controls

Using Controls

As you might expect from their WPF inheritance, the Silverlight controls are attractive and easy to use. Figure 13.4 shows a TextBox with a Button and a TextBlock.

A TextBox with Button and TextBlock

Figure 13.4. A TextBox with Button and TextBlock

Using and configuring the controls from code is splendidly simple. Listing 13.5 shows a Button and a TextBox inside a horizontally oriented StackPanel. When the Button is clicked, or you press Enter, a message is set on the TextBlock.

Example 13.5. A TextBox with a Button in a horizontal StackPanel

A TextBox with a Button in a horizontal StackPanel

Along with the standard controls, there is a set of extended controls that comes with Visual Studio Tools for Silverlight.[7] This set includes additional controls such as Calendar, DataGrid, DatePicker, TabControl, and so on.

Tip

In addition to the standard and extended controls, Microsoft has a Code-plex project (available with source code and tests, under the same open source license as IronPython) called Silverlight Toolkit.[8]

The toolkit is a collection of controls and utilities for Silverlight, including TreeView, DockPanel, and charting components.

Using the Extended Controls

Visual Studio Tools for Silverlight comes with a lot more than just a new set of controls. It also includes additional assemblies for working with JSON, XML, and so on (don’t use the outdated version of IronPython it includes, though!). What we’re about to cover is just as relevant for using these other assemblies as it is for the extended controls.

The two assemblies that implement the extended controls are

  • System.Windows.Controls.dll

  • System.Windows.Controls.Data.dll

As with using other assemblies from IronPython, in order to use them we need to add a reference to them with clr.AddReference.

import clr
clr.AddReference('System.Windows.Controls')

Table 13.1 lists the Silverlight controls.

Table 13.1. Silverlight controls

Border

DatePicker

MultiScaleImage

Slider

Button

Grid

OpenFileDialog

StackPanel

Calendar

GridSplitter

Panel

TabControl

Canvas

HyperlinkButton

PasswordBox

TextBlock

CheckBox

Image

ProgressBar

TextBox

ComboBox

InkPresenter

RadioButton

ToggleButton

ContentControl

ListBox

RepeatButton

ToolTip

DataGrid

MediaElement

ScrollViewer

UserControl

It will be self-evident what most of these controls are for from their names (one of the advantages of consistent naming schemes). One that may not be familiar is MultiScaleImage.[9] This control is for displaying multiresolution images using Deep Zoom. It allows the user to zoom and pan across the image.

These assemblies extend the System.Windows.Controls namespace, so the extended controls are imported from the same place as the standard controls. Of course, you can use the extended controls from XAML as well as from code.

Extended Controls from XAML

Using the extended controls from XAML requires us to tell the XAML loader where to find the classes that correspond to the XAML elements. We do this by adding extra xmlns declarations to the first tag in the XAML. Listing 13.6 is a part of an XAML file that uses several of the extended controls (Calendar, DatePicker, and GridSplitter).

Example 13.6. XAML for a UI using the extended controls

<UserControl
   x:Class="System.Windows.Controls.UserControl"
   xmlns="http://schemas.microsoft.com/client/2007"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:c="clr-
     namespace:System.Windows.Controls;assembly=System.Windows.Controls">
   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition />
      </Grid.ColumnDefinitions>

      ...
        <StackPanel Grid.Row="0" Grid.Column="1"
         Margin="20,20,10,20">
          <ContentControl Content="ContentControl"
           Margin="5"/>
          <Button Content="ToolTipped Button" Margin="5"
           ToolTipService.ToolTip="Some ToolTip"/>
          <ToggleButton Content="ToggleButton" Margin="5"/>
          <c:DatePicker Margin="5"/>
          <c:Calendar Margin="5"/>
       </StackPanel>

       <c:GridSplitter Grid.Column="1"
        Width="5" HorizontalAlignment="Left"
        VerticalAlignment="Stretch"
     ShowsPreview="True" />
  </Grid>
</UserControl>

To load this XAML from IronPython, like the example in listing 13.3,[10] we don’t need to explicitly add references to the assemblies containing the controls; the XML namespace declarations do this for us. The xmlns:c declaration also declares a prefix (c) that we must use to reference controls from the System.Windows.Controls assembly. For example, the DatePicker control is referenced with c:DatePicker in the XAML.

Once this XAML is loaded, the results look like figure 13.5.

A user interface loaded from XAML

Figure 13.5. A user interface loaded from XAML

If you don’t like the standard silver theme, you can use a new theme for all the controls in an application. In discussing the extended controls, there’s an important point that we have glossed over.

Packaging a Silverlight application

If you look in the source code for the example applications that use the extended controls, you’ll see a file that we haven’t yet discussed, AppManifest.xaml. This is an XML file that tells Silverlight not only what assemblies your application uses but also which one provides the entry point. The reason it isn’t included in the earlier examples is that Chiron can autogenerate it for applications that use no assemblies beyond the standard IronPython/DLR ones.

When you deploy a typical dynamic application, the xap file contains the following:

  • app.py—your main Python file

  • The IronPython assemblies (dlls)

  • An XAML (XML) manifest file

  • Any additional Python modules, XAML files, assemblies, or resources your app uses

Listing 13.7 shows the manifest file needed for a basic dynamic application. You can either copy and paste this into your applications or let Chiron create it for you.

Example 13.7. The XML manifest file for an IronPython application

<Deployment
 xmlns="http://schemas.microsoft.com/client/2007/deployment"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  RuntimeVersion="2.0.31005.00"
  EntryPointAssembly="Microsoft.Scripting.Silverlight"
  EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication">
  <Deployment.Parts>
   <!-- Add additional assemblies here -->
   <AssemblyPart Source="Microsoft.Scripting.dll" />
   <AssemblyPart Source="Microsoft.Scripting.Core.dll" />
   <AssemblyPart Source="IronPython.dll" />
   <AssemblyPart Source="IronPython.Modules.dll" />
   <AssemblyPart Source="Microsoft.Scripting.ExtensionAttribute.dll" />
   <AssemblyPart Source="Microsoft.Scripting.Silverlight.dll" />
 </Deployment.Parts>
</Deployment>

Requiring XML manifest files may seem like unnecessary overhead for dynamic applications, but they serve a very serious purpose: making Silverlight applications fit for the enterprise.

It is interesting to note the EntryPoint attribute in the top-level Deployment element. This is how Silverlight knows how to execute a dynamic application, and in fact a dynamic application is just a normal C# application as far as Silverlight is concerned. Microsoft.Scripting.Silverlight.dll actually does the magic for us.

To use additional assemblies in your applications, including the extended controls, you need to include them in the manifest. This has a serious downside, even if your application is only a single Python file a few kilobytes in size; the resulting xap file will need to contain the IronPython assemblies and be several hundred kilobytes in size.

Fortunately, there is a way around this. If instead of just specifying the assembly name as the Source attribute for the assemblies, you specify an absolute URL, the assemblies will be fetched separately rather than being expected inside the xap file. If several of your applications share the same assemblies, then the browser can cache them, and your application can shrink back down to a more manageable size.[11]

We’ve covered all the basics now, and in fact you have everything you need to start writing Silverlight applications; everything else is mere detail. The best way of learning these details is to use them, so in the next section we look at a more substantial Silverlight application: a Twitter client.

A Silverlight Twitter client

Deploying applications through the web has the massive advantage of deploy once, use anywhere, but with JavaScript it means coping with the pain of cross-browser issues and slow client-side performance. With Silverlight we can bring our structured application programming techniques and adapt them for the browser, often using the same libraries that we use on the desktop. In this section we look at a basic Silverlight Twitter client written in IronPython,[12] shown in figure 13.6.

An IronPython Silverlight Twitter client

Figure 13.6. An IronPython Silverlight Twitter client

This example consists of about 600 lines of code in total. We won’t go through all the code line by line, but through it we can explore the following aspects of working with Silverlight:

  • Cross-domain policies and tips for debugging Silverlight programs

  • Creating user interfaces

  • Making web requests from Silverlight

  • Using XML from the Twitter API

  • Threading and asynchronous callbacks and dispatching onto the UI thread

  • Storing data in the browser with IsolatedStorage

  • Timers

In some ways a Twitter client is a difficult example for Twitter. It means working with data fetched from an external server, and we quickly run into problems with making cross-domain calls from Silverlight. We start by looking at what you can and can’t do.

Cross-domain policies

When writing web client applications with JavaScript you can make calls back to the server with XMLHttpRequest, or its equivalent. This uses cookies and authentication that the browser has cached for the site you are accessing. If JavaScript on a web page could access any domain, this would be a security hole, as applications could access any site effectively logged in as you. To prevent this, the browser restricts JavaScript to allow only requests to the same domain as the current web page, blocking cross-domain calls.[13]

In theory, Silverlight doesn’t have the same problems as JavaScript (it does use the browser networking stack under the hood but works with it directly). However, for nebulous security reasons, Silverlight still applies some restrictions. Silverlight allows your applications to make some cross-domain calls, so long as the domain you are accessing allows it.

The way a domain allows calls from a Silverlight application is by providing a clientaccesspolicy.xml file at the top level of the domain.[14] Listing 13.8 is an example client-access policy file that allows access to the whole domain and from any referring domain.

Example 13.8. A clientaccesspolicy.xml file to allow cross-domain calls into a website

A clientaccesspolicy.xml file to allow cross-domain calls into a website

Of course, this is most useful when the domain you want to access is under your control. If it isn’t your server, the typical solution is to proxy the service you want to access. This means that your application queries a server that is under your control, and the server makes the query and returns the result.

Unfortunately, although Twitter exposes a nice and simple API for clients to use, it doesn’t have the necessary client-access policy allowing us to query it from Silverlight.[15] To make the client work, we’ve implemented a simple Python proxy server that runs locally. Because we will be using Chiron, which serves on port 2060, the local proxy server runs on port 9981. You can start it by executing the following command from the directory containing the example code for this section:

python simple_proxy.py

As far as Silverlight is concerned, calls to this server are still cross domain, so our proxy server has to provide a valid client access policy when /clientaccesspolicy.xml is requested. Before we look at the substance of the application, let us share a tip for debugging Silverlight applications.

Debugging Silverlight applications

The normal first resort for tracking down bugs in Python programs is the judicious use of print statements. This technique doesn’t work in Silverlight, and to make it worse, the error messages can be extremely cryptic.[16] There is a way around this, though. Python allows us to override standard output with a custom writer. Listing 13.9 shows how to create a custom writer and set it on sys.stdout.[17]

Example 13.9. Diverting standard out to an HTML text area

Diverting standard out to an HTML text area

When a print statement is executed, the write method of our custom writer is called. This looks up the HTML element with the id debugging and sets the text on it. We fetch this element from the HtmlDocument,[18] which we get hold of via System.Windows.Browser.HtmlPage.Document. Figure 13.7 shows the textarea with the debugging output from running the Twitter client.

The HTML textarea containing the output of print statements from the Twitter client

Figure 13.7. The HTML textarea containing the output of print statements from the Twitter client

Silverlight provides other ways to interact with the browser DOM, which we look at shortly. First we look at parts of the Silverlight UI model that we haven’t already seen.

The user interface

The main user interface for the Twitter client is a Border containing a StackPanel, two classes we worked with in the chapter on WPF. The main application is contained in a class called MainPanel that inherits from StackPanel. The rest of the user interface is laid out by nesting StackPanels in new borders where necessary. From the wonderful color scheme I chose, you should be able to see the different elements nested inside each other.

When you first log in, you are presented with the login panel shown in figure 13.8.

The Silverlight Twitter client login panel

Figure 13.8. The Silverlight Twitter client login panel

This is a horizontally oriented StackPanel (in a border) with nested vertical StackPanels containing the username/password textboxes, the login button, a check box, and a textblock for messages.

We haven’t yet used the CheckBox, but like the other user interface components, it’s very simple. Listing 13.10 shows the configuration of the login button and the Remember me check box in the login panel.

Example 13.10. Configuring the Button and CheckBox in the login panel

Configuring the Button and CheckBox in the login panel

The textbox for the username is a straightforward TextBox, but we can’t use that for entering the password because we don’t want it to be visible while it is being typed. Thankfully, a password textbox is one of the standard controls. The difference between the PasswordBox API and a straight TextBox is that instead of setting and fetching the Text property, we use the Password property.

Once the user has logged in, the tweets are fetched from the Twitter API and displayed inside a grid.

The Grid, the ScrollViewer, and the HyperlinkButton

The grid is inside a ScrollViewer, so that all the Twitter messages can be seen. The grid has two columns. The Twitter usernames are displayed in the left column as clickable links, with the Twitter messages in the right column (these are all visible in figure 13.6). The code that does all this is in listing 13.11.

Example 13.11. Creating and populating the main grid for the Twitter client

Creating and populating the main grid for the Twitter client

There are several details worth noticing in this code. Using color brushes in Silverlight is slightly different from in WPF. Instead of using something like Brushes.White to set the background, we construct a brush with SolidColorBrush(Colors.White).

The HyperlinkButton is placed at the left and the center (vertically) by setting horizontal and vertical alignment. By default the HyperlinkButton opens in the same window. You can specify that the link should open in a new window by setting HyperlinkButton.TargetName = "_blank".[19]

Because the Twitter messages are often longer than the width of the grid, we need to ensure that the TextBlock wraps the text by setting the TextWrapping property. We’ve covered the important parts of the user interface, so now we look at how we access server resources from Silverlight.

Accessing network resources

The basic class for accessing network resources from Silverlight is the WebClient, which can be used for both GET and POST requests. It has an asynchronous API, so you configure it to fetch an API and provide callbacks to handle the response.

The WebClient

The Twitter API is extremely easy to work with,[20] by virtue of its XML or JSON over REST interface. To verify login details or fetch the latest messages for a Twitter user, you simply fetch a URL from the Twitter API (using basic authentication with the user credentials), which then returns either XML or JSON, depending on which format you asked for in the URL.

Because of the cross-domain policy, we can’t access the Twitter domain, so we can leave the proxy server to handle the authentication. The twitter_proxy module wraps the WebClient in a Fetcher class, shown in listing 13.12, which takes a callback function along with the username, the password, and the action to be performed.

Example 13.12. The Fetcher class for downloading web resources with WebClient

The Fetcher class for downloading web resources with WebClient

It is called like this:

Fetcher(username, password, 'fetch', callback)

If you watch the local proxy for the first time this is called, you will see that before fetching the requested URL, Silverlight asks for /clientaccesspolicy.xml. If the server doesn’t respond to this, then an exception will be raised.

Because we are putting the action, username, and password as parameters into the GET string, they need to be URL encoded. This is the task of the HttpUtility class, which also has a corresponding UrlDecode method that could be useful if you ever want to look at the query parameters of the current URL.[21]

When the response is available, the DownloadStringCompleted event fires and the completed method is called. If the request completes successfully, then our original callback is called with the response as a string.

Parsing the XML from Twitter

The main client application takes the results (a string of XML) and needs to parse this into messages suitable for populating the grid. Because Silverlight comes with the XmlReader (and the associated classes and enumerations), we can actually reuse the XmlDocumentReader class from chapter 5 to parse the response.

Literally the only changes we needed to make to this class were to add a reference to the System.Xml assembly and the addition of a single line to handle the XML declaration that we didn’t need for MultiDoc. Since we have our XML as a string, we can pass a TextReader into the call to XmlReader.Create instead of a stream. Just as in the desktop .NET framework, XmlReader lives in the System.Xml assembly, so we need to add a reference to it before we can import from it.

The XML that Twitter returns when we ask for a user’s messages has a top-level statuses element containing a series of status blocks nested in it. Each one of these has various elements that describe the message and the user who posted it. For our basic client, we are interested in only the body of the message itself and the name of the user who posted it. With the event-based parsing of XmlReader, we need only define handler methods for the parts of the Twitter XML that we are using, ignoring the rest. You can see the (brief) code that does this in the twitter module. It returns a list of dictionaries, each dictionary with name and text members, ready for displaying in the grid.

Posting With WebClient

For posting a Twitter message, we need to make a POST request. We can also use the WebClient for this, using the UploadStringAsync method instead. Listing 13.13 shows the Poster class, which posts Twitter messages.

Example 13.13. Making POST requests with HttpWebRequest

Making POST requests with HttpWebRequest

This Poster class wraps up the API, so that when we instantiate Poster with the data for the post and a callback function, it will make the request, and the callback will be called with the results once the request is complete.

As is common in .NET, asynchronous callbacks often happen on a different thread than the one you create them from, and we need to handle this.

Threads and dispatching onto the UI thread

Like WPF and Windows Forms, the Silverlight user interface runs within a single main thread. Any operations that interact with user interface elements, or access the browser DOM, should be done from this thread.

Fortunately this is straightforward. WPF uses dispatchers to invoke delegates onto the UI thread, and Silverlight includes a cut-down version of this system. Silverlight provides a single dispatcher, which is accessible via the Dispatcher property on user interface components. From IronPython, functions that we pass into the Dispatcher.BeginInvoke method are invoked onto the main thread.

A lot of the core threading classes from .NET are available in Silverlight. Listing 13.14 creates a new thread, and after a brief pause it uses the dispatcher to invoke a function that changes the text on a textblock.

Example 13.14. Using the dispatcher to modify the user interface from another thread

Using the dispatcher to modify the user interface from another thread

When this code is run, the textblock displays the message “Created on thread 1.” After three seconds, this changes to show the thread ID of the new thread that we created.

This is simple enough, but there might not always be a convenient user interface element available to dispatch on from code that needs it. The Twitter client has a dispatcher module that does this for us. When the UI is first created, the main application (in app.py) calls the function SetDispatcher. The dispatcher module also exports two other functions, Dispatch and GetDispatchFunction. These either immediately dispatch a function for us or turn a function into one that is dispatched when it is called (which is useful for creating dispatched callbacks). Dispatch and GetDispatchFunction are used throughout the Twitter client, where code might interact with the user interface.

A common need is to have some event regularly occur at a timed interval. We use this in the Twitter client to fetch the latest tweets every 60 seconds. We can do this with the DispatcherTimer. Listing 13.15 uses a DispatcherTimer to update a counter in a textblock.

Example 13.15. Using a DispatcherTimer for timed events on the UI thread

from System.Windows.Threading import DispatcherTimer
from System import TimeSpan

text = TextBlock()
text.Text = "Nothing yet"
text.FontSize = 24
root.Children.Add(text)

counter = 0
def callback(sender, event):
   global counter
   counter += 1
   text.Text = 'Tick %s' % counter

timer = DispatcherTimer()
timer.Tick += callback
timer.Interval = TimeSpan.FromSeconds(2)
timer.Start()

When this code is executed, the Tick event fires every two seconds and increments the counter. Because Tick is executed on the UI thread, there is no need for us to explicitly dispatch the callback. If we didn’t need to interact with the user interface, then we could use the System.Threading.Timer class instead.

Another of the Silverlight APIs that the Twitter client uses is the isolated storage system, for storing user login details in the browser.

IsolatedStorage in the browser

Isolated storage provides a mechanism for applications to store data in the browser cache. The intent of this is to provide a temporary cache for applications or for storing configuration information. Data stored there does persist but is destroyed if the user clears the browser cache. With this in mind, the default limit per application is 100 kilobytes of storage. You can request more, which will present the user with a dialog requesting permission to increase the limit for this application.

Isolated storage provides a filesystem-like mechanism that we access through the System.IO.IsolatedStorage namespace. We can list the files (or directories) stored and load, save, or delete files.

In the Twitter client this is used for the user login credentials. If the Remember check box is checked when the user logs in, then the username and password are saved in a file.[22]

When the application is first loaded, if that file exists in the application storage, then the file is loaded and the username and password textboxes are populated from it.

The basis of using isolated storage from our applications is to get a data store by calling IsolatedStorageFile.GetUserStoreForApplication(). This returns the IsolatedStorageFile instance for the current application. Listing 13.16 shows three functions to load and save files listed in the data store and to list the contents.

Example 13.16. Using the Silverlight isolated storage

Using the Silverlight isolated storage

The data store isn’t a flat file system; we can create subdirectories and work with those as well. The store has many useful methods;[23] one of the more important ones is TryIncreaseQuotaTo to request an increase in the amount of storage available. This method can be called only from inside the event handler of a control such as a button. You pass in the amount of space you want (in bytes), which presents a dialog to the user to approve the request. The method returns a Boolean indicating whether the request succeeded or not. The following snippet of code shows a request to double the amount of storage for an application:

from System.IO.IsolatedStorage import IsolatedStorageFile
store = IsolatedStorageFile.GetUserStoreForApplication()
space = store.AvailableFreeSpace
success = store.TryIncreaseQuotaTo(space * 2)

Figure 13.9 shows the dialog presented to a user when this code runs on Safari under Mac OS X.

Requesting to increase the storage for a Silverlight application

Figure 13.9. Requesting to increase the storage for a Silverlight application

If you attempt to execute this code outside a user-interface event handler, then it will fail and return False without showing the dialog.

We’ve now seen that Silverlight contains all the necessary ingredients for creating serious applications that live on the web. Writing web applications is different from programming for the desktop. Although the CoreCLR lessens that difference, the key to creating effective applications is understanding the difference, and that means being able to make good use of the important Silverlight APIs such as IsolatedStorage.

There are a couple more important Silverlight APIs that we have only skirted around the edges of; these are working with videos and the browser DOM.

Videos and the browser DOM

Despite its diminutive download, Silverlight packs in a great deal. The intended use for Silverlight spans the gamut of games, applications, and media streaming, so along with the .NET Base Class Libraries it comes with APIs specific to these tasks. Some of these are the adapted version of their complementary WPF class in the desktop framework. Others, like those for working with the browser DOM, are new to Silverlight. In this final section of the chapter, we use two more of the core Silverlight APIs.

The MediaElement video player

The media capabilities are provided in part through the MediaElement class. Figure 13.10 shows a video as part of a Silverlight canvas.

The MediaElement class in action

Figure 13.10. The MediaElement class in action

This class is a control, and like the other controls, it lives in the System.Windows.Controls namespace. Having instantiated it, you specify a data source as a Uri, as in listing 13.17.

Example 13.17. Using the MediaElement video control

Using the MediaElement video control

As always, the MediaElement has methods, properties, and events that we haven’t used here. The methods to start and stop playing are Play and Pause, but the video will start playing as soon as it has downloaded events, so it is unnecessary. Other useful properties are Position, which we can set with a TimeSpan object, and both Width and Height to scale the video. As with setting the Width and Height scales, setting one automatically adjusts the other.

In addition to using it directly, we can use the MediaElement as the source for a VideoBrush, which we can use as the foreground mask on another control or to fill a shape, which we then transform or animate. Listing 13.18 shows how to set a video as the foreground mask for the text in a textblock.

Example 13.18. Setting a VideoBrush with a video on a TextBlock

Setting a VideoBrush with a video on a TextBlock

MediaElement exposes an event called MediaEnded that fires when the video ends. We make the video loop by hooking up a restart function to this event that restarts the video.[24]

Figure 13.11 shows the results of setting a VideoBrush on a TextBlock from IronPython.

A VideoBrush showing through the text on a TextBlock

Figure 13.11. A VideoBrush showing through the text on a TextBlock

An important aspect of Silverlight that we have touched on only briefly is working with the browser and the Document Object Model.

Accessing the browser DOM

We flirted with the DOM when we looked at diverting standard output, so that debugging print statements would appear in an HTML text area. We access HTML elements in the page through the Document property on System.Windows.Browser.HtmlPage. We can access elements by using their id as the attribute name on Document, which does a dynamic lookup (one of the joys of working with a dynamic language). This returns an element object,[25] with which we can do many useful things.

Listing 13.19 shows how to set the innerHTML on an element, using GetElementById as an alternative way to fetch elements, modifying style rules on an element with SetStyleAttribute, plus setting properties with the SetProperty method.

Example 13.19. Interacting with DOM elements from inside Silverlight

from System.Windows.Browser import HtmlPage

document = HtmlPage.Document
element = document.some_element
element.innerHTML = "Some <em>HTML</em>."

element2 = document.GetElementById('another_element')
element2.SetStyleAttribute('border', 'solid black 2px')

element3 = document.GetElementById('some_textbox')
element3.SetProperty('disabled', True)
element3.value = 'Text in a textblock'

As well as using innerHTML we could also use innerText. Because we have access to these attributes, we can use all our favorite AJAX tricks from Silverlight.

Some properties on HTML elements require us to use the GetProperty and SetProperty methods rather than simple attribute access. Fetching the disabled property of a textblock as an attribute will return a string instead of a Boolean (and setting it directly with a string or a Boolean doesn’t work), so you should use the getter and setter methods instead.

In addition to access to the HTML elements, we can also set event handlers and interact with JavaScript. Listing 13.20 does three things:

  1. Adds a mythical DoSomething Python function as an event handler for a button in the HTML with the id some_button.

  2. Calls a JavaScript function (function_name) with a string argument.

  3. Creates a JavaScript object (XmlHttpRequest) and invokes methods on it (which executes a synchronous GET request JavaScript and allows us to retrieve result).

Example 13.20. DOM events and JavaScript from Silverlight

DOM events and JavaScript from Silverlight

These simple tricks make it possible to create hybrid applications with both JavaScript and Silverlight, which communicate with each other.

There is one final way of interacting with the browser that we need to mention, and it is a bit more indirect. If we give the Silverlight control an id when we embed it in HTML, we can access it in the same way we have accessed any other element by id. A better way to get a reference to the current Silverlight control is

System.Windows.Application.Current.Host

The SilverlightHost object itself is not particularly interesting, but it exposes a Content subobject that is. This has members[26] like ActualHeight and ActualWidth that tell us the real size of the Silverlight control within the web page. IsFullScreen will not only tell us if the Silverlight control is operating in full-screen mode, but if set to True from a button event handler it will also switch the plugin to full-screen mode. Most important, though, the content object also has a ReSized event, which fires when the control changes size. If, instead of creating the Silverlight control with a fixed size in the HTML embedding code, we let the browser size it, then we can respond appropriately (perhaps re–lay out the UI) when the control is resized.

We’ve had only one chapter to learn about Silverlight, but it’s clear that this framework has a huge amount of potential. It’s particularly exciting that programming it from IronPython is such a good experience; long may the reign of dynamic languages on the web continue.

Summary

Rich internet applications are already an important part of the internet revolution, and they’re only becoming more important. Silverlight is one of the new frameworks that allow programmers to really take advantage of client-side processing power in web applications.

One of the tools we used when writing this chapter was the Silverlight IronPython Web IDE.[27] This allows us to experiment with the Silverlight APIs by executing code in the browser, and it has several examples preloaded, including some topics we didn’t have space for here.

There’s a lot we haven’t had time to use. We haven’t looked at loading XAML or initializing controls from XAML or animations with the StoryBoard. Because the Silverlight user interface model is based on WPF, these features are very similar to using them from WPF, and this includes using Expression Blend[28] as a design tool. With clever structuring, your desktop and online versions of your applications could share a lot of their code and XAML.

This is one of the best things about working with Silverlight; much of our existing knowledge about .NET and IronPython is directly applicable. This even includes extending IronPython with C# and embedding IronPython into C# or VB.NET applications, which are the focus of the next part of this book.



[3] And of course all benchmarks are misleading, in the same way that all generalizations are wrong.

[4] ECMA 3 is the standard that covers JavaScript 2, the version in use by most current browsers.

[5] Yes, you can use Silverlight to create really annoying adverts that everybody hates.

[7] Silverlight Tools for Visual Studio 2008 works with Visual Studio 2008 or Visual Web Developer 2008 Express. The tools are linked to from http://silverlight.net/GetStarted/default.aspx.

[10] This XAML has a UserControl as the root element, so you’ll need to modify listing 13.3 to create a UserControl instead of a Canvas. This is shown in ControlsExample2 in section 13.1.3 of the downloadable source code.

[11] At some point a mechanism like the Global Assembly Cache will be implemented for Silverlight, but there isn’t one yet.

[12] The user interface is deliberately gaudy so that you can visually see the way the UI elements are nested inside each other.

[13] There are various ways around this restriction from JavaScript, of course. For example, the jQuery library provides an API for accessing web services across domains.

[14] Silverlight also supports a subset of the crossdomain.xml schema used by Flash. See the following page for details: http://msdn.microsoft.com/en-us/library/cc197955(VS.95).aspx.

[15] I (Michael) really wanted to write a Twitter client though, as I’ve been having a lot of fun with it recently. You can follow me at http://www.twitter.com/voidspace.

[16] This was to save space in the Silverlight runtime and may be improved in a future version of Silverlight.

[17] The actual code in the example is slightly different. We shouldn’t modify the browser DOM from anything other than the UI thread, so it contains code to dispatch the write in case we want to print from an asynchronous callback off the main thread. This topic is covered in more detail later in the chapter.

[19] Although currently that stops the link button working altogether on Safari on the Mac!

[21] This is made available through System.Windows.Browser.HtmlPage.Document.DocumentUri.

[22] In a plain text file, but this is not recommended for production systems storing sensitive user data like passwords!

[24] In my initial experiments, I forgot to add the MediaElement as well and then spent an hour trying to work out why it wasn’t working.

[28] To use Expression Blend with Silverlight you’ll need version 2.5 or later.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.160.181