Passion * Technology * Ruthless Competence

Thursday, July 09, 2009

Syntax Highlighting TextBoxes in WPF – A Sad Story

One of the big new features in VS 2010 is the WPF based editor. With it, you can build all sorts of cool stuff like control the visualization of XML doc comments, change how intellisense looks, even scale the size of text based on the location of the caret. Huzzah for the WPF Visual Studio editor!

However, as wicked awesome as the new editor is, AFAIK it’s not going to be released as a separate component. So while the PowerShell, Intellipad and other teams inside Microsoft can reuse the VS editor bits, nobody else can. So if you want to do something like embed a colorizing REPL in your WPF app, you’ll have to use something else.

I’ve thought about putting a WPF based UI on top of ipydbg (though now I’d probably use the new lightweight debugger instead). So I downloaded John’s repl-lib code to see how he was doing it. Turns out his REPL control is essentially a wrapper around WPF’s RichTextBox control. It works, but it seems kinda kludgy. For example, the RichTextBox supports bold, italics and underline hotkeys, so John’s REPL does too. Though it is possible to turn off these formatting commands, I decided to take a look at modifying how the plain-old TextBox renders. After all, WPF controls are supposed to be lookless, right?

Well, apparently not all the WPF controls are lookless. In particular to this post, the TextBox is definitely NOT lookless. It looks like the text editing capabilities of TextBox are provided by the Sys.Win.Documents.TextEditor class while the text rendering is provided by the Sys.Win.Controls.TextBoxView class. Both of those classes are internal, so don’t even think about trying to customize or reuse them.

The best (and I use that term loosely) way I found for customizing the TextBox rendering was a couple of articles on CodeProject by Ken Johnson. Ken’s CodeBox control inherits from TextBox and sets the Foreground and Background to transparent (to hide the result of TextBoxView) and then overloads OnRender to render the text with colorization. Rendering the text twice – once transparently and once correctly – seems like a better solution than using the RichTextBox, but it’s still pretty kludgy. (Note, I’m calling the TextBox design kludgy – Ken’s code is a pretty good work around).

So if you want a colorized text box in WPF, your choices are:

  • Build your own class that inherits from RichTextBox, disabling all the formatting commands and handling the TextChanged event to do colorization
  • Build your own class that inherits from TextBox, but set Foreground an Background colors to transparent and overload OnRender to do the visible text rendering.
  • Use a 3rd party control. The only one I found was the AqiStar TextBox. No idea how good it is, but they claim to be a true lookless control. Any other syntax highlighting WPF controls around that I don’t know about?
Posted By Harry Pierson at 3:18 PM Pacific Daylight Time

Monday, November 24, 2008

IronPython and WPF Part 5: Interactive Console

One of the hallmarks of dynamic language programming is the use of the interactive prompt, otherwise known as the Read-Eval-Print-Loop or REPL. Even though I’m building a WPF client application, I’d still like to have the ability to poke around and even modify the app as it’s running from the command prompt, REPL style.

If you work thru the IronPython Tutorial, there are exercises for interactively building both a WinForms and a WPF application. In both scenarios, you create a dedicated thread to service the UI so it can run while the interactive prompt thread is blocked waiting for user input. However, as we saw in the last part of this series, UI elements in both WinForms and WPF can only be accessed from the thread they are created on. We already know how to marshal calls to the correct UI thread – Dispatcher.Invoke. However, what we need is a way to intercept commands entered on the interactive prompt so we can marshal them to the correct thread before they execute.

Luckily, IronPython provides just such a mechanism: clr module’s SetCommandDispatcher. A command dispatcher is a function hook that gets called for every command the user enters. It receives a single parameter, a delegate representing the command the user entered. In the WPF and WinForms tutorials, you use this function hook to marshal the commands to the right thread to be executed. Here’s the command dispatcher from the WPF tutorial:

def DispatchConsoleCommand(consoleCommand):
    if consoleCommand:
        dispatcher.Invoke(DispatcherPriority.Normal, consoleCommand)

The dispatcher.Invoke call looks kinda like the UIThread decorator from the Background Processing part of this series, doesn’t it?

