Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Extensibility Series – WPF & Silverlight Design-Time Code Sharing – Part II

, 13 Jan 2010
Rate this:
Please Sign up or sign in to vote.
In my previous post WPF & Silverlight Design-Time Code Sharing – Part I, I introduced our custom controls and the required design-time features.  I also covered how Visual Studio 2010 discovers and loads control design-time assemblies.  In addition, I explained how to implement p

In my previous post WPF & Silverlight Design-Time Code Sharing – Part I, I introduced our custom controls and the required design-time features.  I also covered how Visual Studio 2010 discovers and loads control design-time assemblies.  In addition, I explained how to implement platform neutral WPF & Silverlight type resolution in a common design-time assembly.

In this post I’ll tie the design-time metadata to each of the design-time features.  This post will be more on how you can implement features in your design-times rather than a detailed analysis of the sample solution  since the the sample solution code has detailed comments and a walk-through the code using the Visual Studio Task List as the navigator for the code. 

I have provided many external links in this post and suggest that you read them to ensure you have the required understanding of design-time extensibility for WPF & Silverlight Designer.

I have updated the code download for the WPF & Silverlight Design-Time Code Sharing – Part I.  If you previously downloaded the code, please re-download the updated code from this post or Part I.  I made a change to the FeedbackControlInitializer.cs that I’ll point out in the Control Default Initializer section below along with a few very minor changes to comments.

To follow along, open the sample solution and view the CiderControlsAttributeTableBuilder.cs file in the CiderControls.Common.VisualStudio.Design project.

Metadata Loading Revisited

If you read the referenced MSDN pages in the example code project, you will notice that some MSDN examples load metadata by applying a design-time attribute to the run-time control.  In other words, the design-time metadata is in the control assembly and not in a separate design-time assembly.  These MSDN examples were written to show you that you “can” load metadata this way, but this is not considered a best practice.

For example, view the DefaultInitializer Class MSDN page.  You will notice in article’s example code that the default initializer metadata has been added to the Button’s class declaration using the Feature attribute.

It’s recommended that control developers place their design-time metadata in a design-time assembly and not in the control assembly.  Having design-time metadata prevents your run-time controls from having a reference to Microsoft design assemblies.  Additionally if you do not have separate design-time assemblies, you by-passes the built-in metadata loading feature of allowing Visual Studio and Blend to have common as well as separate code.  Another advantage of having your design-time code in design assemblies is that you can ship new design assemblies without having to ship a new version of your control assembly.

Please read the Metadata Store MSDN topic for a full discussion of metadata.

Control Default Initializer

Control default initializers are the proper way to assign initial design-time values to properties when a control is created on the design surface using the ToolBox.

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata for the Feedback control’s control initializer.

<span style="color:blue;">new </span><span style="color:#2b91af;">FeatureAttribute</span>(<span style="color:blue;">typeof</span>(<span style="color:#2b91af;">FeedbackControlInitializer</span>))

Control default initializers must derive from DefaultInitializer and override the InitializeDefaults method as in the below example.

<span style="color:blue;">using </span>CiderControls.Common.VisualStudio.Design.Infrastructure;
<span style="color:blue;">using </span>Microsoft.Windows.Design.Model;

<span style="color:blue;">namespace </span>CiderControls.Common.VisualStudio.Design.Controls {

  <span style="color:green;">//TODO 14 - FeedbackControlInitializer

  </span><span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">An initializer is called during control creation on the design surface
  </span><span style="color:gray;">/// </span><span style="color:green;">You can set properties on the ModelItem to adjust the default values.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">internal class </span><span style="color:#2b91af;">FeedbackControlInitializer </span>: <span style="color:#2b91af;">DefaultInitializer </span>{

    <span style="color:blue;">public </span>FeedbackControlInitializer() {
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Callback when the designer has created the FeedbackControl.
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">public override void </span>InitializeDefaults(<span style="color:#2b91af;">ModelItem </span>item) {
      <span style="color:blue;">base</span>.InitializeDefaults(item);
      <span style="color:green;">// The below values are set demonstration purposes to show how its done.
      // See how nice the platform neutral PropertyIdentifiers work.
      // ex: MyPlatformTypes.Feedback.CornerRadiusProperty
      </span>item.Properties[<span style="color:#2b91af;">MyPlatformTypes</span>.<span style="color:#2b91af;">Feedback</span>.CornerRadiusProperty].SetValue(<span style="color:#a31515;">"10"</span>);
      item.Properties[<span style="color:#2b91af;">MyPlatformTypes</span>.<span style="color:#2b91af;">Feedback</span>.BorderThicknessProperty].SetValue(<span style="color:#a31515;">"2"</span>);
      item.Properties[<span style="color:#2b91af;">MyPlatformTypes</span>.<span style="color:#2b91af;">Feedback</span>.BorderBrushProperty].SetValue(<span style="color:#a31515;">"LightGray"</span>);
    }
  }
}

