Last week I started looking into ways to Internet feeds into TV feeds. Although I did come up with a way to turn a data feed into a video file, that wound up being overkill. It turns out that the local station is willing to broadcast the signal from a computer display. To create that signal, several folks suggested using PowerPoint, but I found that its scrolling credits feature doesn’t accommodate really long lists of credits. So I decided to try XAML, the application markup language that works with Silverlight and the Windows Presentation Foundation (WPF), in concert with IronPython.

The plan was as follows. A long-running IronPython script periodically fetches the feed from a web service, interpolates the text into a XAML template that animates the crawl, and displays the XAML in a fullscreen white-on-black WPF window.

Here’s the XAML template:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Width="%s" Height="%s">
<TextBlock xml:space="preserve" FontSize="%s" Margin="%s,%s,0,0"
  FontFamily="Arial">
    <TextBlock.RenderTransform>
      <TranslateTransform x:Name="translate" />
    </TextBlock.RenderTransform>
  <TextBlock.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
      <BeginStoryboard>
        <Storyboard RepeatBehavior="Forever">
          <DoubleAnimation From="%s" To="-%s"
            Storyboard.TargetName="translate"
            Storyboard.TargetProperty="Y"
			Duration="00:%s:%s" />
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
   </TextBlock.Triggers>
<Run>
<![CDATA[
%s
]]>
</Run>
</TextBlock>
</Grid>

Some of the values depend on the number of items in the feed, so the script interpolates those values into the template. Then it formats the feed and plugs the formatted text into the template’s CDATA section. The formatted text looks like this:

EVENTS FOR MON JUN 30 2008 FROM THE ELMCITY.INFO CALENDAR

06:00 AM:  lap swim (ymca)

07:00 AM:  AA: On Awakening Group (eventful: Keene Unitarian
      Universalist Church)

After generating the XAML, the IronPython script fires up an Application object, creates a window, loads in the XAML to start the crawl, and sets a timer to refresh the XAML.

I ran into a snag when I tried to set that timer, though. There are a few different timers you might imagine using in this context, including Python’s own timer object and various timers available in the .NET Framework. All but one of these, however, will complain about invalid cross-thread access when you try to update the application’s user interface from a timer event handler.

The right timer to use, it turns out, is .NET’s System.Windows.Threading.DispatcherTimer. But when I tried it, I ran into another snag. In C#, you create a WPF-friendly timer like so:

DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new EventHandler(event_handler);

event_handler is a method, but EventHandler returns a delegate that encapsulates that method. I couldn’t find a straightforward way to create a delegate, and do that encapsulation, in IronPython.

If you know how, I’d love to hear about it. Then again, it really doesn’t matter. Logically this program has two loosely-coupled parts. The engine part reads the feed from a web service and formats it as XAML. It can be a Python script that runs on a scheduled basis to fetch and format the feed.

The user interface part loads, displays, and then periodically refreshes the XAML. It can be a little C# program that runs forever, displays the animation, and refreshes the data, like so:

using System;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Input;
using System.Windows.Media;
using System.IO;
using System.Windows.Threading;

namespace CalendarCrawl
{
  public class CalendarCrawler
  {
    static private Application app = new Application();

    static private StreamReader getXaml()
    {
    StreamReader sr = new StreamReader("WPF.xaml");
    return sr;
    }

    [STAThread]
    static public void Main(string[] args)
    {
        Window win = new Window();
        win.WindowStyle = WindowStyle.None;           // go fullscreen
        win.WindowState = WindowState.Maximized;      // go fullscreen
        win.Topmost = true;                           // go fullscreen
        win.Cursor = Cursors.None;                    // go fullscreen
        win.Content = XamlReader.Load(getXaml().BaseStream);
        win.Background = Brushes.Black;
        win.Foreground = Brushes.White;
        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 1, 0);       // every hour
        timer.Tick += new EventHandler(eventHandler); // wire up handler
        timer.Start();
        app.Run(win);
    }

    static private void eventHandler(Object sender, EventArgs args)
    {
        app.Windows[0].Content = XamlReader.Load((getXaml().BaseStream));
    }

  }
}

It was odd how reluctantly I came to this division of labor. Evidently I still need to remind myself that in a world of loosely-coupled applications and services, when you need to get something done, There Is More Than One Way To Do It.

Here’s another way. If the engine doesn’t have to talk to the .NET Framework’s WPF machinery, there’s no need to use IronPython. Any flavor of Python makes a handy tool for talking to RESTful web services, wrangling text, and interacting with the file system.

Here’s yet another way: A Silverlight version of the user interface. It’s nice to know that option is available. However, I’m leaning toward the C# version. The target machine is Vista, it already has .NET and WPF, why use a long-running browser instance just to host this tiny little thing?

One final point is worth mentioning. XAML is really just another source language for the .NET runtime and framework, like C# and IronPython and others. You can, for example, create an application window by writing a Window tag in XAML markup, and specifying parameters as attributes. Or you can do it by invoking System.Windows.Window from IronPython or C# or another .NET language, and specifying parameters in code. The boundary between markup and code is very fluid, and you can draw the line for reasons of convenience, maintainability, and taste. It’s a very flexible system, and it becomes even more flexible when you can use a dynamic language like Python to generate the XAML, the code, or both.