Quick aside: I looked at using SyncContext here instead of Dispatcher, since I don’t care about propagating a return value back to the interactive console thread. However, SyncContext expects a SendOrPostDelegate, which expects a single object parameter. The delegate passed to the console hook function is an Action with no parameters. I could have built a wrapper function that took a single parameter which it would ignore, but I decided it wasn’t worth it. The more I look at it, the more I believe SyncContext is a good idea with a bad design.

I wrapped all the thread creation and command dispatching into a reusable helper class called InteractiveApp.

class InteractiveApp(object):
  def __init__(self):
    self.evt = AutoResetEvent(False)
    
    thrd = Thread(ThreadStart(self.thread_start))
    thrd.ApartmentState = ApartmentState.STA
    thrd.IsBackground = True
    thrd.Start()
    
    self.evt.WaitOne()
    clr.SetCommandDispatcher(self.DispatchConsoleCommand)
    
  def thread_start(self):
    try:
      self.app = Application()
      self.app.Startup += self.on_startup
      self.app.Run()
    finally:
      clr.SetCommandDispatcher(None)

  def on_startup(self, *args):
    self.dispatcher = Threading.Dispatcher.FromThread(Thread.CurrentThread)
    self.evt.Set()
    
  def DispatchConsoleCommand(self, consoleCommand):
    if consoleCommand:
        self.dispatcher.Invoke(consoleCommand)
    
  def __getattr__(self, name):
    return getattr(self.app, name)

The code is pretty self explanatory. The constructor (__init__) creates the UI thread, starts it, waits for it to signal that it’s ready via an AutoResetEvent and then finally sets the command dispatcher. The UI thread creates and runs the WPF application, saves the dispatcher object as a field on the object, then signals that it’s ready. DispatchConsoleCommand is nearly identical to the earlier version, I’ve just made it an instance method instead of a stand-alone function. Finally, I define __getattr__ so that any operations invoked on InteractiveApp are passed thru to the contained WPF Application instance.

In my app.py file, I look to see if the module has been started directly or if it’s been imported into another module. If the module is run directly (aka ‘ipy app.py’) then the global __name__ variable will be ‘__main__’. In that case, we start the application up normally (i.e. without the interactive prompt) by just creating an Application then running it with a Window instance. Otherwise, we are importing this app into another module (typically, the interactive console), so we create an InteractiveApp instance and we create an easy to use run method that can create the instance of the main window.

if __name__ == '__main__':
  app = wpf.Application()
  window1 = MainWin.MainWindow()
  app.Run(window1.root)
  
else
  app = wpf.InteractiveApp()

  def run():
    global mainwin
    mainwin = MainWin.MainWindow()
    mainwin.root.Show()

If you want to run the app interactively, you simply import the app module and call run. Here’s a sample session where I iterate thru the items bound to the first list box. Of course, I can do a variety of other operations I can do such as manipulate the data or create new UI elements.

IronPython 2.0 (2.0.0.0) on .NET 2.0.50727.3053  
>>> import app  
>>> app.run()  
#at this point the app window launches
>>> for i in app.mainwin.allAlbumsListBox.Items:  
...     print i.title  
...  
Harvest Festivals  
Mrs. Gardner's Art  
Riley's Playdate  
August 13  
Camp Days  
July 14  
May Photo Shoot  
Summer Play 2006  
Lake Washington With The Gellers  
Camp Pierson '06  
January 28

One small thing to keep in mind: if you exit the command prompt, the UI thread will also exit since it’s marked as a background thread. Also, it looks like you could shut the client down then call run again to restart it, but you can’t. If you shut the client down, the Run method in InteractiveApp.thread_start exits, resets the Command Dispatcher to nothing and the thread terminates. I could fix it so that you could run the app multiple times, but I find I typically only run the app once for a given session anyway.

Posted By Harry Pierson at 10:45 AM Pacific Standard Time

Friday, November 21, 2008

Background Processing Re-Revisited