You can access a property in the ModelItem.Properties collection by using either the name of the property or by using property identifier as I have done above.  Using a property identifier removes quoted strings from your code and IntelliSense provides easy access to defined property identifiers.

The SetValue method takes a parameter of type object and converts values as required.  Notice how “10” is converted to a CornerRadius, “2” is converted to a Thickness and “LightGray” is converted to a SolidColorBrush object.

ModelItem SetValue Method

In the original code download I had a mistake by trying to set the above CornerRadius value using the syntax .SetValue(10) instead of the correct syntax .SetValue(“10”).  Visual Studio 2010 Beta2 swallowed the exception.  All future versions of Visual Studio 2010 will report this as an exception, which is the correct behavior.  I have updated the code download with the corrected code.

SetValue takes an object as the method parameter.  If a string is passed, a type converter will be used to assign the value as in the above code.  Type converters are also used to convert string values in XAML files to property values.

If a string is not passed, an object that matches the type of the property must be passed.

Example, property type is Integer, using .SetValue(10) or .SetValue(“10”) is correct.

Example, property type is Thickness, using .SetValue(10) is not correct because 10 is not a Thickness.  The correct way to pass this parameter is to use (“10”) which will be converted to a Thickness.  You can also pass in a platform specific Thickness object instead of (“10”).

Control Context Menu

MenuAction

A design-time context menu can be added to controls on the design surface.  You can use context menus for many purposes such as setting control values or opening a dialog.

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata for the Feedback control’s context menu.

<span style="color:blue;">new </span><span style="color:#2b91af;">FeatureAttribute</span>(<span style="color:blue;">typeof</span>(<span style="color:#2b91af;">FeedbackControlContextMenuProvider</span>))

To implement a context menu provider, your class must derive from ContextMenuProvider or a class that derives from ContextMenuProvider.  In the example solution I have derived my ContextMenuProvider from PrimarySelectionContextMenuProvider.  PrimarySelectionContextMenuProvider adds additional functionality to ContextMenuProvider by automatically displaying the context menu when the control is selected on the design surface when the developer right clicks the control.

Implementing a context menu is very simple.  In the constructor add a MenuAction for each desired context menu item to your class’s Items collection.  You can add one or more MenuActions and MenuActions can be nested.  MenuActions can also be marked as Checkable as in the above image.

The UpdateItemStatus event is raised just before the context menu item is displayed.  At your option you can hide or display items, enable or disable items and if the menu item is Checkable, set it as Checked or not.  The sample solution uses UpdateItemStatus to Check the MenuAction that represents the current value of the Feedback control.

When adding the MenuAction you must also provide implementation for the MenuAction Execute event.  The Execute event is raised when the menu item is clicked.

The below FeedbackControlContextMenuProvider constructor demonstrates setting up the handler for UpdateItemStatus event, the creation of a sub menu and adding items to it.

