How to wire up a timer-triggered WPF event handler in IronPython

In the last installment of my little series on turning Internet feeds into TV feeds, I had decided to use IronPython to fetch data from the Internet, but C# to drive the WPF (Windows Presentation Foundation) application whose display my local public access TV station will broadcast. This division of labor between C# and IronPython arose because the XAML that drives the display needs to be refreshed periodically, and I didn’t know how, in IronPython, to properly delegate a timer-based event handler for WPF.

In this comment, Michael Foord, author of IronPython in Action and a major contributor to the IronPython Cookbook, showed me the way. Thanks Michael!

Based on his example, I’ve rewritten the C# program shown here as the IronPython script shown below.

I haven’t yet decided which version to deploy, but I’m leaning toward the IronPython version. Not because it’s more concise. It isn’t, really. Nor because I feel any need to use the same language for both components of the solution — that is, the feed fetcher and the feed displayer. I don’t care about language uniformity for its own sake.

I am, however, thinking that the folks at the TV station may want to modify these programs themselves. They’re pretty simple, and there’s no reason they shouldn’t be able to tinker with them. From that perspective, code that can be modified with nothing more than a text editor will be more accessible than code which requires a compiler.

I’m reminded of my early days as a website operator, when I was always glad to discover that a third-party application was written in Perl, rather than in C. That meant I could, and sometimes did, tweak the application in ways that otherwise would have been difficult or even (lacking C source code) impossible.

The difference here, of course, is that all of the underlying machinery — XAML, WPF, and the entire .NET Framework — is exactly the same1 when approached from a scripting language like IronPython or a compiled language like C#. This ability to use common infrastructure from different langages — and from very different kinds of languages — has always seemed like a big deal to me, and still does.


1 The same, that is, modulo the kind of boundary-crossing issue that stumped me until Michael Foord pointed me to CallTarget0, the wrapper for creating a delegate in IronPython.


import clr

clr.AddReferenceByPartialName("PresentationCore")
clr.AddReferenceByPartialName("PresentationFramework")
clr.AddReferenceByPartialName("WindowsBase")
clr.AddReferenceByPartialName("IronPython")

from System import *
from System.Windows import *
from System.Windows.Markup import *
from System.Windows.Media import *
from System.Windows.Input import *
from System.Windows.Threading import *

from IronPython.Runtime.Calls import CallTarget0

def LoadXaml(filename):
  from System.IO import *
  from System.Windows.Markup import XamlReader
  f = FileStream(filename, FileMode.Open)
  try:
    element = XamlReader.Load(f)
  finally:
	f.Close()
  return element

class Scroller(Application):

  def tickhandler(self,sender,args):
    def update_xaml():
      self.window.Content = LoadXaml(self.xaml)
    self.timer.Dispatcher.Invoke(DispatcherPriority.Normal, 
      CallTarget0(update_xaml))

  def __init__(self):
    Application.__init__(self)
    self.xaml = "scroller.xaml"
    self.window = Window()
    self.window.Content = LoadXaml(self.xaml)
    self.window.WindowStyle = WindowStyle.None       # go fullscreen
    self.window.WindowState = WindowState.Maximized  # 
    self.window.Topmost = True                       #
    self.window.Cursor = Cursors.None                #
    self.window.Background = Brushes.Black           #
    self.window.Foreground = Brushes.White           #
    self.window.Show()
    self.timer = DispatcherTimer()
    self.timer.Interval = TimeSpan(0, 60, 0)         # refresh hourly
    self.timer.Tick += self.tickhandler              #
    self.timer.Start()                               #

Scroller().Run()
Posted in .

4 thoughts on “How to wire up a timer-triggered WPF event handler in IronPython

  1. This doesn’t quite work on IronPython 2.0 beta4:

    Traceback (most recent call last):
    File “\Projects\wpf.py”, line 15, in \Projects\wpf.py
    ImportError: No module named Calls

  2. Although its location has changed *again* in recent IronPython / DLR refactorings.

    Its latest dwelling place is: IronPython.Compiler

    You can still catch the import error to import it in a ‘cross-version’ way (although you will also need to add a reference to the ‘IronPython’ assembly in IP 2).

    Michael

  3. Pingback: Sandro

Leave a Reply