OK, here’s the last word on this whole background processing / concurrency decorators thing. I went back and re-wrote the original decorators, but using the approach I used with the SyncContext version. I don’t want to rehash it again, here are the main points:

  • Instead of using a property to retrieve the dispatcher, I get it via Application.Current.MainWindow.Dispatcher (checking to be sure Current and MainWindow aren’t null…err, None). This way, I pick up the dispatcher automatically rather than forcing a specific interface on the class with decorated methods. In fact, this approach should work with pure functions as well.
  • Since I don’t have a convenient function like SetSynchronizationContext, I store the dispatcher in thread local storage for later use in calling back to the UI thread.
  • Unlike the SyncContext version, this version propagates the return value of @UIThread decorated functions. I don’t propagate the return value of @BGThread functions – there’d be no point farming a task to a background thread then blocking the UI thread waiting for a response.

As usual, the code is on my SkyDrive. It includes both the SyncContext and Dispatcher version of the decorators.

Posted By Harry Pierson at 7:20 AM Pacific Standard Time

Thursday, November 20, 2008

IronPython and WPF Background Processing Revisited

Yesterday, I blogged about using decorators to indicate if a given function should execute on the UI or background thread. While the solution works, I wrote “I’m thinking there might be a way to use SynchronizationContext to marshal it automatically, but I haven’t tried to figure that out yet.” I had some time this morning so I figured out how to use SynchronizationContext instead of the WPF dispatcher.

Leslie Sanford wrote a pretty good overview, but the short version is that SyncContext is an abstraction for concurrency management. It lets you write code that is ignorant of specific synchronization mechanisms in concurrency-aware managed frameworks like WinForms and WPF. For example, while my previous version worked fine, it was specific to WPF. If I wanted to provide similar functionality that worked with WinForms, I’d have to rewrite my decorators to use Control.Invoke. But if I port them over to use SyncContext, they would work with WinForms, WPF and any other library that plugs into SyncContext.

SyncContext abstracts away both initially obtaining the sync context as well as marshaling calls back to the UI thread. SyncContext provides a static property to access  current context, instead of a framework specific mechanism like accessing the Dispatcher property of the WPF Window class. Once you have a context, you can call Send or Post to marshal the call back to the UI thread (Send blocks the calling thread, Post doesn’t).

With that in mind, here’s the new version of BGThread and UIThread. Slightly more complex, but still pretty simple clocking in at just under 30 lines.

def BGThread(fun): 
  def argUnpacker(args): 
    oldSyncContext = SynchronizationContext.Current
    try:
      SynchronizationContext.SetSynchronizationContext(args[-1])
      fun(*args[:-1])
    finally:
      SynchronizationContext.SetSynchronizationContext(oldSyncContext)
  
  def wrapper(*args):
    args2 = args + (SynchronizationContext.Current,)
    ThreadPool.QueueUserWorkItem(WaitCallback(argUnpacker), args2)
  
  return wrapper

def UIThread(fun):
  def unpack(args): 
    ret = fun(*args)
    if ret != None:
      import warnings
      warnings.warn(fun.__name__ + " function returned " + str(ret) + " but that return value isn't propigated to the calling thread")

  def wrapper(*args):
    if SynchronizationContext.Current == None:
      fun(*args)
    else:
      SynchronizationContext.Current.Send(SendOrPostCallback(unpack), args)
     
  return wrapper

In the BGThread wrapper, I add the current SyncContext to the parameter tuple that I pass to the background thread. Once on the background thread, I set the current SyncContext to the last element of the the parameter tuple then call the decorated function with the remaining parameters. (for the non pythonic: args[:-1] is Python slicing syntax that means “all but the last element of args”). Using a try/finally block is probably overkill – I expect the current SyncContext to be either None or leftover garbage – but the urge to clean up after myself is apparently much stronger on the background thread than it is in say my office. :)

In the UIThread wrapper, I grab the current context and invoke the decorated method via the Send method. Like QueueUserWorkItem, SyncContext Send and Post only support a single parameter, so I use the same *args trick I described in my last post. (I changed the name to unpack in the code above for blog formatting purposes)

