Design Time Adorners

I've posted about design time adorners a few times in the past however there have been some API changes since then so I thought I'd do a fresh post.

What are design time adorners?

Adorners are WPF elements that you can put on the Cider design surface when your custom control is instantiated and selected.  The end user who is using Cider can interact with your adorners to make updates to the XAML.

For example, the selection handles, grid lines and rails when a Grid is selected are put on the design surface through the same adorner infrastructure I am describing in this post.

Create an AdornerProvider

The first step is to create an AdornerProvider that will add your adorners when your control is selected.  You will associate this AdornerProvider to your control using the Metadata Store as described in this post and this post.

public class MyCustomAdornerProvider : PrimarySelectionAdornerProvider {
Public MyCustomAdornerProvider () {
    Button buttonAdorner = new Button();
    buttonAdorner.Background = Brushes.Red;
    AdornerPanel panel = new AdornerPanel();
    panel.Children.Add(buttonAdorner);
    Adorners.Add(panel);
} (...) }

The key things to point out here are:

  • MyCustomAdornerProvider derives from PrimarySelectionAdornderProvider.  This means the adorners will light up when the control MyCustomAdornerProvder is applied to is selected.
  • Adorners are added to the surface by adding them to the Adorners property from the base class
  • An AdornerPanel is provides a lot of size/positioning help.  You don't strictly need it, an adorner added directly to the Adorners collection will show up in the upper left hand corner of the control it is associated with. 

Add UIElements (the Adorners) to an AdornerPanel

Setup the AdornerPlacementCollection with Position and Size Methods that are additive (take a factor + offset):

  • PositionRelativeToAdorner[Height/Width]
  • PositionRelationToContent[Height/Width]
  • SizeRelativeToAdornerDesired[Height/Width]
  • SizeRelativeToContent[Height/Width]

In this API, "Content" refers to the control being adorned as its layed out on the design surface at 1X zoom.

For example, to setup a button that will be above and to the left of the associated control when it is selected, you would do the following: 

AdornerPlacementCollection placement = new AdornerPlacementCollection();
placement.SizeRelativeToAdornerDesiredHeight(1.0, 0);
placement.SizeRelativeToAdornerDesiredWidth(1.0, 0);
placement.PositionRelativeToAdornerHeight(-1, -NudgeAmount);
placement.PositionRelativeToAdornerWidth(-1, -NudgeAmount);
AdornerPanel.SetPlacements(adorner, placement);

To control how your adorners will act when zooming in and out, you can set the stretch properties on the AdornerPanel:

AdornerPanel.Set[Vertical/Horizontal]Stretch

Handle the interaction with your Adorners (handle events or setup tasks)

For a lot of adorners that you will put on the design surface, handling the interaction is as simple as handling the events on the adorner itself.  For example:

BouncyButton adorner = new BouncyButton();
adorner.Click += new RoutedEventHandler(OnButtonClick);

And in OnButtonClick() you can make changes to the XAML by working through the Editing Object Model.  The following sample code shows how to get the ModelItem for a given adorner and modify one of its properties through the ModelProperty class.

ModelItem adornedElementModelItem = AdornerProperties.GetModel(_ColorsListControl);
adornedElementModelItem.Properties[Control.BackgroundProperty].SetValue(new SolidColorBrush(_ColorsListControl.SelectedColor));

Hook up your AdornerProvider to the control via the FeatureAttribute and MetadataStore

The other thing you can do is use Tasks.  A Task associates a collection of input gestures that are bound to commands and the handlers for those commands.  Cider defines a set of ToolGestures that you can use as input gestures and thus handle -- in other words you can handle a click gesture on a Rectangle even though the Rectangle doesn't have a Click event.

Task clickTask = new Task();
clickTask.ToolCommandBindings.Add(new ToolCommandBinding(_ClickCommand, OnRectangleClick));
clickTask.InputBindings.Add(new InputBinding(_ClickCommand, new ToolGesture(ToolAction.Click)));
AdornerPanel.SetTask(adorner, clickTask);

And as before, in OnRectangleClick() you can use the Editing Model (ModelItem, ModelProperty) to update the XAML from your adorner.

Setup the Metadata

Finally, in your Metadata Assembly, you will set up the metadata to associate your AdornerProvider to the control you want it to show up for:

builder.AddCustomAttributes(typeof(ButtonWithDesignTime), new FeatureAttribute(typeof(PopupButtonAdornerProvider)));

Finally

I've attached a sample to this blog post that shows all of the concepts above and will work on beta 2 and RTM.

DesignTimeAdornerSample.zip