Friday, July 25, 2008
It's been a long time since I blogged anything specific on WPF -- I've been doing a lot of it lately, along with some Silverlight.  Recently I was experimenting with dragging tabs around on a TabControl at runtime.  My end goal is really to implement it with Silverlight 2, but I've found it's much easier to prototype things in WPF and then port them over because the debugging experience is easier with WPF.

I didn't want to create derived implementations of any classes - I wanted something that was non-intrusive to my code so I decided to use an attached property.  Attached properties are basically property values "attached" to a class at runtime - where the property itself isn't defined on the target but instead on some other type.  The cool thing about attached properties is they can register a change notification handler which gives them a reference to the object they are being placed on -- this is how the Spell Checker works with the TextBox in WPF.  All the code for the spell checking lives in the SpellChecker class and when you add the SpellCheck.IsEnabled property onto the TextBox, it adds handlers to the TextBlock's TextChanged property and adds all the nifty spell checking goodness without changing the code in TextBox.

Back to my drag/drop prototype.  So with this code, I can add the property to any TabControl and get a nice, simple drag/drop experience.  It's far from complete - it would be cooler if the tabs moved around as you dragged (they don't), but I was just prototyping here.

Here's the code:




using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApplication1
{
    public class DragDropTabManager
    {
        private static readonly DependencyProperty ManagerProperty =
            DependencyProperty.Register(typeof (DragDropTabManager).ToString(), typeof (DragDropTabManager),
                                        typeof (DragDropTabManager));

        public static readonly DependencyProperty EnabledProperty = 
            DependencyProperty.RegisterAttached("Enabled", typeof(bool), 
                                                typeof(DragDropTabManager),
                                                new PropertyMetadata(false, DDTM_EnabledChanged));

        private static void DDTM_EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tc = d as TabControl;
            if (tc != null)
            {
                var oldValue = (bool) e.OldValue;
                var newValue = (bool) e.NewValue;

                if (oldValue == true && newValue == false)
                {
                    var ddtm = tc.GetValue(ManagerProperty) as DragDropTabManager;
                    if (ddtm != null)
                    {
                        tc.PreviewMouseDown -= ddtm.TabItem_PreviewMouseDown;
                        tc.SetValue(ManagerProperty, null);
                    }
                }
                else if (oldValue == false && newValue == true)
                {
                    var ddtm = new DragDropTabManager();
                    tc.SetValue(ManagerProperty, ddtm);
                    tc.PreviewMouseDown += ddtm.TabItem_PreviewMouseDown;
                }
            }
        }

        public static bool GetEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnabledProperty);
        }

        public static void SetEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(EnabledProperty, value);
        }

        private bool isMoving;
        private TabItem movingTabItem;
        private TabItem lastTab;
        private Point ptStart;

        void TabItem_PreviewMouseDown(object sender, MouseEventArgs e)
        {
            var ti = e.Source as TabItem;
            if (ti != null && e.LeftButton == MouseButtonState.Pressed)
            {
                var tc = ti.Parent as TabControl;
                if (tc != null)
                {
                    tc.MouseMove += tc_MouseMove;
                    tc.MouseLeftButtonUp += tc_MouseLeftButtonUp;

                    ptStart = e.GetPosition(tc);
                    movingTabItem = ti;
                }
            }
        }

        void tc_MouseMove(object sender, MouseEventArgs e)
        {
            var tc = sender as TabControl;
            if (tc == null)
                return;

            Point pt = e.GetPosition(tc);

            if (isMoving == false)
            {
                if (Math.Abs(pt.X - ptStart.X) > 10)
                {
                    movingTabItem.IsHitTestVisible = false;
                    movingTabItem.RenderTransformOrigin = new Point(.5, .5);
                    movingTabItem.RenderTransform = new TranslateTransform(0, 0);
                    tc.Cursor = Cursors.Hand;
                    Panel.SetZIndex(movingTabItem, 1);
                    isMoving = true;
                    tc.CaptureMouse();
                }
                return;
            }

            TabItem newPos = FindTabItem(tc, pt);
            if (newPos == null)
                tc.Cursor = Cursors.No;
            else
            {
                lastTab = newPos;
                var xform = movingTabItem.RenderTransform as TranslateTransform;
                if (xform != null)
                    xform.X = pt.X - ptStart.X;
                tc.Cursor = Cursors.Hand;
            }
        }

        void tc_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var tc = sender as TabControl;
            Debug.Assert(tc != null);

            tc.ReleaseMouseCapture();
            tc.MouseMove -= tc_MouseMove;
            tc.MouseLeftButtonUp -= tc_MouseLeftButtonUp;

            if (isMoving == true)
            {
                isMoving = false;
                tc.Cursor = Cursors.Arrow;
                movingTabItem.RenderTransform = null;
                movingTabItem.IsHitTestVisible = true;

                Panel.SetZIndex(movingTabItem, 0);

                if (lastTab != null)
                {
                    if (lastTab != null && movingTabItem != lastTab)
                    {
                        int targetIndex = tc.Items.IndexOf(lastTab);
                        tc.Items.Remove(movingTabItem);
                        tc.Items.Insert(targetIndex, movingTabItem);

                        movingTabItem.Focus();
                    }
                }
            }
            movingTabItem = lastTab = null;
        }

        private static TabItem FindTabItem(UIElement parent, Point pt)
        {
            var fe = parent.InputHitTest(pt) as FrameworkElement;
            while (fe != null && fe.GetType() != typeof(TabItem))
                fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
            return fe as TabItem;
        }
    }
}
posted on 7/25/2008 2:09:45 PM (Central Standard Time, UTC-06:00)  #   
 Tuesday, May 06, 2008