One major caveat about this approach is that there’s no way to return a value from a function decorated as UIThread. I understand why SyncContext.Post doesn’t return a value (it’s async) but SyncContext.Send is synchronous call, so why doesn’t it marshal the return value back to the calling thread? WPF’s Dispatcher.Invoke and WinForm’s Control.Invoke both return a value. I didn’t handle the return value in my original version of UIThread, but now that I’ve moved over to using SyncContext, I can’t. Not sure why the SyncContext is designed that way – seems like a design flaw to me. Since the return value won’t propagate, I sniff the result decorated function’s return value and raise a warning if it’s not None.

I’ve uploaded the SyncContext version to my SkyDrive in case you want the code for yourself. Note, I’ll thinking I’ll revise code this one more time – I want to rebuild the WPF version so that it propagates return values and picks up an dispatcher via Application.Current.MainWindow rather than having to have a dispatcher property on my class.

Posted By Harry Pierson at 2:57 PM Pacific Standard Time

Wednesday, November 19, 2008

IronPython and WPF Part 4: Background Processing

Like many apps today, my WL Spaces photo viewer is a connected app. The various WL Spaces RSS feeds that drive the app can take a several seconds to download. Unless you like annoying your users, it’s a bad idea to lock up your user interface while you make you make synchronous network calls on your UI thread. Typically, this long running processing gets farmed out to a background thread which keeps the UI thread free to service the user events.

.NET provides a variety of mechanisms for doing long running processing on a background thread. For example you can create a new thread, you can queue a work item to the ThreadPoool or use the BackgroundWorker component. However, none of these are particularly pythonic, so I set out to see if I could leverage any of Python’s unique capabilities to make background processing as easy as possible. This is what I ended up with:

def OnClick(self, sender, args): 
    self.DLButton.IsEnabled = False 
    self.BackgroundTask(self._url.Text) 

@BGThread   
def BackgroundTask(self, url): 
    wc = WebClient()
    data = wc.DownloadString(Uri(url))   
    self.Completed(data) 
     
@UIThread 
def Completed(self, data): 
    self.DLButton.IsEnabled = True
    self._text.Text = data

By using the cool decorators feature of Python, I’m able to declaratively indicate whether I want a given method to be executed on the UI thread or on a background thread. Doesn’t get much easier than that. Even better, the implementations of BGThread and UIThread are only about twenty lines of Python code combined!

Decorators kinda look like custom .NET attributes. However, where .NET attributes are passive (you have to ask for them explicitly), decorators act as an active modifier to the functions they are attached to. In that respect, they’re kind of like aspects. Certainly, I would consider which thread a given method executes on to be a cross-cutting concern.

The Completed function above is exactly the same as if I had written the following:

def Completed(self, data): 
    self.DLButton.IsEnabled = True 
    self._text.Text = data 
Completed = UIThread(Completed)

In C#, you can’t pass a function as a parameter to another function – you have to first wrap that function in a delegate. Python, like F#, directly supports higher-order functions. This lets you easily factor common aspectual code out into reusable functions then compose them with your business logic. The decorators have no knowledge of the functions they are attached to and the code that calls those functions are written in complete ignorance of the decorators. Python goes the extra mile beyond even F# by providing the ‘@’ syntax.

Here are the implementations of my the UIThread and BGThread decorators:

def BGThread(fun): 
  def argUnpacker(args): 
    fun(*args)
  
  def wrapper(*args): 
    ThreadPool.QueueUserWorkItem(WaitCallback(argUnpacker), args)
  
  return wrapper

def UIThread(fun):
  def wrapper(self, *args):
    if len(args) == 0:
      actiontype = Action1[object]
    else:
      actiontype = Action[tuple(object for x in range(len(args)+1))]

    action = actiontype(fun)
    self.dispatcher.Invoke(action, self, *args)
    
  return wrapper 

BGThread defines a wrapper function that queues a call to the decorated function to the .NET thread pool.  UIThread defines a wrapper that marshals the call to the UI thread by using a WPF Dispatcher. I’m thinking there might be a way to use SynchronizationContext to marshal it automatically, but I haven’t tried to figure that out yet. The above approach does require a dispatcher property hanging off the class, but that’s fairly trivial to implement and seems like a small price to pay to get declarative background thread processing.

