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.
I don’t have a windows box handy to test this, but shouldn’t any IronPython function implicitly typecast to a delegate type successfully?
> shouldn’t any IronPython function implicitly
> typecast to a delegate type?
For reasons that I’m probably not even qualified to understand, it seems not to work that way.
To solve the delegate problem you need to use ‘CallTarget0’ provided in the IronPython project.
I can’t off the top of my head remember where you import it from (it is different between IronPython 1 & 2 I believe) – but I’m pretty sure it is on the IronPython Cookbook.
Michael
> To solve the delegate problem you need to
> use ‘CallTarget0′ provided in the
> IronPython project.
Ah. Thanks Michael! In this case, I’m actually thinking of leaving things as they are because of a hunch that a long-running C# program will be less likely to leak than a long-running IronPython program. But that’s just an unfounded hunch, I have no evidence. Curious what you think.
We haven’t found that IronPython leaks memory. The .NET garbage collection is very impressive, and IronPython of course uses it seamlessly.
The *only* caveat is that IronPython does keep a reference to functions that you hook up as event handlers – so that if you want to dispose of GUI components like forms these handlers can sometimes keep them alive.
Explicitly unhooking the event handlers solves the problem – and I think this may have been fixed between IronPython 1 and 2.
Michael
> The *only* caveat is that IronPython does
> keep a reference to functions that you
> hook up as event handlers
Gotcha. Thanks again Michael!
Now on to the /really/ tricky part of this project. How to establish and maintain a list of trusted feeds for community events. Or actually, multiple related lists, since the community standards upheld by local TV and those upheld by a community website will differ slightly.
And then, of course, how to educate people as to how and why to publish and register feeds.
Abstract: We describe the LHCb detector simulation application (Gauss) based on the Geant4 toolkit. The application is built using the Gaudi software framework, which is used for all event- processing applications in the LHCb experiment. The existence of an underlying framework allows several common basic services such as persistency, interactivity, as well as detector geometry description or particle data to be shared between simulation, reconstruction and analysis applications. The main benefits of such…