Last month, my colleague, Pinku Surana, wrote an article about .NET AppDomains and how they can be used to provide component isolation and make your applications more reliable.  If you missed it, you can read the Developments archives on Developmentor’s website.   This month, I’d like to continue exploring reliability and extensibility by introducing you to a new framework included with .NET 3.5: The Managed Add-in Framework (MAF), sometimes referred to as System.AddIn.

Developers (and managers) have long desired a way to easily create extensible applications that allow new features to be added without jeopardizing the stability of the existing code base.  The .NET framework has provided the underlying support to accomplish this from the very beginning through the reflection API and AppDomain support Pinku examined last month.  MAF builds on that fundamental support to provide a higher-level service that allow you to dynamically discover, load, secure and interact with external assemblies used to provide features for your application.  Several common architectural requirements are made trivial with MAF:

1)   Isolate aspects of your code for security reasons or partial-trust scenarios.

2)   Allow business partners to extend your application safely without access to the source code; Adobe Illustrator is a great example of this style of application.

3)   Separate volatile sections of your application out – where depending on the customer the application needs to execute different sets of code.

4)   Add or change code without unloading the application – for example pay-to-play scenarios, or where you need to update an assembly but the application must continue to run.

5)   Develop and evolve different sections of the application in parallel without any fear of destabilizing one based on the other.

If any of these scenarios sound like something you could utilize in your application then you should know about MAF!

At the center of the MAF-based application is the pipeline.

The Pipeline

Communication between add-ins and the host application is strictly regulated by the pipeline.  The pipeline is dynamically created by MAF through a set of loosely-coupled components which are used to version, marshal and transform the data as it passes back and forth between the host and each loaded add-in.

image001.png

Each section of the pipeline is contained in a separate assembly, loaded as necessary to manage the specific add-in.  MAF discovers each component and then loads them on request using reflection.   

For reliability, MAF allows optional isolation boundaries to be created between the add-in and host – everything to the left of the contract is loaded into the main (host) AppDomain and everything to the right loaded into a newly created AppDomain which has its own security permission set.  The isolation can also be done through cross-process calls if true process-level isolation is necessary.  Under the covers, the system uses traditional remoting calls to do the work of marshaling calls back and forth.

Breaking down the pipeline, there are three main parts starting in the center with the contract.

Contract

As you might expect, MAF is based on interface contracts.  Interfaces allow classes in an application to be loosely coupled, reducing the risk when changes are made between dependent sections of the code.   The interface contract is shared between the host and add-in and once it has been established, it should never be changed. 

Consider the simple example of a translator program.  The host will expect to load one or more translator add-ins that will do some work on an inbound string and return the results.  To accomplish this, I might create an ITranslator interface that looks like:

[AddInContract]

public interface ITranslator : IContract

{