A couple of quick implementation notes:

  • The ‘*args’ syntax used in those methods above means “given me the rest of the positional arguments in a tuple”. Kinda like the C# params keyword. But that syntax also lets you pass a tuple of parameters to a function, and have them broken out into individual parameters. QueueUserWorkItem only supports passing a single object into the queued function, so I pass the tupled arguments to the argUnpacker method, which in turn untuples the arguments and calls the decorated function.
  • The System assembly includes the single parameter Action<T> delegate. The current DLR provides Action delegates with zero, two and up to sixteen parameters. However, those are in a separate namespace (remember?) and IPy seems to have an issue with importing overloaded type names into the current scope. I could have used their namespace scoped name, but instead I redefined the version from System to be called Action1.
  • To interop with .NET generic types, IPy uses the legal but rarely used Python syntax type[typeparam]. For example, to create a List of strings, you would say “List[str]()”. The type parameter is a tuple, so in UIThread I build a tuple of objects based on the number of arguments passed into wrapper (with the special case of a single type parameter using Action1 instead of Action).

I haven’t uploaded my WL Spaces Photo Viewer app because I keep making changes to it as I write this blog post series. However, for this post I built a simple demo app so I could focus on just the threading scenario. I’ve stuck the code for that demo up on my SkyDrive, so feel free to leverage it as you need.

Posted By Harry Pierson at 1:47 PM Pacific Standard Time

Monday, November 17, 2008

IronPython and WPF Part 3: Data Binding

Here’s the short version of this post: data binding in WPF to IPy objects just works...mostly. However, I’m guessing you are much more interested in the long version.

Typically, data binding depends on reflection. For example, the following snippet of XAML defines a data bound list box where the title property of each object in the bound collection gets bound to the text property of a text block control. WPF would typically find the title property of the bound objects via reflection.

<ListBox Grid.Column="0" x:Name="listbox1" >
  <ListBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding Path=title}" />
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

The problem is that IronPython objects don’t support reflection – or more accurately, reflection won’t give you the answer you’re expecting. Every IPy object does have a static type, but it implements Python’s dynamic type model. [1] Thus, if you reflect on the IPy object looking for the title property or field, you won’t find it. It might seem we’re in a bit of a bind (pun intended). However, WPF does provide an out:

“You can bind to public properties, sub-properties, as well as indexers of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.”
WPF Binding Sources Overview, MSDN Library

Luckily for us, IronPython objects implement ICustomTypeDescriptor [2]. That snippet of XAML above? It’s straight from my photo viewing app. All I had to do was define the data template in the list box XAML then set the ItemsSource property of the list box instance.

w.listbox1.ItemsSource = albumsFeed.channel.item

As I said, it just works. However, I did hit one small snag – hence the “mostly” caveat above.

If you look at the top level WL Spaces photos feed, you’ll see that each item’s title starts with “Photo Album:”. Yet in the screenshot of my app, you’ll notice that I’ve stripped that redundant text out of the title. Typically, if you want to change the bound value during the binding process, you build an IValueConverter class. I needed two value conversions in my app, stripping “Photo Album:” for the album list box and converting a string URL into a BitmapImage for the image list box.

IronPython objects can inherit from a .NET interface, so there’s no problem building an IValueConverter. However, in order to use a custom IValueConverter from XAML, you need to declare it in XAML as a static resource. However, as you might imagine, dynamic IPy objects don’t work as static resources. So while I can define an IValueConverter in Python, I can’t create one from XAML.

There are a few possible solutions to this. The first is to build up the data template in code. If you do that, they you can programmatically add the converter to the binding. I was hopeful that I could define the data template in XAML then manipulate the binding, but there doesn’t appear to be any way to do that. Another option would be to build some type of generic IValueConverter class in C# that loads either an IPy based IValueConverter or embedded python conversion code. That’s problematic because those IPy object would need to be created in the right ScriptRuntime, and there’s no built-in way to access that. There are also a small set of XamlReader extensions such as XamlTypeMapper that might be able to provide the right hook into the XAML parsing to allow IronPython based conversion.

