Some basic sample to make your code “Linq ready”
Linq has been shipped with VS2008/.Net 3.5 and is not really new. If more and more people are getting used to its syntax, it’s sometimes hard to imagine all the scenarii where Linq to object can replace the classical way we program. During some Silverlight coding, I changed my code to make it run in a better Linq spirit and I wanted to share with you this little sample.
Silverlight (and WPF) maintains a tree of visuals. A useful class named VisualTreeHelper provides a very simple api to navigate this tree. One very common scenario is to look for the first visual parent of a T type for a given control. For instance, your control is in a template and you want to retrieve the templated control which you know is a ListBoxItem.
The VisualTreeHelper.GetParent() method (see prototype below) simply gives the graphical parent of any visual (as DependencyObject).
public static class VisualTreeHelper
{
//
// Summary:
// Returns an object's parent object in the visual tree.
//
// Parameters:
// reference:
// The object to get the parent object for.
//
// Returns:
// The parent object of the reference object in the visual tree.
//
// Exceptions:
// System.InvalidOperationException:
// reference is null, or is not a valid System.Windows.UIElement.
public static DependencyObject GetParent(DependencyObject reference);
...
}
Let’s try to implement this simple scenario.
public static class MyDependencyObjectExtensions
{
public static T GetVisualParent<T>(this DependencyObject source)
where T : class
{
do
{
source = VisualTreeHelper.GetParent(source);
} while (!(source is T) && (source != null));
return source as T;
}
}
We can now call:
var lbi = button1.GetVisualParent<ListBoxItem>();
Now let’s try to think in a Linq way. From a given control in the control tree, we can consider all its graphical parents until the root (Page or Window) to be an iteration. In this first implementation we are classically using a while loop to retrieve each parent one by one. This is exactly the definition of an IEnumerable.
In a first step we will just try to retrieve the list of parents :
public static class MyDependencyObjectExtensions
{
public static IEnumerable<DependencyObject>
GetVisualParents(this DependencyObject source)
{
while (true)
{
source = VisualTreeHelper.GetParent(source);
if (source != null)
yield return source;
else
yield break;
}
}
}
Now we have a IEnumerable (a Linq compatible source), we can look for the first parent of a T type using Linq !
I have also created a version allowing to define the depth of your query in case you don’t want to find the first T element but a further one.
public static class MyDependencyObjectExtensions
{
public static IEnumerable<DependencyObject>
GetVisualParents(this DependencyObject source)
{
while (true)
{
source = VisualTreeHelper.GetParent(source);
if (source != null)
yield return source;
else
yield break;
}
}
public static T GetVisualParent<T>(this DependencyObject source)
where T : class
{
return source.GetVisualParent<T>(0);
}
public static T GetVisualParent<T>(this DependencyObject source, int level)
where T : class
{
return source.GetVisualParents().OfType<T>().Skip(level).FirstOrDefault();
}
}
We can see how Linq simplifies the implementation and the composition possibilities.
Last effort, let’s try to imagine a generic way to create enumerables from functional parts.
In our case we have :
- the starting value
- the way we get the next value
- the exit condition
public static class EnumerableHelper
{
public static IEnumerable<T> Create<T>(T startElement,
Func<T, T> nextElementProvider, Predicate<T> exitCondition)
{
while (true)
{
startElement = nextElementProvider(startElement);
if (!exitCondition(startElement))
yield return startElement;
else
yield break;
}
}
public static IEnumerable<T> Create<T>(T startElement,
Func<T, T> nextElementProvider)
{
return Create(startElement, nextElementProvider, e => e == null);
}
}
We can now call :
var parents = EnumerableHelper.Create(
button1 as DependencyObject,
d => VisualTreeHelper.GetParent(d));
var lbi = parents.OfType<ListBoxItem>().FirstOrDefault();
You can notice how generic is the Create method. From the caller view the Enumerable is created “on demand”.
Comments
- Anonymous
June 18, 2010
The comment has been removed - Anonymous
June 18, 2010
No, I did not even knew it was now on codeplex. Kevin Moore, is that you ?!