Smart editor declarations in the WPF Property Grid
We’ve had a couple of customers recently asking about assigning editors in the WPF Property Grid at runtime rather than in XAML. For this article, I’m going to borrow a scenario from one of our forum participants. He’s using an ItemsSource dictionary to populate the grid and wants to hook up every “image” entry to a custom editor he’s developed. Because he can’t know the list of “image” entries at compile time, he’s programmatically adding PropertyEditors to the PropertyGrid.Editors collection as he builds his dictionary: whenever he sees a dictionary entry go past that needs his image editor, he creates a PropertyEditor, sets the PropertyName to the dictionary entry name, and adds it to the grid.
This is a good working solution, and probably the best available using the standard editor types. But he got me wondering if there was a way to make the grid work a bit harder to help him. There’s nothing inherently wrong with procedural code, but it feels a bit antithetical to the usual WPF idioms of declarative XAML and data binding. There’s potentially a minor scalability issue if you end up adding zillions of editors (we’ll see why below). And on a more practical level, having to add editors to the grid as you add entries to the dictionary means that you have to guard access to the dictionary, whereas the data binding idiom says you should be able to add stuff to the dictionary from wherever it’s appropriate, and let the UI layer worry about hooking up the UI.
In order to understand how to achieve this, we need to understand something of how the WPF Property Grid editor infrastructure hangs together. An “editor”, in WPF Property Grid terminology, is actually a declaration that tells the grid to use a certain DataTemplate for certain nodes. A TypeEditor tells the grid to use a certain DataTemplate if the property value is of a certain type; a PropertyEditor tells the grid to use a certain DataTemplate if the property has a certain name. When the WPF Property Grid needs to select a DataTemplate for a node, it goes through the list of editors in its Editors collection until it finds one that can edit the node in question. If it doesn’t find one in the Editors collection, it falls back on some internal logic to select one of the built-in editors. (This, by the way, is why adding zillions of editors might slow things down: for each node in the grid, the grid has to perform a linear search of the Editors collection. But you’d have to add quite a lot of editors for that to be an issue.)
Now, as it happens, the WPF Property Grid has no idea that TypeEditors know about types or that PropertyEditors know about property names: it just asks each editor “can you edit this node?” until it finds one that says “yes,” at which point it says, “give me a DataTemplate for it then.” So if we could create an editor class that answered “yes” to “image” nodes, we wouldn’t need individual PropertyEditor declarations for each image entry. And if we look at the documentation it turns out that the Editor base class does expose the CanEdit method, allowing you to override it to create your own custom hookups.
Before we rush in and start deriving classes from Editor, though, it’s worth glancing at the Editor hierarchy. This is because the other abstract method on Editor, BuildTemplate, is much too tedious to implement ourselves. (It’s not just a matter of returning EditorTemplate.) PropertyEditor doesn’t inherit directly from Editor, but from ObjectWrappingEditor. Since our scenario is motivated by a wish to be “like a PropertyEditor only smarter,” and since ObjectWrappingEditor provides an implementation of BuildTemplate for us, that will actually be a good starting point for us as well.
Finally, let’s write some code, or at least let’s get Visual Studio to write it for us:
public class PropertyNameContainsEditor : ObjectWrappingEditor { protected override void SetContentTemplate(FrameworkElementFactory factory, Node node) { throw new NotImplementedException(); } public override bool CanEdit(Node node) { throw new NotImplementedException(); } }
We have two abstract methods that we need to override: CanEdit and SetContentTemplate.
CanEdit is pretty obvious, though you do need to bear in mind that the method can be called for any and every node in the grid, so don’t make any assumptions about what you’re being passed, and don’t write anything that runs too slow. For our scenario, we have a dead simple criterion: if the name contains the word “image,” we’re interested. We’ll actually make our editor a bit more flexible so we can reuse it (with different templates of course) for other magic words such as “email” or “URL”:
public string ContainedWord { get; set; } public override bool CanEdit(Node node) { // Ignoring issues of casing, culture, etc. for simplicity return node.HumanName.Contains(ContainedWord); }
SetContentTemplate is a bit more mysterious. The documentation says it “attaches the editor data template to the EditorTemplate.” What this actually means is that the ObjectWrappingEditor has built a bunch of plumbing and now wants you to bolt the DataTemplate specified in EditorTemplate into that plumbing. This is a wart: the reason it exists is because of some internal components that use the ObjectWrappingEditor infrastructure but don’t use the EditorTemplate property. For our purposes, we always want to use the DataTemplate specified in EditorTemplate: that’s what it’s there for, after all. This requires the following cryptic incantation:
protected override void SetContentTemplate(FrameworkElementFactory factory, Node node) { factory.SetValue(ContentControl.ContentTemplateProperty, EditorTemplate); }
Don’t worry too much about what this is doing: just shove it in a base class and forget about it. We’ll probably provide such a base class ourselves in a future release or sample.
And that’s it for the editor definition. Now we can hook it up in the same way as we hook up TypeEditors and PropertyEditors:
<ms:PropertyGrid AllowModifyCollections="False"> <ms:PropertyGrid.Editors> <local:PropertyNameContainsEditor ContainedWord="Image" EditorTemplate="{StaticResource ImageSelector}" /> </ms:PropertyGrid.Editors> </ms:PropertyGrid>
Here’s a picture of our smart editor doing its stuff:

You can of course take this approach still further. One idea that offers some interesting possibilities is mixing it with smart dictionary keys. For example, another forum participant asked about selectively making dictionary entries read-only. You could do this using a smart dictionary key to carry the IsReadOnly property, and have your CanEdit method examine node.IndexedPropertyArguments[ 0 ] (watch out: the grid may spring hidden nodes on you which don’t have any indexed arguments, so only do this inside a guard clause or an exception handler). If you’re primarily driving the grid off dynamic data via ItemsSource rather than off a static set of properties using SelectedObject, then smart editors are a handy technique to have around!
4 Responses to “Smart editor declarations in the WPF Property Grid”
Leave a Reply
Categories
BrainDump (1)
Community Code (4)
Events (16)
F# (14)
General (53)
Lab Samples (2)
LightSpeed (268)
MegaPack (8)
News (71)
NHibernate Designer (26)
Nightly news (52)
Phone Elements (24)
Products (87)
Projects (5)
Screencast (6)
SharePoint (3)
Silverlight (14)
Silverlight Elements (66)
SimpleDB Management Tools (20)
Visual Studio (9)
VS File Explorer (7)
Web Workbench (39)
WPF (44)
WPF Diagrams (57)
WPF Elements (110)
WPF Property Grid (32)


Tagged as

Posted by Ivan Towlson on 30 April 2008 



Excellent blog Ivan! I had wondered about an easier way to do this. This is much more elegant…
Thanks a lot Ivan!
i tried your recommendation and wrote a DataTemplate to involve a OpenFileDialog to edit a property.
…
But could you please give me a hint, how to get the Click event of the button ?
Thanks in advance
joerg
If your DataTemplate is defined in your Window.Resources section, you can just hook up the Click event in the normal way. There is an example of this in the forums at http://www.mindscape.co.nz/forums/Thread.aspx?ThreadID=1206. You may also find the thread at http://www.mindscape.co.nz/forums/Thread.aspx?ThreadID=1202 useful too.
If your DataTemplate is defined in a separate resource dictionary, it is a little bit trickier because a ResourceDictionary does not have a codebehind class where you can put your FileOpenDialog call. In this case I would suggest having the button send a Command rather than wiring up the Click event directly, and handle the command in the window where you instantiate the grid.
[...] solution has three components: a smart editor to wire up the attributed types to a data template, a data template to display the “show [...]