In the end, I took the easiest way out – I transformed the data to be bound before binding it. It’s cheating of sorts, but given the read-only nature of this app, it was the easiest thing to do. So the actual line of code to set listbox1’s ItemsSource looks like this:

class Album(object):
  def __init__(self, item):
    self.title = item.title.Substring(13)
    self.itemRSS = item.itemRSS
    
w.listbox1.ItemsSource = [Album(item) for item in albumsFeed.channel.item]

I create a Python class for each RSS item in the feed, saving the stripped title and the album RSS URL as fields. It’s kinda annoying to basically be parsing the feed twice, but at least it’s not much code. Python’s list comprehension syntax makes creating a list of Albums from a list of RSS items a single line of code. I do something very similar for data binding the second list box:

class Picture(object):
  def __init__(self, item):
    self.title = item.title 
    self.picture = BitmapImage(Uri(item.enclosure.url + ":thumbnail"))

w.listbox2.ItemsSource = [Picture(item) for item in albumfeed.channel.item]

Here I’m not only converting the raw data (adding “:thumbnail” at the end of the URL) but also changing the data type from string to BitmapImage. I’m binding to an image object in the second list box, but to do that I need a BitmapImage instead of a string.

This “convert the data first” approach feels like a hack to me. After I get this series of posts done, I am planning on going back and improving this sample. Hopefully, I can find a better approach to value conversions. Any gurus out there on XAML parsing, please feel free to drop me a line or leave me a comment.


[1] you can access the underlying CLR type for any Python type via the clr.GetClrType method. You an also check out the CreateNewType method from NewTypeMaker.cs

[2] I spent the better part of an afternoon trying to make TypeDescriptionProviders work before Dino pointed out that we already support ICustomTypeDescriptor in Python objects. I didn’t realize at first because I had a case sensitivity bug in my original prototype code - it turns out that “Title” != “title”.

Posted By Harry Pierson at 5:00 PM Pacific Standard Time

Friday, November 14, 2008

IronPython and WPF Part 2: Loading XAML

If we’re going to build a WPF app, we’re going to want to be able to load some XAML. Sure, you can programmatically build up your UI, but WPF and more importantly WPF tools like Expression Blend are designed to work with XAML. Luckily, loading XAML is fairly easy:

def LoadXaml(filename):
    from System.IO import File
    from System.Windows.Markup import XamlReader
    with File.OpenRead(filename) as f:
        return XamlReader.Load(f)

We simply open the filename provided and use XamlReader to build out the corresponding WPF object graph. Note, this is very different from the XAML approach used by C#/VB or even by IronPythonStudio. In those scenarios, the XAML is compiled into a binary format (BAML) and embedded in the compiled assembly. For my TechieWife Photo viewer, it’s all script so there’s neither a XAML to BAML compile step nor a compiled assembly to embed the BAML into, so we’re just loading raw XAML.

Since we’re using raw XAML, there are additional rules we need to follow. First, when using compiled XAML, we can specify the name of the event handler in the XAML directly. For XamlReader, that’s no allowed since there’s no C#/VB class associated with the XAML. Speaking of class, you can’t specify x:Class either. Finally, anywhere you want to use a static resource, as far as I can tell those need to be compiled in a static language. I think you could build one in C#, add a reference to that assembly via clr.AddReference, then use it from XAML and it should just work. However, since I’m trying to stick to IronPython exclusively, I didn’t try that scenario out. 

Since you can’t specify the event handlers in XAML loaded by XamlReader, you have to bind the event handlers in code. There are two listboxes in my photo viewing app, and I want to capture the SelectionChanged event of both of them. Binding event handlers in IronPython code uses the same += syntax as C# uses.

win1 = wpf.LoadXaml('win1.xaml')
    
win1.listbox1.SelectionChanged += listbox1_OnSelectionChanged
win1.listbox2.SelectionChanged += listbox2_OnSelectionChanged