   string Translate(string input);   
}


Notice that the interface derives from IContract.   This is a requirement for add-in contracts and is what will be used to provide marshalling support when the pipeline is established.  We also need to decorate the interface with the [AddInContract] attribute – this is the marker used by MAF to identify the contract when it is dynamically constructing the pipeline.  Both of these types come from the System.AddIn.Pipeline namespace in System.AddIn.Contract.dll.

Views

Moving to the edges, we find the views.  This is the code that the add-in and host directly interact with.  It represents a host or add-in specific “view” of the contract and, like the contract, is contained in a separate assembly.

Both of the view classes will echo the structure of the contract, but not actually be dependent upon the contract.  For example, our host side view might look like this:

public abstract class TranslatorHostView

{

   public abstract string Translate(string input);
}


The view is commonly exposed as an abstract class to make it look more natural when used by the host, but an interface will work just as well.  The host uses the view directly when communicating with any add-in based on the contract.

On the add-in side, we have an almost identical class – except we decorate this type with the [AddInBase] attribute so MAF knows which side of the pipeline this view is for (the add-in).   This is located in the System.AddIn.Pipeline namespace in the System.AddIn assembly.

When we create each add-in, the implementation will use this type as the base class.

[AddInBase]

public abstract class TranslatorAddInView

{

   public abstract string Translate(stringinp);
}

Adapters

The last piece of the pipeline is the adapters.  The adapters play a very special role in the pipeline – they are the glue that binds the contract to the view: implementing lifetime management and any necessary data conversion between the two ends. 

It may seem redundant to have this class, but by separating the view from the contract we introduce version tolerance into our architecture – the host can version independently of the add-in and vice-versa.  Depending on the situation, the adapters can be as simple as a pass-through class, or can provide higher-level services to allow non-serializable types to be marshaled across the isolation boundaries.

On the host side, the adapter will implement the view (remember it is either an interface or abstract class).  It will be passed a reference to the contract in the constructor and it is responsible for connecting the two together.

[HostAdapter]

public class TranslatorHostViewToContract :  

                     TranslatorHostView

{

   ITranslator _contract;

   ContractHandle _lifetime;

 

   public TranslatorHostViewToContract(

              ITranslator contract)  

   {

      _contract = contract;

      _lifetime = new ContractHandle(contract);

   }

 

   public override string Translate(string inp) 

   {

      return _contract.Translate(inp);
   }
}


In this simple example, the code caches off the contract interface.  This is a remoting proxy to the actual loaded add-in.  The adapter then implements the Translate method and passes it forward to the contract for implementation.  If we had non-serializable types, then the adapter would be responsible for converting them into something that was serializable. 

It also provides some lifetime management for the contract.  Because the contract is a remoting proxy and is likely running in a separate AppDomain (or even process) we have to be concerned with how long it lives.  MAF provides all the support for this through the ContractHandle class.  Most of the time, all you will need to do is store a ContractHandle in your host adapter and then pass it the inbound contract to wrap in your constructor.

Finally, in order for MAF to identify this class, it must be decorated with the [HostAdapter] attribute from the System.AddIn.Pipeline namespace out of the System.AddIn.dll.

The add-in side looks very similar, but does the opposite: it makes the add-in view look like the contract.

[AddInAdapter]

public class TranslatorAddInViewToContract :

                  ContractBase, ITranslator

{

   TranslatorAddInView _view;

 

   public TranslatorAddInViewToContract(

             TranslatorView view)  

   {

      _view = view;

   }

 

   public string Translate(string inp)  { 

      return _view.Translate(inp); 

   }

}

 
MAF passes the view to the constructor and the class caches the reference off in a field.  It implements the contract (ITranslator) and ContractBase which provides the implementation of the IContract interface for us (remember this was a required interface on our contract).  As the host makes calls to the contract interface, this class will translate those calls to the add-in view, which as you will see is the implementation provided by the add-in itself.  Note how the [AddInAdapter] attribute is used to mark this class so MAF can discover it.

If it seems like all the above is a lot of repetitious, boilerplate code well.. you’re right!  To make it easier on the developer to create the pipeline components, the MAF team has created a pipeline builder available at http://www.codeplex.com/clraddins.  It takes the contract assembly and then generates the views and adapters from it:

image003.jpg

Creating an add-in

Once the pipeline pieces are built you can create add-ins.  Each add-in provides an implementation of the abstract add-in view.  For example, we might provide a BabelFish add-in for universalized translation:

[AddIn(“BabelFishTranslator”,

       Description=“Universal translator”,

       Version=“1.0.0.0”, Publisher=“Zaphod Beeblebrox”]

public class BabelFishAddIn : TranslatorAddInView

{

   public string Translate(string input)

   {

     ...

   }
}


The add-in implements the AddInView, providing a concrete implementation of the Translate method.  It is decorated with the [AddIn] attribute that allows it to provide a name, version, description and other data the host can use to decide whether it is a useful translator.

Putting the pieces together – the directory structure

To properly identify each of the components necessary, MAF enforces a particular directory structure you need to follow for deployment.  Each piece is stored in a sub-directory off the root of the pipeline directory (this is typically the APPBASE where the host executable is stored). 

image006.png

The directory names are required, but not case sensitive – each directory holding a single piece of the pipeline that MAF will load dynamically when it is loading the add-in.  The host-side view is always located in the same directory as the host executable itself so it does not need a dedicated directory.  When creating your Visual Studio project, it is important to set the output directories appropriately so that you create the above directory structure.  In addition, all references between the components should be marked as CopyLocal = “false” in Visual Studio to ensure a local copy of the assembly isn’t placed into the sub-directory: 

image008.png

Discovering and loading add-ins from the host

The last piece of the puzzle is the actual loading of add-ins from the host.  This is done in three basic steps:

1.     Identify and catalog the add-ins

2.     Retrieve the list of specific add-ins based on view or name

3.     Activate and use the add-in

The first step is to identify the available add-ins for the host.  This is done through the System.AddIn.AddInStore class:

string[] errorList = AddInStore.Rebuild(

         PipelineStoreLocation.ApplicationBase);

 

Calling Rebuild forces MAF to walk the directory structure and create the pipeline database.  It stores this information in the pipeline root directory and returns a list of errors, if any occur.  The most common errors are missing pipeline components – where MAF is unable to locate some required portion of the pipeline such as a View or Adapter.

Next, the host will retrieve a list of add-ins based on the host view through the FindAddIns method – these will be the add-ins conforming to a specific contract (whatever the view/adapter is bound to):

Collection<AddInToken> addinTokens = AddInStore.FindAddIns(

                typeof(TranslatorHostView),

                PipelineStoreLocation.ApplicationBase);

 

The first parameter is the host view type – so MAF knows what add-ins we are looking for, the second is the pipeline root directory, which is the same directory passed to the Rebuild method and indicates where the pipeline database is stored.

 

The returning collection represents a series of tokens that are used to identify and control the add-ins.  This is how the host can examine, activate and unload the add-in.  To activate a specific add-in, you can take the token and call Activate:

 

Collection<AddInToken> addinTokens;

    ...

foreach (AddInToken token in addinTokens)
{

   TranslatorHostView view =

       token.Activate<TranslatorHostView>(

             AddInSecurityLevel.Internet);

  

   string hello = view.Translate(“Bonjour”);

     ...
}

 

The Activate call loads all the required components, instantiates the add-in type and returns the host view used to interact with the add-in.  Calls made to this object will be marshaled back and forth to the add-in using the pipeline.

 

Notice that the parameter passed to Activate indicates the security level required.  There are several overrides that allow you to dictate exactly how the add-in is loaded.  You can load the add-in into a specific AppDomain – the current one for best performance:

 

token.Activate<TranslatorHostView>(AppDomain.CurrentDomain)

 

You can specify a specific permission set to restrict the things the add-in can do on your behalf:

PermissionSet pset = ...;

token.Activate<TranslatorHostView>(pset);


Or you can specify a known permission set based on the CAS zones:

image010.png

Once the add-in is activated, the host can call it just like any other object – but never forget that you are likely crossing an isolation barrier!  Make sure to design your contracts so that you minimize the number of calls between the host and add-ins to ensure your performance doesn’t suffer.

When you are finished with the add-in, you can tell MAF to unload it through the AddInController associated with the view:

AddInController ctrl =    

      AddInController.GetAddInController(view);

ctrl.Shutdown();


This will unload the add-in side pipeline and then destroy the containing AppDomain so you release the resources associated with it.

There are many other capabilities MAF provides such as versioning, passing collections and WPF visuals, passing non-serializable types, etc.  You can get a pretty decent overview of the features from MSDN and the MAF team has a blog at http://blogs.msdn.com/clraddins/ that has some great, practical information on using this new framework.

posted on 5/6/2008 3:46:26 PM (Central Standard Time, UTC-06:00)  #   
 Monday, November 19, 2007
Thanks to all those who attended the GNET in Tampa last week. We had a blast with all of you and wish you the best of luck going back and using all the cool tricks and technologies we showed you! As promised, here are the demos we build during the week - Demos. Enjoy!
posted on 11/19/2007 5:01:13 PM (Central Standard Time, UTC-06:00)  #   
 Friday, July 13, 2007

Sales of the TSP++ products have steadily fallen over the past 5 years, so much so that I decided two years ago to release the 2.x product into the open-source community, and now I am doing the same for the server edition. 

The download is available here and requires a key to install it - use 

J35M-3KXo-q60T

which will let you install the full product.  Documentation and samples are all part of the image so have fun!

posted on 7/13/2007 3:46:33 PM (Central Standard Time, UTC-06:00)  #   
 Wednesday, January 17, 2007

About a year ago, I blogged on using the .NET 2.0 facility SynchronizationContext to create thread-aware components which could be hosted and would "do the right thing" for callbacks. Here's the Link to that post.

Recently, I got an email from a reader who wanted to use that paradigm, but wanted to be able to suspend the operation for some period of time and then resume it later on. He asked if there was an easy way to modify the sample I presented, and it turns out that there is.

It essentially involves three steps:

1. In the AsyncStateData structure, add a ManualResetEvent object. This will be used to trigger the pause/resume behavior. It must be initially set as signaled.

2.
In the actual asynchronous operation loop, add the event into the loop condition by calling event.WaitOne(). Make sure you have released any locks and the code can safely be halted at that point in the logic otherwise you may create deadlock scenarios and other difficult debugging issues.

3.
Add a Pause and Continue method into the class which signals and resets the event.

Modifying my previous code example, here's what I come up with:

Step 1: Add the event to AsyncStateDate

   1:  class AsyncStateData
   2:  {
   3:      private ManualResetEvent pauseEvent = new ManualResetEvent(true);
   4:      ...
   5:  }

Step 2: Add event into the loop at a safe point

   1:  public partial class Calculator
   2:  {
   3:      private void InternalCalculatePi(int digits, AsyncStateData
   4:  asyncData)
   5:      {
   6:         int completedDigits = 0;
   7:         for (; !asyncData.canceled && pauseEvent.WaitOne(-1, true) &&
   8:  completedDigits < digits; completedDigits++)
   9:         {   ...  }
  10:      }
  11:   
  12:  ...
  13:  }

Step 3: Add a Pause and Continue API

   1:  public partial class Calculator
   2:  {
   3:      public void PauseAsync(object asyncTask)
   4:      {
   5:          AsyncStateData asyncData = asyncTask as AsyncStateData;
   6:          if (asyncData != null && asyncData.running == true)
   7:              asyncData.pauseEvent.Reset();
   8:      }
   9:   
   10:     public void ContinueAsync(object asyncTask)
   11:     {
  12:          AsyncStateData asyncData = asyncTask as AsyncStateData;
  13:          if (asyncData != null && asyncData.running == true)
  14:              asyncData.pauseEvent.Set();
  15:     }
  16:  ...
  17:  }


There are certainly other ways to do this as well, for example, create a dedicated Thread object instead of using an Async delegate and then call Suspend on the thread (and Resume to continue). That can be dangerous if you take locks while running your async operation so the ManualResetEvent is probably a better solution from that perspective (since you are blocking in a known location vs. just suspending the thread arbitrarily and potentially holding onto shared resources.
posted on 1/17/2007 10:11:04 AM (Central Standard Time, UTC-06:00)  #   

One of the nifty new features of the WPF platform is the pluggable data providers.  It ships with two out of the box:

ObjectDataProvider: allows you to execute binding expressions against an object and it's methods
XmlDataProvider: loads an XML data source and makes it available as a binding source

Both of these derive from the abstract class System.Data.DataSourceProvider which implements the binding glue (INotifyPropertyChanged) needed for data binding.  A side note here is that you could write your own custom data provider if you needed to, although if the data is exposed through a .NET object, then the ObjectDataProvider is probably sufficient.

Using the providers is fairly easy -- let's say we have some XML data that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<taxrecords>
 
<entry name="Opal Harrison" state="AL" income="$51,466.81" age="27" />
 
<entry name="Eugene Black" state="FL" income="$13,314.89" age="71" />
 
<entry name="Opal Chang" state="NC" income="$225,115.15" age="41" />
 
<entry name="Gary Waters" state="WI" income="$151,788.49" age="39" />
 
<entry name="Xavier Davis" state="AK" income="$136,344.97" age="66" />
 
<entry name="Stacy Harrison" state="TX" income="$122,432.82" age="32" />
</taxrecords>

The goal is to put this data into a ListBox - displaying the fields in the following format:

Name
State, Age, Income

We could clearly do all of this from procedural code -- create an XmlReader object, load the data and render each XmlNode into the listbox.  However, this is the 21st century and so we want to avoid coding as much as we can and utilize the underlying framework support instead!

We can get the data loaded into a collection source through the XmlDataProvider.  This is easily done in XAML:

<XmlDataProvider Source="largeXmlFile.xml" x:Key="xmlData" XPath="/taxrecords" />

This will look for the file "largeXmlFile.xml", create an XmlDocument and load the file into memory.  Notice we supply an XPath expression as part of this to indicate what we'd like the data provider to hand us as the collection itself.  In this case, we want to see everything under the node "taxrecords" which is the root of the document.  An interesting facet of this provider is that it performs it's work asynchronously -- you can see this behavior when you load very large XML files.  The UI will come up first, completely empty and then suddenly be populated with data.  The behavior can be adjusted through the IsAsynchronous property of the data provider.  Setting this to false will delay the display of the UI until the data is fully available.

Another interesting thing about this class is that we can define the XML data inline within the XAML document.  You do this with the x:XData tag:

<XmlDataProvider x:Key="xmlData" XPath="/taxrecords">
   <x:Data>
       <taxrecords>
         
<entry name="Opal Harrison" state="AL" income="$51,466.81" age="27" />
         
<entry name="Eugene Black" state="FL" income="$13,314.89" age="71" />
              ...

       </taxrecords>
  
</x:Data>
</XmlDataProvider>

The next step is to bind this data to a ListBox control - this is a normal Data Binding expression:

<ListBox Name="lb1" Margin="10" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource xmlData, XPath=entry}}">

