Archive for the ‘Markup Extensions’ Category

Tips and Tricks: Making Value Converters More Accessible in Markup

Tuesday, March 17th, 2009

I recently had the pleasure of presenting some rather advanced WPF concepts (along with some cool tips and tricks) to a group of smart folks.  Unfortunately, I ran up against time constraints and was forced to drop the data binding portion of my talk.  I’m a little bummed about not being able to show one cool trick that I use a lot, so here it is…

Call me lazy (really, I’m okay with it), but I’ve never been wild about having to declare my value converters as resources before using them.  It’s just one extra step:

<Window.Resources>
  <src:MyConverter x:Key="MyConverter" />
</Window.Resources>

And then later the converter must be specified using a StaticResource reference, which incurs some overhead (albeit small overhead, in the grand scheme of things) for the resource resolution:

<TextBlock Text="{Binding SomePath, Converter={StaticResource MyConverter}}" />

I often choose to skip the middleman.  Instead of using the StaticResource markup extension to look up a converter, I simply derive my value converter, itself, from MarkupExtension.  Then I return an instance of it in the ProvideValue() override.

I have to assume there are plenty of others using this approach too.  (It’s clever, afterall, but that’s about the extent of the hyperbole I’d grant it.)  If you’re not one of them, I simply wanted to put it out there as a potential trick for your arsenal.

Below is a super simple example of a dummy converter (typically used to debug bindings) that does this:

public class DummyConverter : MarkupExtension, IValueConverter
{
    private static DummyConverter _converter = null;
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (_converter == null)
        {
            _converter = new DummyConverter();
        }
        return _converter;
    }
    #region IValueConverter Members
    public object Convert(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        return value; // set breakpoint here to debug your binding
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        return value;
    }
    #endregion
}

The above class creates a singleton dummy converter that can be used across my app without any resource lookup.  The XAML usage is now simply this:

<TextBlock Text="{Binding SomePath, Converter={src:DummyConverter}}" />

This is much better, to my lazy way of thinking.

Supplying an Object Reference in the ConstructorParameters Collection of an ObjectDataProvider

Sunday, September 2nd, 2007

Dear Dr. WPF,

I have a .NET 2.0 data collection object that aggregates RSS feeds and maintains the incoming posts in an in-memory collection (as well as saving them to a data store). The class has no associated UI… it just provides the data as well as a few events that can be used to filter (accept/reject) posts as they arrive.  I have used it quite successfully in several different applications.

I recently decided to enhance the class to better support .NET 3.0 scenarios.  As such, the posts are now maintained in an observable collection, which works great for databinding.  But I would also like to support the routed event model and rather than requiring consumers of my object to attach event handlers, like in the 2.0 version, I’d like to have the object raise bubbled events.

This is where the trouble comes…  As mentioned earlier, the control has no knowledge of the UI.  It just knows about the data.  In order to raise routed events, it needs a target element.  To support this, I’ve created a constructor that accepts a UIElement, and if present, I will raise events on that element.  This works fine when I create my data object in code, but I would like to support creating it in XAML using an ObjectDataProvider.

Here is what I would like to do:

<Grid Name="RssGrid">
  <Grid.Resources>
    <ObjectDataProvider x:Key="RssData" 
        ObjectType="{x:Type rss:RssFeeder}">
      <ObjectDataProvider.ConstructorParameters>
        <Binding ElementName="RssGrid" />
      </ObjectDataProvider.ConstructorParameters>
    </ObjectDataProvider>
  </Grid.Resources>
  <ListBox DataSource="{StaticResource RssData}"
      ItemsSource="{Binding Path=RecentPosts}" ... />
</Grid>

But this doesn’t work because bindings can only target dependency properties.  The ConstructorParameters collection is a CLR property.

Is there any way to do this in XAML?

Sincerely,
Marie
 


Hi Marie,

It’s funny how the same question will pop up all at once from several different sources.  Yours is one such example.  I have received variations of this same question from no less than 4 different people within the last month.  It typically takes one of the following forms:

In markup, how do I pass an instance of an object …

1.  in the ConstructorParameters of an ObjectDataProvider?

2.  as the ConverterParameter of a Binding?

3.  as the value of a CLR property (that is not backed by a dependency property)?

And just last week, I saw this version of the same question (very similar to your scenario) in the WPF forum on the MSDN site.  In my response to that post, I said I would blog about an approach that I sometimes use to do this.  I think this solution will work nicely for you also.  You’ll find the promised details below.

Best regards,
Dr. WPF

 

Introducing the ObjectReference Markup Extension

First, let me say that although this solution can be used as an answer to all 3 of the scenarios described above, it’s not always the most appropriate solution.  In this earlier post, I provided a MultiBinding approach that I believe is often a more dynamic answer for question 2.  And question 3 can sometimes be better accommodated by using a one-way-to-source (or two-way) binding on another property wherein the source (the CLR property) essentially becomes the target.  I will likely blog about that approach in a future post.

Enough blather.

So how do you pass an object instance to the ConstructorParameters collection of the ObjectDataProvider?  The short answer is…

It Can’t Be Done

… using the native XAML support in 3.0 or 3.5.  More specifically, unless the object is a resource, there is no way to directly reference it in markup.  If the target property is a dependency property on a dependency object within the visual tree, then a binding can be used to create an indirect reference to the object.  But in all of the cases above, the target is not a dependency property, so this won’t work.

One strength of WPF is that it’s more than just a framework… it’s an extensible “platform”.  So what can we do?  We can extend markup!

A New Markup Extension

To solve the problem at hand, I introduce to you a markup extension I’ve created called ObjectReference.  You can use this extension in your projects by adding this small code file to the project. 

You are certainly already familiar with a number of native WPF markup extensions:  {Binding}, {TemplateBinding}, {StaticResource}, {DynamicResource}, {x:Null}, {x:Static}, {x:Type}, {x:Array}, etc.  Just like all of these classes, the ObjectReference class derives from the MarkupExtension base class and provides extended functionality for XAML-based scenarios. 

Usage in Markup

The ObjectReference extension can be used in markup in two ways:  first as a “declaration” on an object and second as a “reference” to an earlier declaration.  Both of these uses can be seen in the following example:

<Grid xmlns:dw="clr-namespace:DrWPF.Markup"
    dw:ObjectReference.Declaration="{dw:ObjectReference RssGrid}" >
  <Grid.Resources>
    <ObjectDataProvider x:Key="RssData" 
        ObjectType="{x:Type rss:RssFeeder}">
      <ObjectDataProvider.ConstructorParameters>
        <dw:ObjectReference Key="RssGrid" />
      </ObjectDataProvider.ConstructorParameters>
    </ObjectDataProvider>
  </Grid.Resources>
  <ListBox DataSource="{StaticResource RssData}"
      ItemsSource="{Binding Path=RecentPosts}" ... />
</Grid>

Note the use of an attached Declaration property to declare the object reference.  This is one method of declaring a reference on a dependency object.  In this case, a reference is created for the Grid object and it is assigned a Key value using the string “RssGrid”.

Later, the object is referenced in the ConstructorParameters collection by using a second ObjectReference with the same Key value.

If the target object is not a dependency object, you can declare a reference by setting an existing property using the ObjectReference extension.  In this case, you must set the IsDeclaration property to true when creating the ObjectReference, as shown here:

<winforms:MonthCalendar 
    Name="{dw:ObjectReference mc, IsDeclaration=True}" >

Because IsDeclaration is true, the reference is treated as a declaration for the MonthCalendar object.  In this case, the supplied key value of “mc” is passed straight through to the Name property.  It is essentially the same as saying Name=mc, except that it also creates a reference to the object.

Download a Sample

If you’ve examined the code in the sample I posted earlier in my forum response, note that it is slightly different that the version described herein.  I have updated the extension to make it fit for wide consumption and more applicable to other scenarios, such as referencing non-dependency objects.  You can download the updated ObjectReference sample here.

How it works

Let’s take a look at how this ObjectReference markup extension works…

Like any markup extension, our class derives from MarkupExtension and overrides the ProvideValue() method to return a resolved value.  The IServiceProvider parameter supplied to this method provides access to the parser’s context and allows us to know things such as which property the extension is targeting and the exact instance of the object that owns the property.  This is very helpful for our purposes.

If you look at the code in the ProvideValue() function, you’ll see that it first checks to see if we’re creating a declaration or a reference.  If it’s a declaration, then a reference to the target object is added to a static dictionary of weak references using the Key property as the dictionary key for the reference.  In this case, the Key value is returned as the result of the ProvideValue() function.  If the ObjectReference is being used as a reference (rather than a declaration), the ProvideValue() function looks up the supplied Key in the dictionary and returns the object stored earlier.

Sidenote:  Since we are maintaining references in a static dictionary, it is important that we only keep weak references to the objects.  Otherwise, our references would keep the objects alive indefinitely.  We certainly don’t want to leak objects.

Limitations

There are definitely some limitations to be aware of with this approach. 

Parse order matters

First and foremost, this solution is very dependent on the order in which the markup is parsed.  The declaration must be parsed before the reference.  Keep in mind that XAML is parsed from top to bottom.  This is similar to the way static resource references work, but not exactly.  Static resources are resolved based on the tree of elements, whereas, with our extension, it is purely based on parse order.  The reference can appear in another tree entirely and, as long as that tree is parsed first, the reference will resolve.

It’s not a binding

Second, note that the reference is not a binding.  It is resolved exactly once at parse time and it resolves based on the values in the static dictionary at that point in time.  If you change a reference later, it will have no effect on the earlier returned object.

References will be overwritten

Also, note that a reference will be overwritten if another declaration is parsed having the same Key value.  Most of the time, this will be the desired behavior.  For example, if you parse a XAML page that contains a declaration on its root element and references in the same page, you’ll want the references to refer to the declaration for that instance of the page.  There is no way to get at the earlier references after they’ve been replaced.

The ObjectReference class must be in the project

You must include the ObjectReference.cs file in the project.  If you use ObjectReference extensions in loose XAML files, other parsers may not be able to parse the files.  To work around this limitation, you could compile a separate assembly including the extension and include it alongside the loose XAML.  The xmlns namespace mapping in the XAML files would need to reference the assembly containing the ObjectReference class.

The reference dictionary is never compacted

To clean up this extension, I really should periodically walk the dictionary of weak references and remove entries for objects that no longer exist.  So far, I’ve just been too lazy to do that.  You’ll note that I’m providing the ObjectReference.cs file under a BSD open source license, so feel free to make any such improvements that you see fit.  J

Let me know how it works for you!

Please let me know in what creative ways you are able to use this markup extension.

Cheers,
Dr. WPF