<span style="color:blue;">public </span>FeedbackControlContextMenuProvider() {

  <span style="color:blue;">this</span>.UpdateItemStatus +=
    <span style="color:blue;">new </span><span style="color:#2b91af;">EventHandler</span><<span style="color:#2b91af;">MenuActionEventArgs</span>>

When the HasDropDown property is true, it will display its sub items in a fly out menu.  If false, the sub items will be listed inline in the context menu.

To make a context menu item (MenuAction) Checkable, set the Checkable property to true.  To check a menu item, set the Checked property to true.

Set up an event handler for each menu item’s Execute event.  This code will be called when the menu item is clicked at design-time.

Control Adorner

Design-time control adorners can be used to provide additional UIElements on the design surface adding features like selection handles, grid lines, grid rails, buttons or other design-time features that your control requires at design-time.  The display of an adorner is controlled by a SelectionPolicy.  I recommend that you read up on the base class that all policies like SelectionPolicy derive from, ItemPolicy.

MSDN has a great two adorner articles Adorner Architecture and AdornerProvider Class that provide a solid understanding of adorners.  These articles also have several links to example code for implementing an adorner.

In the below image, the Feedback control design-time exposes a Rating Control in an adorner to provide a design-time interactive way to set the value of the Feedback control.  The lower set of 3 green ellipses and 2 black ellipses is the adorner.  

Adorner

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata for the Feedback control’s adorner.

<span style="color:blue;">new </span><span style="color:#2b91af;">FeatureAttribute</span>(<span style="color:blue;">typeof</span>(<span style="color:#2b91af;">FeedbackControlAdornerProvider</span>))

To implement an adorner your class must derive from AdornerProvider or a class that derives from AdornerProvider like PrimarySelectionAdornerProvider. 

The adorner in the sample solution derives from AdornderProvider.  Using this class instead PrimarySelectionAdornerProvider requires that we do more work to control when our adorner is visible, but this also gives us more control over when the adorner is displayed. 

PrimarySelectionAdornerProvider will display the adorner when the control is selected on the design surface.  This would be desirable for adorners like resize, control alignment, grid rails or control size label adorners.

However, our adorner is only used to set a control value so we didn’t want this adorner showing when the Feedback control is being resized or moved.  This was accomplished by deriving from AdornderProvider and applying a SelectionPolicy to the adorner class.

The below FeedbackControlAdornerProvider code is an example shell for an adorner.  I have stripped out the implementation code for clarity so we can focus on writing an adorner.  The solution sample code has comments for how I implemented the adorner in the sample.

The UsesItemPolicy attribute is used to associate a policy with an adorner.   The policy determines when the adorner is visible.

The IsToolSupported method allows you to inform the Designer if your adorner supports the currently selected tool or not.  Your adorner will not be displayed if the adorner does not support the current tool.  The Visual Studio 2010 WPF & Silverlight Designer ships with two tools, CreationTool and the SelectionTool.  The below IsToolSupported code demonstrates how to indicate that the adorner does not support the CreationTool.  

The Activate method can be used to create your adorner and add required event handlers.  At your option, you can elect to create your adorner each time Activate is called or you can create the adorner once here or in the adorner constructor and maintain a module level reference to it.

The Deactivate method is used to unhook any event handlers added in the Activate method.

Please beware that Activate and Deactivate may be called several times during the adorner lifetime.

[<span style="color:#2b91af;">UsesItemPolicy</span>(<span style="color:blue;">typeof</span>(FeedbackControlSelectionPolicy))]
<span style="color:blue;">internal class </span><span style="color:#2b91af;">FeedbackControlAdornerProvider </span>: <span style="color:#2b91af;">AdornerProvider </span>{

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">The main kinds of tools are:
  </span><span style="color:gray;">///   </span><span style="color:green;">- CreationTool (when the toolbox is activated - and clicking means create new object)
  </span><span style="color:gray;">///   </span><span style="color:green;">- SelectionTool (when clicking means select object)
  </span><span style="color:gray;">///
  /// </span><span style="color:green;">For this adorner, we don't want it to be in the way of creating new objects
  </span><span style="color:gray;">/// </span><span style="color:green;">so we'll hide it when the current tool is not the SelectionTool.
  </span><span style="color:gray;">/// </summary>
  /// <param name="tool"></param>
  /// <returns></returns>
  </span><span style="color:blue;">public override bool </span>IsToolSupported(<span style="color:#2b91af;">Tool </span>tool) {
    <span style="color:blue;">if </span>(tool <span style="color:blue;">is </span><span style="color:#2b91af;">SelectionTool</span>) {
      <span style="color:blue;">return true</span>;
    }
    <span style="color:blue;">return false</span>;
  }

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">Activate is called when the policy (specified by the UsesItemPolicy attribute)
  </span><span style="color:gray;">/// </span><span style="color:green;">for the adorner provider says it's found a new model item.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">protected override void </span>Activate(<span style="color:#2b91af;">ModelItem </span>item) {

    <span style="color:green;">// Create the adorner
    // Add required event handlers

    </span><span style="color:blue;">base</span>.Activate(item);
  }

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">Deactivate is called when the policy says a previously active item is no
  </span><span style="color:gray;">/// </span><span style="color:green;">longer within the policy.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">protected override void </span>Deactivate() {

    <span style="color:green;">// unhook event handlers added in the Active method.
    </span><span style="color:blue;">base</span>.Deactivate();
  }
}
Controlling Adorner Display