Notice how we use a new property of the BindingExpression called XPath.  This property allows you to identify which element(s) you want to load from the XML data source.  It is specific to this data provider and allows for any XPath expression to be supplied.  This will succesfully load each of the "/taxrecord/entry" nodes into the ListBox, but the data itself will show up as a blank line.  This, of course, is because the data is really an XmlNode object which the ListBox has no idea how to display.  To fix this, we supply a DataTemplate to render our data: 

<ListBox.ItemTemplate>
   <
DataTemplate>
      <
StackPanel>
         <TextBlock FontWeight="Bold" Text="{Binding XPath=@name}" />
         <
StackPanel Orientation="Horizontal">
            <
TextBlock Text="{Binding XPath=@state}" />
            <
TextBlock Text=", " />
            <
TextBlock Text="{Binding XPath=@age}" />
            <
TextBlock Text=", " />
            <
TextBlock Text="{Binding XPath=@income}" />
         </
StackPanel>
      </
StackPanel>
   </
DataTemplate>
</
ListBox.ItemTemplate>

Again, we use the XPath property to define what piece of information we are binding to -- attributes of our entry in this case.  This template will give us the format we are looking for:

We can add sorting, filtering and grouping using the normal CollectionViewSource support.  Here is a XAML file which will present the above UI complete with sorting by the age element.  Notice how the XPath expression now moves to the CollectionView.  This is because the CollectionViewSource creates a ListCollectionView to manage the XML nodes because the data provider supports the IList interface.  The binding expression on the listbox is now simply a binding to the collection view.

<