Smart editors for the WPF Property Grid meet smart templates

One question that we occasionally get asked about the WPF Property Grid is whether it supports the Windows Forms EditorAttribute/UITypeEditor model. The answer is that it doesn’t, partly because the way our editors work is quite different to the WinForms grid, and partly because we’re a bit dubious about putting presentation metadata on your business objects. Nevertheless, when you’re dealing with extensibility scenarios where you’re having to load and edit unknown types, having those unknown types bring their own editors to the party is pretty much the only convenient way to go. So how can you do something like this with the WPF Property Grid?

Another question we often get asked is about how to display popup dialogs in an editor. We’ve covered this in the forums and in the 2.0 help file, but a natural follow-up question to this is, “Okay, but how do I create a generic version of this so I don’t have to write code-behind each time I have a different dialog I want to display?” Well, you would need to create a generic data template, and then somehow tell it the type of dialog for any given instance. How would you do that?

Here’s a technique that addresses both of these cases. We’re going to use a generic “text block plus ellipses button” for properties of any type that is marked with a specific attribute (which we’ll call EditorDialogTypeAttribute), but we’re going to have the actual dialog depend on the specific property being edited.

For simplicity’s sake, we’ll assume that editor dialogs are always going to be WPF Windows, not Windows Forms Forms, and that they’ll receive the edited object via their DataContext (as opposed to, say, via a custom property or a constructor parameter). We’ll also assume that the attribute is going to be on the type rather than the property. It’s easy to extend the sample to handle these other cases, but showing it would just clutter things up. Finally, we’re going to assume that we’re always editing reference types, and that we’re never going to have to deal with a null reference (we’ll assume that the constructor has initialised the property to a suitable default). It’s possible to extend the sample to handle null references, but a bit more fiddly.

The solution has three components: a smart editor to wire up the attributed types to a data template, a data template to display the “show dialog” UI (an ellipses button) and some code-behind to actually show the dialog.

Let’s start with the smart editor. We said we were interested in anything marked up with the EditorDialogTypeAttribute, so the implementation is pretty easy:

public class DialogTemplateSmartEditor : ObjectWrappingEditor
{
  public override bool CanEdit(Node node)
  {
    // the actual editor logic
    return node.Property.IsDefined(typeof(EditorDialogTypeAttribute), true);
  }
 
  protected override void SetContentTemplate(FrameworkElementFactory factory, Node node)
  {
    // magic incantation used in all smart editors
    factory.SetValue(ContentControl.ContentTemplateProperty, EditorTemplate);
  }
}

Now we need to create a data template that displays the ellipses button, and to hook that up to the grid using the smart editor:

<DataTemplate x:Key="PopupEditor">
  <DockPanel>
    <Button DockPanel.Dock="Right" Padding="0,-2,0,-2">...</Button>
    <TextBlock Text="{Binding Value}" />  <!-- ugly but simple -->
  </DockPanel>
</DataTemplate>
 
<ms:PropertyGrid.Editors>
  <local:DialogTemplateSmartEditor EditorTemplate="{StaticResource PopupEditor}" />
</ms:PropertyGrid.Editors>

Finally, we need some code to handle the ellipses button. Firstly we’ll add a Tag to the button so that we can pick up the edited value from the Click handler, and we’ll also attach a Click handler:

<Button DockPanel.Dock="Right" Padding="0,-2,0,-2"
        Tag="{Binding Value}"
        Click="PopupEditor_PopupButtonClick">...</Button>

And the code itself just looks for the attribute, instantiates the dialog and sets its DataContext, and shows the dialog:

private void PopupEditor_PopupButtonClick(object sender, RoutedEventArgs e)
{
  object bound = ((Button)sender).Tag;
  EditorDialogTypeAttribute attr = (EditorDialogTypeAttribute)(bound.GetType().GetCustomAttributes(typeof(EditorDialogTypeAttribute), true)[0]);
  Type editorDialogType = attr.EditorDialogType;
  Window window = Activator.CreateInstance(editorDialogType) as Window;
  if (window != null)
  {
    window.DataContext = bound;
    window.ShowDialog();  // ignoring OK/Cancel issues for now
  }
}

That’s it! Our property grid can now handle any custom type that is marked up with EditorDialogTypeAttribute:

You can’t see it from the screenshot, but those two buttons will launch different dialogs, each appropriate to the property being edited. Here’s a working version of the above which you can use to try it out.

Finally, a couple of notes on the above.

First, I noted in a comment that I’d punted on allowing the user to cancel out of the dialog. This is because, assuming the dialog is working directly on the editee object, those changes are being applied as the user works, and we’d somehow need to roll the changes back if the user then cancelled. If you want to use this technique and need the user to be able to cancel, your objects need to implement IEditableObject (or a similar custom interface). You would then call BeginEdit before launching the dialog, and EndEdit or CancelEdit after returning from the dialog, depending on the dialog result (OK or Cancel). If you’re using LightSpeed entities as your objects, these automatically implement IEditableObject, as do some of the .NET data classes. Otherwise, you’ll need to come up with your own mechanism.

Second, you may remember that UITypeEditor supports drop-down and in-place editors as well as popup dialogs. If you need this flexibility, one way to do it would be to create three attributes, three smart editors and three generic data templates. Alternatively, you could create one stupendously smart template, with inline, drop-down and popup functionality all present, but shown or hidden depending on the property being edited. It depends on how you want to organise your code and how you want to annotate your properties.

Third, this isn’t the only way to do it. You could also use the EditContext to pass a dialog type from a PropertyEditor declaration to a generic data template. This only works for static scenarios, where the properties and types are known at design time (unless you also use run-time editor selection). You could have your objects implement an IShowEditUI interface, making them responsible for displaying their own editor UI (and thus getting around the assumptions about Windows vs. Forms and DataContexts vs. custom properties or constructor parameters). Once again, the appropriate design will depend on your application’s design needs and constraints. With maybe just a pinch of personal taste.

2 Responses to “Smart editors for the WPF Property Grid meet smart templates”

  • […] Towlson on Smart editors for the WPF Property Grid meet smart templates Possibly related posts: (automatically generated)Why Jquery […]

  • Little off-topic: I really liked your implementation of the Observable class with the generic Set! Probably the neatest solution I have seen for INotifyPRopertyChanged (they should have included the “Set” in their M$ INotify interface:) )

    Thanks for the article – great starting point!

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top