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
I found some other markup extensions discussed here (http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1772475&amp;SiteID=1) and here (http://rrelyea.spaces.live.com/blog/cns!167AD7A5AB58D5FE!2260.trak) that provide similar functionality by leveraging name resolution. These provide handy ways to look up named elements. But they will not work for unnamed objects or non-DOs. Nor will they work across namescopes. But it’s great to hear that this functionality is planned for a future release. 🙂
Impressive!!!
When I read your posts I feel like I know nothing about WPF!
You are too much. Can you please give me a small piece of you knowledge? 🙂
Joking apart good job, nicely done!
Regards
Hi Dr.Wpf
I have some problem when Binding a validation like this:
<TextBox DockPanel.Dock=”Left” Width=”160″ Validation.ErrorTemplate=”{StaticResource validationTemplate}”>
<Binding UpdateSourceTrigger=”PropertyChanged” >
<Binding.ValidationRules>
<val:AgeValidationRule Min=”0″ Max=”180″ />
</Binding.ValidationRules>
</Binding>
</TextBox>
but I want to set the next TextBox
Hi Ykey,
The ValidationRule base class is not a dependency object and it is not a member of the visual tree, so you cannot create a “binding” for its Min property. You could create a separate rule instance for each textbox, but any such trick will be a hack.
The good news is that .NET 3.5 introduces a much better solution via its IDataErrorInfo support.
Cheers,
-dw
In your example you have used a user control as object instance. Can I use it with other business objects?
For example I have an object objA, which needs to be passed as a parameter to method in objectDataProvider. Further one of my text box control is bind to objA.
How can I achieve this by using the new markup?
Hi Madhukar,
This markup extension is more about referencing other objects declared in markup. If your business object is declared in markup, then you can use this extension to reference it.
Cheers,
-dw
I’m sorry but your blog is very poorly set out and difficult to read.
Apology accepted, Paul.