My win1.xaml file has a Window type instance as the root. You don’t need to be a deep WPF expert to realize that the WPF Window doesn’t have listbox1 or listbox2 properties. Yet, in the code snippet above, I was able to say win1.listbox1 and get back the WPF ListBox element with that name. Cool trick, eh? Well, I can’t take credit for it – I copied the code from our Silverlight integration for dynamic languages. Unfortunately, this code has to be written in C# code, but it is the only C# code in my whole solution (and it’s reusable!)

[assembly: ExtensionType(
    typeof(FrameworkElement), 
    typeof(DevHawk.Scripting.Wpf.FrameworkElementExtension))] 

namespace DevHawk.Scripting.Wpf 

    public static class FrameworkElementExtension 
    { 
        [SpecialName] 
        public static object GetBoundMember(FrameworkElement e, string n) 
        { 
            object result = e.FindName(n); 
            if (result == null
            { 
                return OperationFailed.Value; 
            } 
            return result; 
        } 
    } 
}

GetBoundMember is kinda like Python’s __getattr__ or Ruby’s method_missing. Of course, it doesn’t work with C#, but it does lets us trap dynamic member resolution when calling a C# object from a DLR language. Srivatsn has a great write up on using GetBoundMember and the four other special methods you can use to make your CLR objects act more dynamic.

In this case, if the standard reflection-based member name resolution fails, we try calling FrameworkElement’s FindName method to see if there’s a corresponding control with the provided name. So win.listbox1 is the equivalent to win.FindName(‘listbox1’), but with less code and a much more pythonic feel.

You’ll notice that we’re attaching this GetBoundMember method to FrameworkElement as an extension method. It’s kinda cool that we can inject a new method into an existing class to provides dynamic behavior and it all works seamlessly from Python. However, DLR uses a different mechanism to locate and bind extension methods than C# or VB. Those languages use ExtensionAttribute to mark extension methods and the assemblies and classes that contain them. However, that approach forces you to examine ever single class in marked assemblies and every single method in marked classes. Examining every class and method is no big deal to do at compile time, but it would be a significant perf issue at runtime. By using ExtensionType attribute, the DLR only has to look at assembly attributes in order to bind extension methods.

Once you’ve got the compiled FrameworkElementExtension assembly, you just need to load it via clr.AddReference. I called the assembly Devhawk.Scripting.Wpf and I load it automatically in my wpy.py module. So if you’re building a WPF app in IronPython, you can simply “import wpy” and you get the GetBoundMember extension method, the LoadXaml function, and a bunch of WPF related namespaces imported into the wpf scope. That way, you can write wpf.Button() instead of System.Windows.Control.Button() to programmatically create a new button.

Posted By Harry Pierson at 3:06 PM Pacific Standard Time

Wednesday, November 12, 2008

IronPython and WPF Part 1: Introduction

I decided to start my IronPython and “veritable universe of cool technologies” examples with WPF. I figured that since we already have Silverlight support, there might be some overlap (there was). Futhermore, after seeing BabySmash on Surface I’m jonesing to build a Surface app of my own. Getting vanilla WPF working with IPy seems like a smart step before trying to build a Surface WPF app with IPy.

WPF is all about cool graphics, so I decided to build a photo viewing app. Kinda boring, I know. But it turns out my wife has posted hundreds of photos to her WL Space, and WL Spaces provides convenient RSS feeds of both photo albums as well as photos in specific albums. So I built out a simple WPF based photo viewer for my wife’s WL Space photos in IronPython.

TechieWife Photo Viewer screenshot

As you can see, I'm not quitting my job to go pursue a career in design anytime soon. But hey, the point is demonstrate building a WPF app in IPy, not to be a great designer. Plus, don’t those cute kids make up for the ugliness of the app?

Turns out building this app in IPy was fairly straightforward, with a few pitfalls. I wasted half a day digging thru data binding before realized that data binding against IPy objects works out of the box - but only if you type the case of the property correctly (Title != title). Also, I couldn’t make TypeConverters work the way I wanted, but python list comprehensions made it enough to transform the feed data before binding it to the UI. That approach worked great for this scenario but maybe not so much for others. (I’ve got feelers out to the WPF data binding wonks, so maybe there’s still hope for type converters)