The below FeedbackControlSelectionPolicy class is a generic implementation of a selection policy that displays the adorner when the control is selected, but hides it when the control is being moved or resized.  You can reuse this selection policy in your applications to get the same behavior.

The below code is commented in detail.

<span style="color:blue;">using </span>Microsoft.Windows.Design.Interaction;
<span style="color:blue;">using </span>Microsoft.Windows.Design.Model;
<span style="color:blue;">using </span>Microsoft.Windows.Design.Policies;

<span style="color:blue;">namespace </span>CiderControls.Common.VisualStudio.Design.Controls {

  <span style="color:green;">//TODO 11 - FeedbackControlSelectionPolicy

  </span><span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">This FeedbackControlSelectionPolicy provides for hiding of the adorner when the
  </span><span style="color:gray;">/// </span><span style="color:green;">Feedback control is being resized or moved.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">internal class </span><span style="color:#2b91af;">FeedbackControlSelectionPolicy </span>: <span style="color:#2b91af;">PrimarySelectionPolicy </span>{

    <span style="color:blue;">private bool </span>_isFocused;

    <span style="color:blue;">public </span>FeedbackControlSelectionPolicy() {
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Determines if an item is part of the policy.  Called back from
    </span><span style="color:gray;">/// </span><span style="color:green;">OnPolicyItemsChanged on all the items in e.ItemsAdded
    </span><span style="color:gray;">/// </summary>
    /// <param name="selection"></param>
    /// <param name="item"></param>
    /// <returns></returns>
    </span><span style="color:blue;">protected override bool </span>IsInPolicy(<span style="color:#2b91af;">Selection </span>selection, <span style="color:#2b91af;">ModelItem </span>item) {
      <span style="color:blue;">bool </span>inPolicy = <span style="color:blue;">base</span>.IsInPolicy(selection, item);
      <span style="color:blue;">return </span>inPolicy && !_isFocused;
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Called when this policy is activated.  In order to determine
    </span><span style="color:gray;">/// </span><span style="color:green;">which items are in/out of a policy, we need to subscribe to events.
    </span><span style="color:gray;">/// </span><span style="color:green;">The base class, PrimarySelectionPolicy, subscribes to
    </span><span style="color:gray;">/// </span><span style="color:green;">changes in selection.  Additionally we need to subscribe to changes
    </span><span style="color:gray;">/// </span><span style="color:green;">in the focused task so we can disable during resize/move.
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">protected override void </span>OnActivated() {
      <span style="color:blue;">this</span>.Context.Items.Subscribe<<span style="color:#2b91af;">FocusedTask</span>>

Category Editor

Category editors are used in the properties window category view to provide a custom UI for editing related properties in a specific category.  The Text category editor is a good example of a category editor.  Category editors are implemented as DataTemplates.  DataTemplates provide developers the freedom  to implement any UI for editing properties.

I strongly recommend that you read the MSDN topics Property Editing Architecture and Property Editing Namespace.  These will provide you an outstanding overview of property editing.

CategoryView

The Feedback control has a Custom category editor that is pictured above.  This editor allows the four custom properties exposed by the Feedback control to be edited as a group.

You can include the PropertyMarker in your category editors as I have done above if you desire.  The PropertyMarker provides a lot of free functionality such as applying a data binding, applying a resource, extracting a value to a resource or resetting the property value.

Properties can be edited using the default PropertyValueEditor or a custom PropertyValueEditor.  Notice the above Value property has a custom PropertyValueEditor.  See the Property Value Editor section below for registering and implementing a PropertyValueEditor.

Examples of a default PropertyValueEditor are TextBoxes for strings or numbers, CheckBoxes for Booleans and ComboBoxes for properties of an enum type.  If you do not assign a custom PropertyValueEditor the Designer will select the most appropriate PropertyValueEditor for you without any action on your part.  In the above category editor, the first three properties are using the default PropertyValueEditor.

A custom PropertyValueEditor is used to provide a custom UI for setting a value on the property.  The above category editor Value property is edited using the Rating control.

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata for the Feedback control’s category editor.

AddCategoryEditor(feebackType, <span style="color:blue;">typeof</span>(<span style="color:#2b91af;">FeedbackControlCategoryEditor</span>));

To implement a category editor your class must derive from CategoryEditor.

When the ConsumesProperty method returns true, this indicates that the property is included in the category editor and won’t be listed outside of the category editor.  If ConsumesProperty returns false, the property will be listed as a separate row within the category but outside the category editor.  This category editor provides editing for all properties in the Custom category so ConsumesProperty always returns true.

The EditorTemplate property returns a DataTemplate that will act as the UI for the category editor.  The DataTemplate will have its DataContext set to a CategoryEntry.

The TargetCategory property identifies which category this category editor applies to.

<span style="color:blue;">namespace </span>CiderControls.Common.VisualStudio.Design.Controls {

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">A category editor is a group of properties that can be edited together.
  </span><span style="color:gray;">///
  /// </span><span style="color:green;">The Text and Brushes category editors are examples of a category editor.
  </span><span style="color:gray;">/// </span><span style="color:green;">The Text category editor consumes all the Text related properties and
  </span><span style="color:gray;">/// </span><span style="color:green;">displays them in a complex UI.  The brush category editor consumes all the
  </span><span style="color:gray;">/// </span><span style="color:green;">properties of type brush, displays UI and a list of properties to manipulate.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">internal class </span><span style="color:#2b91af;">FeedbackControlCategoryEditor </span>: <span style="color:#2b91af;">CategoryEditor </span>{

    <span style="color:blue;">public </span>FeedbackControlCategoryEditor() {
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Provides ability to list or not list properties by name in a category.
    </span><span style="color:gray;">/// </summary>
    /// <param name="propertyEntry"></param>
    /// <returns></span><span style="color:green;">return true if the property is edited by this category editor.</span>
    <span style="color:gray;">/// </span><span style="color:green;">Returning false will cause the property to be listed as a separate row</span>
    <span style="color:gray;">/// </span><span style="color:green;">within the category.</span><span style="color:gray;"></returns>
    </span><span style="color:blue;">public override bool </span>ConsumesProperty(<span style="color:#2b91af;">PropertyEntry </span>propertyEntry) {
      <span style="color:blue;">return true</span>;
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Caegory editor data template
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">public override </span><span style="color:#2b91af;">DataTemplate </span>EditorTemplate {
      <span style="color:blue;">get </span>{
        <span style="color:blue;">return </span><span style="color:#2b91af;">FeedbackControlResourceDictionary</span>.Instance.FeedbackCategoryEditor;
      }
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Used by Blend only
    </span><span style="color:gray;">/// </summary>
    /// <param name="desiredSize"></param>
    /// <returns></returns>
    </span><span style="color:blue;">public override object </span>GetImage(<span style="color:#2b91af;">Size </span>desiredSize) {
      <span style="color:blue;">return null</span>;
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">Defines the category this category editor is for.
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">public override string </span>TargetCategory {
      <span style="color:blue;">get </span>{ <span style="color:blue;">return </span><span style="color:#2b91af;">Constants</span>.STR_CUSTOM; }
    }
  }
}

EditorTemplate

The EditorTemplate is a DataTemplate stored in a resource dictionary.  The below code-behind file for the FeedbackControlResourceDictionary demonstrates one technique for conserving resources at design-time by exposing the resource dictionary as a Singleton and also how to expose one or more DataTemplates as strongly typed properties.

The above EditorTemplate property illustrates strong type access to the FeedbackCategoryEditor DataTemplate through the Instance property.  The Instance property provides Singleton design pattern access to the resource dictionary.

If the resource dictionary has more that one DataTemplate, you would add additional properties to expose additional DataTemplates. 

Notice the constant STR_FEEDBACKCATEGORYEDITORTEMPLATE is used in the FeedbackCategoryEditor property.  This same constant is also used in the below FeedbackControlResourceDictionary DataTemplate Key.  Using constants improves the maintainability your code by removing quoted strings, while IntelliSense provides quick access to your defined constants when editing code.

<span style="color:blue;">namespace </span>CiderControls.Common.VisualStudio.Design.Controls {

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">The FeedBackCategoryEditor property supplies a strong typed access
  </span><span style="color:gray;">/// </span><span style="color:green;">to the FeedBackCategoryEditor DataTemplate located in the
  </span><span style="color:gray;">/// </span><span style="color:green;">FeedbackControlResourceDictionary.xaml file.
  </span><span style="color:gray;">///
  /// </span><span style="color:green;">This class implements the Singleton pattern so that the resource
  </span><span style="color:gray;">/// </span><span style="color:green;">dictionary is only created once and is accessed through the Instance property.
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">internal partial class </span><span style="color:#2b91af;">FeedbackControlResourceDictionary </span>: <span style="color:#2b91af;">ResourceDictionary </span>{

    [<span style="color:#2b91af;">ThreadStatic</span>]
    <span style="color:blue;">private static </span><span style="color:#2b91af;">FeedbackControlResourceDictionary </span>_instance;

    <span style="color:blue;">private </span>FeedbackControlResourceDictionary() {
      InitializeComponent();
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">return a cached copy of the resource dictionary
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">internal static </span><span style="color:#2b91af;">FeedbackControlResourceDictionary </span>Instance {
      <span style="color:blue;">get </span>{
        <span style="color:blue;">if </span>(_instance == <span style="color:blue;">null</span>) {
          _instance = <span style="color:blue;">new </span><span style="color:#2b91af;">FeedbackControlResourceDictionary</span>();
        }
        <span style="color:blue;">return </span>_instance;
      }
    }

    <span style="color:gray;">/// <summary>
    /// </span><span style="color:green;">DataTemplate for the FeedbackCategoryEditor
    </span><span style="color:gray;">/// </summary>
    </span><span style="color:blue;">public </span><span style="color:#2b91af;">DataTemplate </span>FeedbackCategoryEditor {
      <span style="color:blue;">get </span>{
        <span style="color:blue;">return this</span>[<span style="color:#2b91af;">Constants</span>.STR_FEEDBACKCATEGORYEDITORTEMPLATE] <span style="color:blue;">as </span><span style="color:#2b91af;">DataTemplate</span>;
      }
    }
  }
}
Category Editor DataTemplate

The below DataTemplate is a Grid with four rows each containing a PropertyContainer control.

A PropertyContainer control gives you several options for specifying the type of UI editor you want for each property.  I have chosen to implement the UI using an InlineRowTemplate.

In the below XAML you’ll see Binding MarkupExtensions with ( ) and  [  ] used when assigning the Path.  If these are unfamiliar to you, please read the MSDN topic Binding Declarations Overview.   (  ) used in a Path is for attached properties.  [  ]  used in a Path is for indexers.

<span style="color:blue;"><</span><span style="color:#a31515;">DataTemplate
  </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Key</span><span style="color:blue;">="{</span><span style="color:#a31515;">x</span><span style="color:blue;">:</span><span style="color:#a31515;">Static </span><span style="color:red;">c</span><span style="color:blue;">:</span><span style="color:red;">Constants</span><span style="color:blue;">.STR_FEEDBACKCATEGORYEDITORTEMPLATE}">

The PropertyEntry establishes the context for the editors exposed by the PropertyContainer. Noticed that the property name in the Binding Path is surrounded with [  ], indicating that this is a indexer.

The InlineRowTemplate property takes a ControlTemplate that determines the UI for the property specified by the PropertyEntry.

Have a look at the DataContext property for the Grid. The PropertyContainer OwningPropertyContainer attached property establishes the PropertyContainer as the DataContext, giving the UI elements within the Grid, access to the PropertyContainer.

In Grid Row 0 the property name is displayed.

In Grid Row 1 the PropertyMarker and the InlinePropertyEditor are displayed. 

Yes, that’s all the required code to get all the functionality of the PropertyMarker.  Note: this currently the only way to surface the features exposed by the PropertyMarker like the Data Binding Builder in your custom editors.

The ContentPresenter is used to render the UI that edits the property value.  In the above ContentPresenter I’m using the current InlineEditorTemplate assigned to the property.  The first three properties in the category editor all use a TextBox for editing their values.  The last property Value uses the Rating control.  See next section for how the Rating control is assigned as the InlineEditorTemplate.

Property Value Editor

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata to assign the Rating control as the PropertyValueEditor for the Feedback control’s Value property.

AddMemberAttributes(feebackType,
    <span style="color:#2b91af;">Constants</span>.STR_VALUE,
    <span style="color:blue;">new </span><span style="color:#2b91af;">CategoryAttribute</span>(<span style="color:#2b91af;">Constants</span>.STR_CUSTOM),
    <span style="color:#2b91af;">PropertyValueEditor</span>.CreateEditorAttribute(<span style="color:blue;">typeof</span>(<span style="color:#2b91af;">RatingSelectorInlineEditor</span>)));

To implement a property value editor your class must derive from PropertyValueEditor.  In the constructor assign the InlineEditorTemplate property to a DataTemplate that has your value editor.  In the code I’m using the now familiar Singleton pattern to access the RatingEditorResourceDictionary’s RatingSelector DataTemplate.

<span style="color:blue;">namespace </span>CiderControls.Common.VisualStudio.Design.Controls {

  <span style="color:gray;">/// <summary>
  /// </span><span style="color:green;">Rating control property value editor
  </span><span style="color:gray;">/// </summary>
  </span><span style="color:blue;">internal class </span><span style="color:#2b91af;">RatingSelectorInlineEditor </span>: <span style="color:#2b91af;">PropertyValueEditor </span>{

    <span style="color:blue;">public </span>RatingSelectorInlineEditor() {
      <span style="color:blue;">this</span>.InlineEditorTemplate =
        <span style="color:#2b91af;">RatingEditorResourceDictionary</span>.Instance.RatingSelector;
    }
  }
}

The RatingSelector DataTemplate exposes the Rating control as its UI.  Notice the TwoWay binding mode.

<span style="color:green;"><!-- Using the Rating Control for the Value property inline editor. -->
</span><span style="color:blue;"><</span><span style="color:#a31515;">DataTemplate </span><span style="color:red;">x</span><span style="color:blue;">:</span><span style="color:red;">Key</span><span style="color:blue;">="{</span><span style="color:#a31515;">x</span><span style="color:blue;">:</span><span style="color:#a31515;">Static </span><span style="color:red;">c</span><span style="color:blue;">:</span><span style="color:red;">Constants</span><span style="color:blue;">.STR_RATESELECTORTEMPLATE}">
    <</span><span style="color:#a31515;">cc</span><span style="color:blue;">:</span><span style="color:#a31515;">Rating </span><span style="color:red;">Value</span><span style="color:blue;">="{</span><span style="color:#a31515;">Binding </span><span style="color:red;">Path</span><span style="color:blue;">=Value, </span><span style="color:red;">Mode</span><span style="color:blue;">=TwoWay}" />
</</span><span style="color:#a31515;">DataTemplate</span><span style="color:blue;">>
</span>

StringConverter for Properties of Type Object

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata to enable string editing of the Header property that is of type object.

Without the TypeConverterAttribute metadata, the Header property would not be editable in the properties window.  Assigning a StringConverter enables the properties window TextBox string to be assigned to the Header property value.  Without the StringConverter the developer will get an error when trying to edit the Header property value using the properties window.

AddMemberAttributes(feebackType,
    <span style="color:#2b91af;">Constants</span>.STR_HEADER,
    <span style="color:blue;">new </span><span style="color:#2b91af;">CategoryAttribute</span>(<span style="color:#2b91af;">Constants</span>.STR_CUSTOM),
    <span style="color:blue;">new </span><span style="color:#2b91af;">TypeConverterAttribute</span>(<span style="color:blue;">typeof</span>(<span style="color:#2b91af;">StringConverter</span>)));

Category Attribute

Properties are assigned to a category by using a CategoryAttribute as in the below code from CiderControlsAttributeTableBuilder.cs.

You should assign all custom properties to a category so that your properties are displayed in the correct category in the properties window.

AddMemberAttributes(feebackType,
    <span style="color:#2b91af;">Constants</span>.STR_COMMENTHEADING,
    <span style="color:blue;">new </span><span style="color:#2b91af;">CategoryAttribute</span>(<span style="color:#2b91af;">Constants</span>.STR_CUSTOM));

Description Attribute

While not used in the sample solution, a DescriptionAttribute can be added to a property’s metadata as in the below code.  The description string will appear in the property name ToolTip when the properties window is in alpha view.  This description will also appear in Expression Blend’s property inspector.

AddMemberAttributes(feebackType,
    <span style="color:#2b91af;">Constants</span>.STR_COMMENT,
    <span style="color:blue;">new </span><span style="color:#2b91af;">DescriptionAttribute</span>(<span style="color:#a31515;">"The comment is filled in by the end user."</span>),
    <span style="color:blue;">new </span><span style="color:#2b91af;">CategoryAttribute</span>(<span style="color:#2b91af;">Constants</span>.STR_CUSTOM));

ToolBoxBrowseable Attribute

The following line of code from CiderControlsAttributeTableBuilder.cs adds the required metadata to keep the Rating control from appearing in the ToolBox Choose Items dialog. 

You should use this metadata to keep controls that you do not want appearing in the ToolBox Choose Items dialog when a developer navigates to your control assembly and selects it or when your control assembly is in a folder that is loaded in the AssemblyFoldersEx registry key.  In the future I’ll publish a blog post on installing controls into the ToolBox.

AddTypeAttributes(ratingType,
    <span style="color:blue;">new </span><span style="color:#2b91af;">ToolboxBrowsableAttribute</span>(<span style="color:blue;">false</span>)
    );

You can also use the following alternate syntax taking advantage of the static (Shared for VB) No property that returns a new instance of the ToolBoxBrowseableAttribute with Browseable set to false.

AddTypeAttributes(ratingType,
    <span style="color:#2b91af;">ToolboxBrowsableAttribute</span>.No
    );

Downloads

C# Source Code Download

VB.NET Source Code Download

PowerPoint Slides from the Development Tools Ecosystem Summit Presentation

Links

Walkthrough: Creating a Category Editor

Walkthrough: Implementing an Inline Value Editor

How to: Create a Value Editor

Close

The title of this post had the words “Code Sharing” in it.  The above code provides a design-time for both the WPF & Silverlight controls in the sample solution.  I was just thinking how easy it was to forget that this code supports both platforms so easily.

Creating custom design-time experiences for developers using your controls is fun, gives your controls a very professional and polished feel and enables those users to be more productive when creating their applications.

Have a great day,

Just a grain of sand on the worlds beaches.

Posted in C#, Cider Designer, CodeProject, Extensibility, Extensibility Series, Silverlight, Silverlight Controls, VB.NET, Visual Studio 2010, WPF Controls, WPF General

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Karl Shifflett
Architect Gayle Manufacturing Company
United States United States
Karl loves .NET, WPF, WCF, ASP.NET, VB.NET and C#.
 
Awards:
 
  • December 2008 VB.NET Code Project Article Award
  • 2009 Code Project MVP
  • 2008 Code Project MVP
  • 2008 Microsoft MVP - Client App Dev
  • December 2007 VB.NET Code Project Article Award
  • Gold Medal Winner at IBM's 1998 PROIV Programming Contest in Las Vegas
Click here to check out my Blog
 
Click here to learn about Mole 2010 debugging tool for Visual Studio 2010
 
Click here to read about XAML Power Toys
 

Just a grain of sand on the worlds beaches.

Follow on   Twitter

Comments and Discussions

 
QuestionShortcut for content editors? PinmemberPhilipp Sumi4-May-10 2:34 
AnswerRe: Shortcut for content editors? PinmemberKarl Shifflett4-May-10 18:27 
GeneralRe: Shortcut for content editors? PinmemberPhilipp Sumi4-May-10 20:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 13 Jan 2010
Article Copyright 2010 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid