Oh wow! This is one I’ve been wanting almost since the day I started learning WPF. It seemed like such a huge thing missing from WPF. How in the heck do you manage to make an attached read-only dependency property with a collection type?
At first blush, many of you are thinking "What’s the big deal? Attached read-only dependency properties are clearly documented and easy to implement." Very true. But if you’re thinking that, you’re glossing over the critical bit here: the type of this property is supposed to be a collection. "So what?" is your response? Well, smarty, how do you initialize that property? See, if we rely on the dependency property’s "default value" mechanism, since this is an attached property every instance will have that value. Now, what you need to be able to do here is lazily create the collection on first access. However, WPF doesn’t provide you with any callbacks for retrieving the value, only for setting the value, and the XAML parser bypasses the CLR "get wrapper". So, there’s no place to lazily instantiate the collection. I searched high and low for a solution here. I even talked directly with several Microsoft WPF developers. I think I may have even made an official request for an event to hook into for this purpose. In the end, though, I was left wanting.
That is, until today. John Gossman e-mailed me today with a solution to this problem that works with the current WPF system. Holy smokes!
The key to his solution lies with the fact that I didn’t fully understand WPF (because this something that’s implied and not spelled out in the documentation). Remember when I said the XAML parser bypasses the CLR "get wrapper"? Well, that’s not always true. It seems that if you register the dependency property with a "name" that differs from the CLR name, the XAML parser will call your CLR "get wrapper" instead of bypassing it and going directly to GetValue. This may not make a lot of sense with out an example. Here’s such an example (I’m not including the code for the Foo type, as it’s not really relevant):
public class FooCollection : ObservableCollection<Foo> { public static readonly DependencyPropertyKey InstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly("InstanceInternal", typeof(FooCollection), typeof(FooCollection), new UIPropertyMetadata(null)); public static readonly DependencyProperty InstanceProperty = InstancePropertyKey.DependencyProperty; private static void SetInstance(DependencyObject obj, FooCollection value) { obj.SetValue(FooCollection.InstancePropertyKey, value); } public static FooCollection GetInstance(DependencyObject obj) { if (obj == null) throw new ArgumentNullException("obj"); FooCollection foos = obj.GetValue(FooCollection.InstanceProperty) as FooCollection; if (foos == null) { foos = new FooCollection(); SetInstance(obj, foos); } return foos; } }
The key above is the first parameter passed to RegisterAttachedReadOnly: "InstanceInternal". Since the name is different than the implied CLR name ("Instance"), the XAML parser will now call our GetInstance method, which does the lazy creation. With this in place, the following XAML code actually works (it doesn’t do anything useful, mind you, as this isn’t a meaningful example beyond illustrating the concept of lazily creating the read-only attached collection dependency property).
<Window x:Class="WpfAttachedCollection.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:WpfAttachedCollection" Title="Window1" Height="300" Width="300"> <ui:FooCollection.Instance> <ui:Foo Name="Bar"/> </ui:FooCollection.Instance> <Grid> </Grid> </Window>
This is one case where the VisualStudio designer fails me. I get an error "The attachable property ‘Instance’ was not found in type ‘FooCollection’." I’m not sure what the cause of the failure here is, but when I running the executable, it actually does work. A little more work and I can add a button that displays the added Foo objects in a MessageBox.Show, just to prove to myself that everything is working.
Like I said, this isn’t exactly documented in the MSDN. It’s only implied. The relevant documentation given to me by Mr. Gossman is this:
Implications for Custom Dependency Properties
Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.
Similarly, other aspects of the XAML processor that obtain property values from XAML processing also use GetValue rather than using the wrapper. Therefore, you should also avoid any additional implementation in the get definition beyond the GetValue call.
The following example is a recommended dependency property definition with wrappers, where the property identifier is stored as a public static readonly field, and the get and set definitions contain no code beyond the necessary property system methods that define the dependency property backing.
Mr. Gossman assures me the behavior here won’t change, because it’s relied upon by too much existing code. So, maybe it will be documented in the future, but for now you can feel safe in make use of this "hack". I owe Mr. Gossman a huge thank you for providing this to me, and I’m very happy to provide a post here for future Googlers.
Update: Looks like Mr. Gossman blogged about this as well.
Still not sure if I see the problem here. You could assign the collection property in the constructor, but yes, that would mean creating it when the object is created rather than when it is first needed. An empty collection really shouldn\’t be that much overhead, though. If you need to populate the collection, and populating it is expensive, then that shouldn\’t stop you creating it. Just have it populate in GetEnumerator or raise a callback event for the owner to populate. I guess this is just a problem I\’ve never come up against when using WPF. Also, I\’d be scared of relying on tricks like this. When I call blah.GetValue(Blah.InstanceProperty) on your code and I start getting strange NullReferenceExceptions, I\’ll know why 🙂
Paul,
These are attached properties. The object they are attached to has no knowledge the property exists. This is an advanced concept from WPF, and has many important uses.
So, the problem isn\’t one of space. Nor is it about the expense of populating the collection. The problem is in when to create the collection. Either the user has to do it, adding "clutter" to the XAML markup, or you need to lazily create it. In the example of the FooCollection above, with out this trick you\’d have to write the XAML like this:
<Window x:Class="WpfAttachedCollection.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:WpfAttachedCollection" Title="Window1"Height="300"Width="300"> <ui:FooCollection.Instance> <ui:FooCollection> <ui:Foo Name="Bar"/> </ui:FooCollection> </ui:FooCollection.Instance> <Grid> </Grid></Window>
Not the end of the world, but the extra code required here does lead to harder to code and understand XAML.
YOU NEED A DEMO PROJECT ONE CAN DOWNLOAD TO SEE WHAT YOU OTHER TALKING AOTWITH SOURCE CODE AND A DEMO PROJECT YOU POSTING IN NOTHIN ABOUT A BUNCH OG CRAPOLA MUCH LIKE YOU MAMA
THIS POSTING IS WORTHLESS AND CRAP WHAT SHIT FOR BRAINS POSTED THIS
No name,I know you\’re a troll, but… if you can\’t create a project that contains the code above and then compile it and run it, maybe you should reconsider coding, whether it be a hobby or a profession. This concept isn\’t complicated enough to warrant a full project. In fact, all the extra code required for a project would do nothing but to obscure the important information in the code I posted here.That\’s enough of a response from me. Any other attempts at trolling my blog shall be ignored.
I agree with you William, don\’t let people like that to annoy you. Keep up the great work. This was a great post and it may be very important for an advanced developer to know about this!
What I don\’t get is… what would be the problem in creating the collection when defining the default value? Instead of null the default could be "new FooCollection()".Is there something I\’m not taking into account?
Mmm… now that I think about it you could need to assign an owner to the collection or some other stuff like that, it\’s not only about craeting the object. Besides we would still need to assign (set) the created object to the property or it would usually be garbage collected. Since we\’d need to place code on the GetValue accessor anyway better to lazily create the collection.
This is a great solution and i found it working in the CommandBehavior v2. The only bug I see is using these Properties in a Style. You\’ll get the error "cannot convert to DependencyProperty" and so on. This always occurs when the name in the RegisterAttached call differs from the Get- and Set- Methods. In conclusion the access to the properties in XAML differs from that in Styles? I\’m not sure about my thought.Is there any solution for this?
It also helps solve the problem of determining when the items in the collection have changed. When the collection is set in XAML, you get a property changed call and then you can hook up to some collection changed event.However, using this prevents you from doing binding… if you try you get a message saying the property is read only.
Really Really very nice..!!