Over the next several posts, I’m going to show you all the code for this app. It’s pretty small, only about 50 lines of app-specific python code + 50 lines of XAML to describe the window. There’s also some reusable code – 50 lines of WPF module code (mostly stolen from avalon.py in the IPy tutorial), 200 lines of xml2py code which I’ve discussed before and a very small C# based assembly to make accessing WPF elements by name very pythonic.

Posted By Harry Pierson at 5:18 PM Pacific Standard Time
Change Congress
Recent Bookmarks
Tags .NET Framework (2) __clrtype__ (9) ADO.NET (5) Agile (7) AJAX (3) Architecture (288) Guidance (6) Interop (2) Modelling (61) Patterns (7) Process (4) SOA (94) Web Services (5) ASP.NET (25) Async Messaging (2) Azure (1) Battlestar Galactica (3) BI (2) BizTalk (4) Blogging (117) dasBlog (11) Podcasting (4) BPM (1) C# (11) C++ (4) Capitals (5) CardSpace (3) CLR (2) CodePlex (1) College Football (10) Comedy Central (1) Community (81) Concurrency (6) Consumer Electronics (1) Database (13) Debugger (23) Dependency Injection (2) Development (122) C Plus Plus (1) Embedded (5) Lanugages (42) Media (2) P2P (11) Rotor (1) SharePoint (6) SOP (3) DIY (1) DLR (25) Domain Specific Languages (15) Durable Messaging (5) Dynamic Languages (12) Dynamic Silverlight (1) Education (3) Enterprise 2.0 (1) Entertainment (14) ETech (15) F# (51) Functional Programming (17) Game Development (2) Guidance Automation (3) Hardware (8) HawkCodeBox (1) HawkEye (3) Health (1) Hockey (31) Home Electronics (1) Home Network (5) Hosting API (1) Humor (5) IASA (1) Idempotence (3) infrastructure (5) Instrumentation (4) Integration (2) IronPython (112) IronRuby (16) Java (2) Job (3) Kodu (1) LangNET (2) Lightweight Debugger (5) LINQ (23) Live Framework (3) Live Mesh (2) Lost (1) Master Data Management (1) Media 2.0 (6) Microsoft (31) MIX06 (2) Mobile Phone (1) Monads (5) Morning Coffee (172) Object Oriented (4) Office (5) Open Source (8) Open Space (2) Operations (3) Other (135) Art (1) Books (1) Family (33) Games (18) General Geekery (27) Home Theater (1) Movies (23) Music (20) Politics (3) Society (1) Sports (37) Working at MSFT (19) Parallel Programming (3) Parsing Expression Grammar (16) patterns & practices (2) PDC08 (5) Politics (48) Polyglot (3) PowerPoint (2) PowerShell (39) Presentation (7) Projects (1) HawkWiki (1) Pygments (5) Python (6) Quote of the Day (4) Refactoring (1) Research (2) REST (18) Reuse (5) Robotics (2) Rock Band (4) Rome (5) Ruby (23) Ruby on Rails (1) Sci-Fi (2) Scripting (4) Security (3) Service Broker (14) SharePoint (2) Silverlight (20) Social Software (1) Software + Services (2) Software Design (2) Software Engineering (1) Software Factories (11) Software Industry (1) Space Elevator (1) Spark (1) SQL Server (2) Stephen Colbert (1) TechEd (7) TechEd06 (1) TechRec League (1) Television (6) Travel (7) Unified Client (1) Unit Testing (4) USC (1) UX (1) Virtual PC (2) Visual Basic (3) Visual Studio (20) Volta (2) Washington Capitals (37) WCF (31) Web 2.0 (67) Web Services (7) WF (21) Windows (3) Windows Live (29) Windows Live Writer (3) WPF (8) Xbox (1) Xbox 360 (54) XML (11) XNA (15) Zune (4)
Disclaimer: The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion.