| William 的个人资料So long, and thanks for ...日志列表 | 帮助 |
|
7月28日 Attached Read-only Collection Dependency PropertiesOh 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:
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. 评论 (9)
引用通告此日志的引用通告 URL 是: http://wekempf.spaces.live.com/blog/cns!D18C3EC06EA971CF!468.trak 引用此项的网络日志
|
|
|